Author SHA1 Message Date
jiantw83 926936cd88 chore(manifest): bump jsc-cli to 0.2.3 2026-08-28 16:23:10 +08:00
jiantw83 b17941248e feat(doctor): 加入 CLI 實測流程 2026-08-28 16:23:05 +08:00
admin fc446bfe55 Merge pull request 'feat/plugin-dependencies/main' (#35) from feat/plugin-dependencies/main into develop
Reviewed-on: #35
2026-08-28 04:04:53 +00:00
admin 3833055499 Merge pull request 'feat/plugin-dependencies/declare-requires' (#34) from feat/plugin-dependencies/declare-requires into feat/plugin-dependencies/main
Reviewed-on: #34
2026-08-28 04:02:56 +00:00
jiantw83 4aa691a9cb feat(deploy): update 前檢查 jsc requires 相依版本 2026-08-28 11:59:16 +08:00
jiantw83 a89484c9fa feat(manifest): 宣告部署工具相依版本 2026-08-28 11:59:16 +08:00
admin c4e75f7f83 Merge pull request 'fix/deploy-hook-runtime-path' (#32) from fix/deploy-hook-runtime-path into develop
Reviewed-on: #32
2026-08-28 03:27:53 +00:00
jiantw83 382f946167 chore(release): 發布 jsc-cli 0.2.1 2026-08-28 11:22:13 +08:00
jiantw83 d918ccc6dc fix(deploy): 優先從穩定路徑尋找重啟閘門 2026-08-28 11:22:08 +08:00
admin a94a942712 Merge pull request 'fix(cli): 部署收尾的重啟狀態檔路徑改為一支 CLI 一份' (#30) from fix/restart-gate-per-cli-state into develop
Reviewed-on: #30
2026-08-27 10:51:25 +00:00
jiantw83 2e85dc4978 chore(cli): 三份 manifest 版本升到 0.2.0
What:`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 的 `version` 從 0.1.9 升到 0.2.0,三份同步,description 不動。

Why:這一輪改的是部署收尾留在機器上的檔案落點——狀態檔從單一檔案變成一支 CLI 一份的狀態目錄,`config-spec.tsv` 這份對外的設定落點清單也跟著改。那是對外可見的契約變更,不只是內部修正,所以走次版號而不是修訂號。版本不升,`version-guard.sh` 與 `jsc-cli:deploy` 都判不出本機還是舊版,機器上就不會被提示更新。

How:只改版號一個欄位。三份必須一致:`plugin.json` 給 marketplace、`.claude-plugin` 給 claude、`.codex-plugin` 給 codex,任一份落後都會讓那一路的版本比對抓錯。這一版的部署工具要搭 `jsc-hooks` 0.2.6 才有一支 CLI 一份的狀態檔,兩者一起發佈。

Who:`jsc-cli` 的三份 plugin manifest,配合這一輪重啟狀態檔路徑的修正發佈。
2026-08-27 18:49:58 +08:00
jiantw83 284292ffcb docs(deploy): 部署說明同步一支 CLI 一份的重啟狀態檔
What:`README.md`「部署留在機器上的檔案」那張表,`$JSC_HOME/restart-required` 那一列改成 `$JSC_HOME/restart-required.d/{cli}`,寫明一支 CLI 一份、內容是四行 key=value,以及路徑與格式的唯一來源在 `jsc-hooks/hooks/restart-gate.sh`。`skills/deploy/SKILL.md` 第 9 步的路徑說明同步改寫,補上「每支 CLI 只讀自己那一份」與「重啟一支只清自己那份、別支的閘門還立著」,全篇維持英文。

Why:這兩份說的是同一件事在不同讀者面前的樣貌——README 給看存取庫的人,技能文件給執行部署的模型。收尾提示裡的路徑是操作者唯一會拿到的線索,寫成舊的單一檔案就對不上機器上的實況,也會讓人以為重啟一支就把全部閘門解除了,那正是這一輪要修掉的錯誤認知。

How:只改說明,第 9 步的完成判準與那句固定的重啟指示都不動。舊表格那一列原本寫「一行一次成功部署」,其實是四行 key=value 的狀態檔,這次一併訂正。路徑與格式指回 `restart-gate.sh`,兩個存取庫不各自維護一份格式說明。

Who:`jsc-cli` 的存取庫說明與 `jsc-cli:deploy` 技能文件,對齊 `jsc-hooks` 的重啟狀態檔設計。
2026-08-27 18:49:58 +08:00
jiantw83 98781a331f fix(deploy): 部署工具的重啟狀態檔路徑改為一支 CLI 一份
What:`tools/deploy.sh` 的 `restart` 那一行改印 `$JSC_HOME/restart-required.d/{CLI 代號}`,檔頭註解一併改寫成「轉呼叫 `restart-gate.sh require` 掛上這支 CLI 的閘門」。`tools/write-guides.sh` 產生的更新指引,狀態檔說明改成一支 CLI 一份、重啟只清自己那份、別支的閘門不受影響。`tools/config-spec.tsv` 的 `$JSC_HOME/restart-required` 那一列改成 `$JSC_HOME/restart-required.d` 目錄,說明改為「該 CLI 那份不存在代表這支沒有待重啟的部署」。

Why:`jsc-hooks` 這一輪把狀態檔改成一支 CLI 一份,路徑從單一檔案變成狀態目錄底下的一份。`deploy.sh` 印的是操作者接下來要看的檔案路徑,印錯就指向一個不存在的檔案;`write-guides.sh` 寫出的更新指引是下一輪部署的依據,留著舊路徑會讓人以為刪掉那個檔案就能解除閘門;`config-spec.tsv` 是 `/jsc-cli:doctor` 比對設定落點的依據,路徑對不上就查不到這份執行期暫態。

How:三處都只跟著改路徑與說明,掛閘門的動作本來就是轉呼叫 `jsc-hooks` 的 `restart-gate.sh require`,這裡不重寫一份判定,也不自己組狀態檔內容——路徑與格式的唯一來源留在 `restart-gate.sh`。`config-spec.tsv` 那一列的型別仍是 `global` 的執行期暫態、備份與還原都維持 `none`,欄位數不變。

Who:`jsc-cli:deploy` 的部署收尾與更新指引,對齊 `jsc-hooks` 一支 CLI 一份的重啟狀態檔。
2026-08-27 18:49:58 +08:00
admin 07a652b309 Merge pull request 'feat(cli): 部署收尾產生更新與移除指引,並掛上部署後重啟閘門' (#28) from feat/skillset-governance/main into develop
Reviewed-on: #28
2026-08-27 08:54:45 +00:00
admin 6bbcc2a695 Merge pull request 'feat(cli): 部署收尾產生更新與移除指引,並掛上部署後重啟閘門' (#27) from feat/skillset-governance/deploy-guides-and-restart-gate into feat/skillset-governance/main
Reviewed-on: #27
2026-08-27 08:39:02 +00:00
jiantw83 0f9aeefead chore(manifest): 三份 manifest 版本升到 0.1.9
What:`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 的 `version` 由 0.1.8 改為 0.1.9。

Why:本次新增 `write-guides.sh`、`deploy.sh` 收尾多掛一道重啟閘門、`deploy` 技能多兩個步驟,設定規格表也多九列,屬於行為變更,版本要跟著往上走,各 CLI 才知道要更新。

How:三份只改 `version` 一個欄位,其餘內容不動,三份保持同一版號。

Who:`jsc-cli` 外掛的套件描述檔。
2026-08-27 16:34:17 +08:00
jiantw83 afc4c5b50a docs(cli): README 補上兩份指引、重啟狀態檔與新增環境變數
What:`README.md` 四處增修。工具表新增 `tools/write-guides.sh` 一列,`tools/deploy.sh` 那一列補上收尾寫重啟狀態檔與 `restart` 行;環境變數表新增 `JSC_HOME` 與 `JSC_RESTART_GATE` 兩列;新增「部署留在機器上的檔案」一節,用表列出三個檔案的產生時機與用途,並寫明兩份指引一律整份覆寫、`uninstall` 兩者都不產生。

Why:部署會在機器上留下三個檔案,這件事原本 README 一個字都沒寫。操作者不知道更新與移除的依據就在 `$JSC_HOME` 底下,也不知道 `restart-required` 存在代表什麼,只能去讀腳本註解。

How:三個檔案併成一張表,欄位是「何時產生」與「用途」,讓人一眼分得出哪些是可以放心刪的(指引重跑就有)、哪些有判定意義(重啟狀態檔)。閘門的判讀與逃生門明寫在 `jsc-hooks` 那一邊,`jsc-cli` 只負責寫狀態檔,避免兩份文件各寫一套判定規則。

Who:讀 `jsc-cli` 說明的操作者,以及要手動更新或移除技能組的人。
2026-08-27 16:34:16 +08:00
jiantw83 9377c2fbc7 feat(config-spec): 設定規格表補上重啟閘門、兩份指引與四項既有設定共九列
What:`tools/config-spec.tsv` 新增九列。這次新增的五項:`JSC_RESTART_GATE`、`JSC_WIKI_REPO_SKILLSET`、`$JSC_HOME/update-guide.md`、`$JSC_HOME/remove-guide.md`、`$JSC_HOME/restart-required`;補登既有但漏列的四項:`JSC_LANG_GUARD`、`JSC_COMMENT_SCOPE`、`JSC_CHANGED_FILE`、`JSC_SIMPLIFIED_FILE`。

Why:這一份是體檢與設定共用的唯一規格表,`scan-config.sh` 與 `/jsc-cli:doctor` 都讀它。沒登錄的設定項體檢查不到,等於機器上有一批設定沒人管;`orphans` 那一側也對不起來。準則寫的「新增設定時要同步補一列」就是為了這件事。

How:兩份指引標 `manual`,缺了就重跑 `/jsc-cli:deploy`,不由體檢自動補。`$JSC_HOME/restart-required` 與 `JSC_CHANGED_FILE`、`JSC_SIMPLIFIED_FILE` 都是執行期暫態,驗證與修法欄一律標 `none` 與 `-`:不存在是正常狀態,標成必要項會讓體檢把「沒有待重啟的部署」誤判成缺失。三個 `off` 開關(`JSC_RESTART_GATE`、`JSC_LANG_GUARD`、`JSC_COMMENT_SCOPE`)預設值一律寫 `on`,說明欄講明什麼情況才關。九列的欄位數與既有列一致為 8 欄。

Who:`scan-config.sh`、`/jsc-cli:doctor` 的執行環境體檢與 `/jsc-cli:setup` 的引導設定。
2026-08-27 16:34:16 +08:00
jiantw83 eca467696f feat(deploy): deploy 技能收尾加上產生指引與要求重啟兩步
What:`skills/deploy/SKILL.md` 新增兩個步驟並改寫 `description`。新的第 7 步:install 或 update 在所有 CLI 跑完之後,整台機器跑一次 `tools/write-guides.sh {mode} {domain}...`,完成條件是兩份指引都印出 `wrote` 行;原本的回報順延為第 8 步;新的第 9 步:收尾一律印出重啟指示「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」,並把兩份指引的路徑講出來。

Why:兩份指引與重啟提示都是部署收尾的一部分,腳本做得到、技能流程沒寫,就等於沒人會跑。重啟這件事尤其要在收尾講清楚:`deploy.sh` 已經把這一輪記進 `$JSC_HOME/restart-required`,使用者不知道要重啟就會繼續用舊版技能,然後以為部署沒生效。

How:指引那一步明寫「整台機器跑一次」,排在每個 CLI 都跑完之後——第 5 步是一個 CLI 一個子代理,指引寫的卻是整台機器的樣貌,跟著 CLI 跑就會被覆寫成最後一支的內容。`uninstall` 跳過這一步:指引描述的是裝好的技能組。重啟指示用固定字句,不讓每次回報各講一套;`JSC_RESTART_GATE=off` 作為逃生門一併寫出,判讀在 `jsc-hooks`。

Who:`/jsc-cli:deploy` 技能的執行流程與收尾回報。
2026-08-27 16:34:16 +08:00
jiantw83 c397994ce8 feat(deploy): 部署收尾轉呼叫 restart-gate.sh require 掛上重啟閘門
What:`tools/deploy.sh` 新增 `restart_gate_sh()` 與 `mark_restart()` 兩個函式,並在全部指令成功、印出 `result` 之前呼叫 `mark_restart`。`install` 與 `update` 會轉呼叫 `jsc-hooks` 的 `hooks/restart-gate.sh require {模式} {domain}...` 掛上重啟閘門,成功就多印一行 `restart<TAB>{狀態檔路徑}`;`uninstall` 與 dry-run 不寫。輸出行別表與檔頭的環境變數說明同步補上。

Why:部署換掉的是磁碟上的技能檔,目前工作階段載入的還是舊版。這段落差期間跑技能,改動看起來沒生效,人會以為部署失敗又重跑一次。要有一個「這台機器有一輪部署還沒重啟」的證據留在檔案上,判定那一端才擋得下來。

How:狀態檔的路徑、格式與判讀全留在 `jsc-hooks` 的 `restart-gate.sh`,這裡只轉呼叫它的 `require` 子命令,比照 `jsc-sdlc` 轉呼叫 `sdlc-gate.sh wp-lock` 的慣例。兩邊各拼一份格式就會對不上:這裡一開始自己寫四欄 TSV,而 hooks 那端讀的是 `key=value`,狀態檔存在卻解不出欄位,改成轉呼叫才修好,格式只能有一個真實來源。找腳本的順序比照 `jsc-sdlc` 的 `wp-gate.sh`:環境變數 `JSC_HOOKS_DIR` 優先,再找並排的工作樹,最後找 plugin 快取;找不到就印 `note` 行據實說「這次沒有掛上重啟閘門」,不自己補寫一份——閘門本來就由 `jsc-hooks` 判讀,它不在就沒有判定點,寫下去只是留一個沒人讀的檔案,還會讓下一輪誤以為閘門掛上了。`require` 一律接 `</dev/null`:它不讀標準輸入,但這裡的標準輸入是宿主餵進來的管線,不關掉會卡住。

Who:`/jsc-cli:deploy` 的 install 與 update 收尾,與 `jsc-hooks` 的部署後重啟閘門對接。
2026-08-27 16:34:16 +08:00
jiantw83 d1da14c778 feat(write-guides): 新增指引產生腳本,部署收尾寫下本機的更新與移除指引
What:新增 `tools/write-guides.sh`(`write-guides.sh [-n] {install|update} {domain}...`),產生 `$JSC_HOME/update-guide.md` 與 `$JSC_HOME/remove-guide.md` 兩份指引,兩份都整份覆寫。輸出 TSV 四種行別:`cli`(偵測到的 CLI)、`plan`(dry-run 時會寫入的檔案)、`wrote`(實際寫入的檔案)、`note`(非致命說明);結束碼 0 寫成、2 參數錯誤、4 目錄或檔案寫不進去。

Why:更新與移除這兩件事原本只存在於技能內文裡。CLI 壞掉、沒有工作階段、或是換人接手的時候,機器上找不到任何一份寫著「這台機器要怎麼更新、怎麼移除」的東西,只能回頭讀技能。指引落成本機檔案,不開工作階段也照著走得完。

How:內容一律依實際偵測結果生成,不寫死。CLI 清單來自 `detect-clis.sh`;每支 CLI 的指令字面直接取自 `deploy.sh -n` 的輸出,所以指引寫的就是 `deploy.sh` 真正會跑的指令——各 CLI 的差異只有 `deploy.sh` 一個真實來源,這裡再抄一份就會有兩套指令,改了一邊忘了另一邊,指引就開始騙人。kiro 走不走本地複製退路,也是讀 `deploy.sh` 的 `note` 行判斷,不自己再探測一次。獨立成一支腳本、不併進 `deploy.sh`:`deploy.sh` 的職責是「對單一 CLI 部署」,一輪部署會逐個 CLI 呼叫它,而指引寫的是整台機器的樣貌,只該產生一次;併進去還會與 `deploy.sh -n` 形成雙向遞迴。寫檔走暫存檔再 `mv`,寫一半不會留下半份指引。移除指引另外列出 plugin 指令管不到的殘留物(`$JSC_HOME`、本地 clone、kiro 技能目錄、rc 檔的 `# jsc-config` 段落)與各自清掉的影響。

Who:`/jsc-cli:deploy` 的 install 與 update 收尾,以及日後要手動更新或整組移除的操作者。
2026-08-27 16:34:16 +08:00
admin 7af195b342 Merge pull request 'fix(deploy): kiro 本地複製補上 tools、references、templates、hooks 與 plugin.json' (#25) from fix/kiro-copy-missing-tools into develop
Reviewed-on: #25
2026-08-27 04:40:09 +00:00
jiantw83 ebca8883fc chore(version): jsc-cli 版本推進到 0.1.8
What:
把三份 manifest 的 `version` 從 `0.1.7` 改成 `0.1.8`:`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`。三份只改版本號,其他欄位不動。

Why:
`kiro_copy()` 的修正改變了 kiro 實際拿到的檔案內容,各 CLI 要靠版本號才看得出本機落後、才會重新部署。版本不動,已經裝好舊版的機器不會更新,修正等於沒發出去。

How:
三份 manifest 一起改,避免不同 CLI 讀到不一樣的版本。`kiro_copy()` 這次也把 `plugin.json` 複製到 `$KIRO_SKILLS/jsc-cli/`,所以 kiro 上查到的版本會和 master 一致。

Who:
jsc-cli 技能組的版本發布。
2026-08-27 12:37:04 +08:00
jiantw83 e01ec1b3f5 fix(deploy): kiro 本地複製補上 tools、references、templates、hooks 與 plugin.json
What:
改寫 `tools/deploy.sh` 的 `kiro_copy()`。原本只複製 `skills/.` 一個目錄,現在同時複製 `tools`、`references`、`templates`、`hooks` 四個子目錄與 `plugin.json`。四個子目錄逐一判斷來源是否存在,存在才複製;`plugin.json` 缺了不算錯誤,函式一律回傳 0。`deploy_kiro()` 印給使用者看的 `note` 訊息也一併改寫,說清楚退路實際複製了哪些內容。

Why:
`kiro-cli 2.18.1` 已經沒有 `plugin` 子指令,kiro 只能走本地複製這條退路。過去只複製 `skills/` 還能動,是因為舊版 SKILL.md 沒叫 kiro 跑同伴目錄裡的腳本。2026-08-27 放行的這批技能(cli 0.1.7、sdlc 0.1.9、git 0.0.8、gitea 0.1.5 等)把 `jsc-sdlc/tools/wp-gate.sh owns`、`jsc-gitea/tools/pr-watch.sh`、`jsc-git/tools/base-branch.sh --derive` 寫成 SKILL.md 的完成條件,kiro 於是拿到一份「指令要求跑腳本、腳本卻不在機器上」的技能,`jsc-sdlc:implement` 與 `jsc-git:pr` 會直接卡住,比不更新更糟。缺 `references/` 也讓 `consensus.md`、`branch.md`、`guidelines.md` 在 kiro 上讀不到。

How:
目標路徑維持 `$KIRO_SKILLS/jsc-{domain}/`,跨 plugin 的引用(例如 `jsc-gitea/tools/…`)以 `$KIRO_SKILLS` 為根就解析得到,不必改動任何 SKILL.md。每個子目錄都用「先 `mkdir -p` 目標,再複製 `src/.` 到 `dst/`」的寫法:目標目錄已經存在時,`cp -R src dst/` 會把來源塞進 `dst/{名稱}/{名稱}`,第二次更新就多疊一層。`plugin.json` 以單檔複製處理,讓 `version-guard.sh` 之類的呼叫端查得到本機版本。函式尾端明確 `return 0`,避免 `plugin.json` 不存在時的測試結果變成函式結束碼。

Who:
jsc-cli:deploy 技能的 kiro 部署退路。
2026-08-27 12:37:04 +08:00
jiantw83 1840af2ace Merge pull request 'feat(cli): 設定規格登錄 PR 盯場輪詢間隔' (#23) from feat/sdlc-flow-rules/main into develop 2026-08-27 03:39:41 +00:00
admin 96cd7bd14d Merge pull request 'feat(cli): 設定規格登錄 PR 盯場輪詢間隔' (#22) from feat/sdlc-flow-rules/config-spec-pr-watch into feat/sdlc-flow-rules/main
Reviewed-on: #22
2026-08-27 03:26:27 +00:00
jiantw83 bbd48b5d95 chore(manifest): 三份 manifest 版本升到 0.1.7
What:`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 的 `version` 由 0.1.6 改為 0.1.7。

Why:設定規格表新增一個環境變數,讀表的部署與體檢行為跟著變,版本要往上走,各 CLI 才知道要更新。

How:三份只改 `version` 一個欄位,其餘內容不動,三份保持同一版號。

Who:`jsc-cli` 外掛的套件描述檔。
2026-08-27 11:20:30 +08:00
jiantw83 ffff2152c2 feat(config-spec): 登錄 JSC_PR_WATCH_INTERVAL 設定項
What:`tools/config-spec.tsv` 新增一列 `JSC_PR_WATCH_INTERVAL`:型別 `env`、全域、非必填、預設 `60`、檢查方式 `set`、缺少時 `ask`,說明寫明它是 `pr-watch.sh` 輪詢 PR 狀態的間隔秒數,實作在 `jsc-gitea/tools/pr-watch.sh`。

Why:設定規格表是 jsc 環境變數的清單正本,`jsc-cli` 的部署與體檢都讀它。新變數沒登錄進來,體檢就看不到它,使用者也無從得知有這個旋鈕可以調。

How:只加一列,排在同為 `jsc-hooks` 系列旋鈕的 `JSC_VERSION_TTL` 之後,欄位順序與既有各列相同;說明點名實作位置,讀表的人才知道去哪裡查行為。

Who:`jsc-cli:deploy` 與環境體檢,以及要調整盯 PR 頻率的使用者。
2026-08-27 11:20:30 +08:00
admin 7e6591e644 Merge pull request 'fix/codex-update-plugin-add' (#21) from fix/codex-update-plugin-add into develop
Reviewed-on: #21
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-27 02:31:25 +00:00
jiantw83 9a7fbeb229 fix(manifest): 三份 manifest 版本升到 0.1.6
What:三份 manifest 由 0.1.5 升到 0.1.6。

Why:codex 更新路徑的修正要能被 version-guard.sh 判定為落後,使用者才會收到更新
提示。不升版的話,帶著壞掉更新路徑的舊版會一直留在各 CLI 上。

How:以 jsc-meta 的 sync-skill-manifest.sh 統一 bump,三份同步成同一個值。

Who:跨 CLI 技能組批次部署。
2026-08-27 10:28:18 +08:00
jiantw83 63d4185a40 fix(deploy): codex 更新補上逐網域 plugin add
What:deploy_codex 的 update 分支在 marketplace upgrade 之後,補上逐網域的
plugin add,與 install 分支一致。

Why:codex 沒有 plugin update 子指令,只有 add、list、marketplace、remove。
marketplace upgrade 只重抓 marketplace 快照,而 jsc 的 marketplace.json 只列各
網域的 git URL、不含版本,內容不會變,codex 一律回「already up to date」並結束碼 0。
已安裝外掛的版本在 plugin add 當下決定,快取不會被連帶重抓。結果是十個網域裡八個
版本完全沒動,指令卻全部成功——只看結束碼會判定成功,是最難察覺的那種失敗。
實機驗證過:更新前 review 是 0.0.2,跑完 marketplace upgrade 仍是 0.0.2。

How:update 分支照 install 的寫法逐網域跑 plugin add。實測 codex 的 add 會就地
升級到快照裡的最新版(review 0.0.2 升到 0.0.5),不需要先 remove。同時在函式上方
寫明這個限制與理由,避免後人再把那一行當成多餘的重複而刪掉。

Who:跨 CLI 技能組批次部署。
2026-08-27 10:28:10 +08:00
admin 581b9de5bc Merge pull request 'feat/model-tags-add-opus-4-5-sonnet-4-5-gpt-5-4-mini' (#19) from feat/model-tags-add-opus-4-5-sonnet-4-5-gpt-5-4-mini into develop
Reviewed-on: #19
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-27 00:56:24 +00:00
jiantw83 497a16b877 feat(model-tags): 補上三款舊世代模型的能力標籤
What:在 `references/model-tags.md` 的對照表新增三列——claude-opus-4.5、claude-sonnet-4.5、gpt-5.4-mini,並把三份 plugin manifest 版本由 0.1.4 升到 0.1.5。

Why:這三款模型仍在線上,實務上常被指定使用。對照表沒有它們,`tools/model-tags.sh` 查不到標籤,jsc-sdlc 的階段閘門就無從判定,規劃與分析階段會被誤擋。

How:依既有欄位格式各補一列標籤。claude-opus-4.5 同時列出 reasoning-max 與 reasoning-high,遵守上位標籤必須並列的規則;claude-sonnet-4.5 給 reasoning-high、coding、long-context、vision,不列 fast;gpt-5.4-mini 比照 gpt-5.x-mini 給 coding、fast、cheap。接著用 jsc-meta 的 sync-skill-manifest.sh 同步 README 技能目錄並升版,README 沒有增減技能小節。

Who:jsc-cli 的 models 技能與其 `tools/model-tags.sh`;下游是 jsc-hooks 的 sdlc-gate,它讀 `$JSC_HOME/model-tags.tsv` 執行閘門。
2026-08-26 18:40:11 +08:00
admin 2720bf4d33 Merge pull request 'fix/scan-config-jsc-home-default-fallback' (#17) from fix/scan-config-jsc-home-default-fallback into develop
Reviewed-on: #17
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-26 04:34:06 +00:00
jiantw83 a2f1f65ff8 chore(cli): 因應 scan-config.sh 修正,將外掛版本升級至 0.1.4 2026-08-26 12:26:52 +08:00
jiantw83 7077d65766 fix(cli): 展開 $JSC_HOME 時補上文件記載的預設值 ~/.jsc,避免 $JSC_HOME/* 設定列在未設環境變數時被誤判為缺失或未設定 2026-08-26 12:26:52 +08:00
admin 27ddf4e5a6 Merge pull request 'fix/deploy-domain-prefix-and-kiro-plugin-probe' (#15) from fix/deploy-domain-prefix-and-kiro-plugin-probe into develop
Reviewed-on: #15
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-26 03:32:00 +00:00
jiantw83 49cd4d3ee4 fix(deploy): 修正 domain 前綴歧義與 kiro-cli 探測
What: deploy.sh 收到帶 jsc- 前綴的 domain 名時自動去掉前綴再組 jsc-{domain}@jsc;deploy_kiro 先探測這個版本的 kiro-cli 認不認得 plugin 子指令,不認得就整批直接走本地複製退路。
Why: marketplace.json 的 plugins[].name 本身就帶 jsc- 前綴,SKILL.md 只寫「domain 名單來自 marketplace」沒講清楚要不要去前綴,實際執行時餵進去兜成 jsc-jsc-ask@jsc 雙重前綴,claude、copilot、antigravity、kiro 四支 CLI 的更新全部第一輪失敗。另外 kiro-cli 2.18.1 這個版本已經完全沒有 plugin 子指令,逐一嘗試再退回複製會先洗出一長串看似失敗、實則設計內的錯誤訊息。
How: 腳本層正規化 domain 參數(去前綴),比只改文件更可靠——不管呼叫端傳哪種格式都對。kiro 的探測用 kiro-cli --help-all 抓子指令清單,一次性判斷,不逐一撞錯誤才退回;探測本身不算部署動作,不印 cmd/exit。SKILL.md 同步補上前綴說明。
Who: jsc-cli:deploy 的執行正確性與輸出可讀性。
2026-08-26 11:30:18 +08:00
admin 9067c643b2 Merge pull request 'feat/cli-doctor-and-setup-skills' (#13) from feat/cli-doctor-and-setup-skills into develop
Reviewed-on: #13
2026-08-26 02:51:23 +00:00
jiantw83 e655f9a963 feat(setup): 新增引導與自動設定技能
What: 新增 jsc-cli:setup,讀 doctor 的待修清單逐項確認後修復,並新增 tools/apply-config.sh 負責實際寫入。
Why: 體檢找得出問題,修還是得靠人一個一個查文件。修法又分三種:算得出來的、要人給值的、只能手動的,混在一起講不清楚。
How: 依規格表的 fix 欄分流,auto 直接寫、ask 先用決策樹問到值、manual 印步驟。複合修復交回原主(deploy、hooks-install、models)。apply-config.sh 只動 rc 檔的 # jsc-config 標記段落,寫前備份到 $JSC_HOME/backup/config/,寫後重讀驗證,fish 自動改用 set -gx 語法。
Who: 體檢與修復流程,接在 jsc-cli:doctor 之後。
2026-08-26 10:43:50 +08:00
jiantw83 2adf9a172e feat(doctor): 新增執行環境體檢技能
What: 新增 jsc-cli:doctor,一次體檢技能版本、Hook 接線、全域設定與自我設定,只讀不改,結果寫進 wiki CHECK_{HASH}。
Why: 安裝或更新技能組之後,沒有任何工具說得出這台機器還缺什麼。設定散在環境變數、rc 檔與專案目錄,出錯時只能一個一個猜。
How: 版本比對複用 jsc-hooks 的 version-guard.sh report,接線狀態複用新加的 wire-cli.sh status(唯讀),設定則由 tools/scan-config.sh 比對 tools/config-spec.tsv 判定必要或選擇。規格表是必要性的唯一判準,掃描只負責抓出漏登錄的變數。
Who: 體檢與修復流程,搭配 jsc-cli:setup 收尾。
2026-08-26 10:43:37 +08:00
admin 08ad10353b Merge pull request 'feat/cli-delegate-subagent' (#11) from feat/cli-delegate-subagent into develop
Reviewed-on: #11
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-25 08:58:11 +00:00
jiantw83 17329a94d3 feat(cli): 新增 delegate 子代理技能 2026-08-25 16:56:22 +08:00
admin 7df56159e1 Merge pull request 'fix/skillset-audit-compliance-and-guard-fixes' (#10) from fix/skillset-audit-compliance-and-guard-fixes into develop
Reviewed-on: #10
2026-08-25 07:14:43 +00:00
jiantw83andClaude Opus 5 3082e5167f chore(cli): 三份 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 a739ae20f8 docs(cli): 同步文件與參考資料
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 26ffaa8a07 fix(cli): 補齊稽核缺失並修掉護欄失效
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 cea4d701aa Merge pull request '發佈 jsc-cli 0.0.6:deploy 依版本落後推薦更新' (#9) from develop into master
Reviewed-on: #9
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-25 05:06:04 +00:00
23 changed files with 1832 additions and 61 deletions
+3 -3
View File
@@ -43,7 +43,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git" "url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
}, },
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄" "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
}, },
{ {
"name": "jsc-log", "name": "jsc-log",
@@ -75,7 +75,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git" "url": "https://gitea.jsc.idv.tw/plugins/review.git"
}, },
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組" "description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
}, },
{ {
"name": "jsc-sdlc", "name": "jsc-sdlc",
@@ -83,7 +83,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git" "url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
}, },
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)" "description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
} }
] ]
} }
+3 -3
View File
@@ -43,7 +43,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git" "url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
}, },
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄" "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
}, },
{ {
"name": "jsc-log", "name": "jsc-log",
@@ -75,7 +75,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git" "url": "https://gitea.jsc.idv.tw/plugins/review.git"
}, },
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組" "description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
}, },
{ {
"name": "jsc-sdlc", "name": "jsc-sdlc",
@@ -83,7 +83,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git" "url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
}, },
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)" "description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
} }
] ]
} }
+9 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "jsc-cli", "name": "jsc-cli",
"version": "0.0.6", "version": "0.2.3",
"description": "CLI 偵測、模型能力標籤與技能庫批次部署", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
"name": "JSC" "name": "JSC"
@@ -13,5 +13,11 @@
"cli", "cli",
"skills", "skills",
"cross-tool" "cross-tool"
] ],
"jsc": {
"requires": {
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8"
}
}
} }
+9 -3
View File
@@ -1,6 +1,12 @@
{ {
"name": "jsc-cli", "name": "jsc-cli",
"version": "0.0.6", "version": "0.2.3",
"description": "CLI 偵測、模型能力標籤與技能庫批次部署", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills" "skills": "./skills",
"jsc": {
"requires": {
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8"
}
}
} }
+50 -3
View File
@@ -22,8 +22,16 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| 工具 | 用途 | | 工具 | 用途 |
| --- | --- | | --- | --- |
| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;kiro 的執行檔為 `kiro-cli`) | | `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;antigravity 的執行檔為 `agy`、kiro 為 `kiro-cli`) |
| `tools/test-clis.sh` | 實際呼叫已偵測到的 AI CLI,跑版本、說明頁與 plugin 清單等唯讀命令(`test-clis.sh [cli...]`);印出每項命令、結束碼、判定與輸出摘要,最後一行 `summary` 標出通過、降級、失敗、略過數。`JSC_CLI_TEST_TIMEOUT` 可調整單項命令逾時秒數,預設 10 秒 |
| `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。install 或 update 全數成功時,收尾轉呼叫 `jsc-hooks` 的 `restart-gate.sh require` 掛上重啟閘門,並印一行 `restart` 標出狀態檔位置;uninstall 不寫。尋找 `restart-gate.sh` 時優先用 `$JSC_HOME/current/jsc-hooks`、本地 clone 與 Kiro skills,最後才掃各 CLI 快取,避免部署收尾綁死單一 CLI 的版號路徑。狀態檔的路徑、格式與判讀全在 `restart-gate.sh`,這支腳本不自己拼——格式只留一個真實來源。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 |
| `tools/check-requires.sh` | `check-requires.sh {cli} {manifest}` 檢查 manifest 的 `jsc.requires` 最低版本。沒有宣告就通過;版本不符或缺相依 plugin 就回 `status=blocked`。`deploy.sh update` 在每個 domain 更新前呼叫它,不符就跳過該 domain 並列出原因 |
| `tools/write-guides.sh` | 產生這台機器專屬的更新指引 `$JSC_HOME/update-guide.md` 與移除指引 `$JSC_HOME/remove-guide.md`(`write-guides.sh [-n] {install\|update} {domain}...`),一輪部署跑一次。CLI 清單取自 `detect-clis.sh`,每支 CLI 的指令字面直接取自 `deploy.sh -n` 的輸出,所以指引寫的就是實際會跑的指令;kiro 走不走本地複製退路也依實際偵測結果標注 |
| `tools/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 |
| `tools/model-config.sh` | 解析 SDLC 各階段的偏好模型鏈(`get {stage}`、`list`、`resolve {stage}` 印出目前 CLI 可用的第一個模型);專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`,格式見 `references/model-tags.md`。鏈只影響建議與偏好順序,不影響閘門放行 | | `tools/model-config.sh` | 解析 SDLC 各階段的偏好模型鏈(`get {stage}`、`list`、`resolve {stage}` 印出目前 CLI 可用的第一個模型);專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`,格式見 `references/model-tags.md`。鏈只影響建議與偏好順序,不影響閘門放行 |
| `tools/config-spec.tsv` | 設定規格表:每個環境變數與設定檔一列,標明必要或選擇、預設值、驗證方式、修法。體檢與設定共用這一份,新增設定時要同步補一列 |
| `tools/scan-config.sh` | 依規格表盤點設定現況(`scan {global\|project\|all}` 印 TSV 與 summary、`spec` 印規格表、`orphans` 找出漏登錄的變數);`-o` 為離線模式,需要連 Gitea 的檢查一律標 skipped。唯讀,不寫任何設定;帶 TOKEN 的項目只印 set 或 unset |
| `tools/apply-config.sh` | 把設定寫進 shell rc 檔(`set {KEY} {VALUE}`、`unset {KEY}`)或建立目錄(`mkdir {PATH}`);`show` 印出目前設定,`rcfiles` 印出會寫入的檔案。內容一律收在 `# jsc-config` 標記段落之間,整段重寫不疊加,段落外不動。動檔案前先備份到 `$JSC_HOME/backup/config/{yyyyMMdd_HHmmss}/`,備份失敗就不寫;寫完重讀驗證。fish 自動改用 `set -gx` 語法 |
| `tools/model-tags.sh` | 解析 `references/model-tags.md` 的能力標籤與 SDLC 階段必要標籤(`dump`、`sync`、`stage {階段}`、`model {模型 id}`、`gate {階段} {模型 id}`);`sync` 寫出 `$JSC_HOME/model-tags.tsv` 供 `jsc-hooks` 的 sdlc-gate 讀取 | | `tools/model-tags.sh` | 解析 `references/model-tags.md` 的能力標籤與 SDLC 階段必要標籤(`dump`、`sync`、`stage {階段}`、`model {模型 id}`、`gate {階段} {模型 id}`);`sync` 寫出 `$JSC_HOME/model-tags.tsv` 供 `jsc-hooks` 的 sdlc-gate 讀取 |
## Skills 目錄 ## Skills 目錄
@@ -34,14 +42,53 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `models` ### `models`
讀取各已安裝 CLI 可使用的模型並加上能力標籤(`references/model-tags.md`),用 `tools/model-tags.sh sync` 把標籤表寫進 `$JSC_HOME/model-tags.tsv` 供 sdlc-gate 讀取,列出 SDLC 各階段的必要標籤(plan、analyze 需 `reasoning-max`;implement 需 `coding`;maintain 任意),並用 `tools/model-config.sh` 列出各階段的偏好模型鏈。`jsc-sdlc` 閘門一律以能力標籤判定,偏好鏈只用來建議切換目標。 用 `tools/list-models.sh` 讀出各已安裝 CLI 可使用的模型並加上能力標籤(`references/model-tags.md`),用 `tools/model-tags.sh sync` 把標籤表寫進 `$JSC_HOME/model-tags.tsv` 供 sdlc-gate 讀取,列出 SDLC 各階段的必要標籤(plan、analyze 需 `reasoning-max`;implement 需 `coding`;maintain 任意),並用 `tools/model-config.sh` 列出各階段的偏好模型鏈。`jsc-sdlc` 閘門一律以能力標籤判定,偏好鏈只用來建議切換目標。
### `delegate`
把單一明確任務交給另一個已安裝的 AI agent CLI 當作 subagent 執行。可指定目標 CLI,也可依任務需求用能力標籤篩選模型,或強制指定模型。每個目標分開派工,預設只讀,並回傳結構化結果供主 agent 驗證與彙整。此技能不負責模型盤點或 plugin 部署。
### `deploy` ### `deploy`
技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。 技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 呼叫 `tools/deploy.sh` 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)→ update 前逐一檢查 `jsc.requires`,版本不符就跳過該 domain 並回報缺哪一版 → install、update 後呼叫 `jsc-hooks:hooks-install`,並把每支 CLI 的 smoke 結果納入部署成敗。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。
### `doctor`
一次體檢執行環境,只讀不改。五項檢查:技能版本(`jsc-hooks/hooks/version-guard.sh report`)、Hook 接線(`jsc-hooks/tools/wire-cli.sh status`,唯讀子命令)、CLI 實測(`tools/test-clis.sh` 實際呼叫版本、說明頁與 plugin 清單)、全域設定與自我設定(`tools/scan-config.sh` 比對 `tools/config-spec.tsv`)。每項各出一張表,整份結果寫進 wiki `CHECK_{HASH}`,`HASH` 取 `{主機名}/{登入帳號}`,只保留最新一次。修復交給 `/jsc-cli:setup`,體檢本身不動任何設定。
### `setup`
修復 `/jsc-cli:doctor` 找出的問題,一次一項,逐項確認才動手。待修清單優先讀 wiki `CHECK_{HASH}`,沒有頁面就當場重掃一份。依修法分流:`auto` 用 `tools/apply-config.sh` 直接寫、`ask` 先用決策樹問到值再寫、`manual` 印出步驟交給操作者。複合修復交回原主:版本落後找 `/jsc-cli:deploy`、hook 未接線找 `/jsc-hooks:hooks-install`、缺 `model-tags.tsv` 找 `/jsc-cli:models`。每一項寫完都重驗一次,最後覆寫 CHECK 頁。
<!-- JSC-SKILLS:END --> <!-- JSC-SKILLS:END -->
## 環境變數
| 變數 | 用途 | 未設定時 |
| --- | --- | --- |
| `GITEA_HOST` | Gitea 站台(可省略 scheme,預設 https) | 用正本站台 `https://gitea.jsc.idv.tw` |
| `JSC_GITEA_OWNER` | 技能組存取庫的 owner | 用 `plugins` |
| `JSC_LOCAL_PLUGINS` | antigravity 與 kiro 退路用的本地 clone 目錄 | 用 `$JSC_HOME/plugins`(即 `~/.jsc/plugins`) |
| `JSC_KIRO_SKILLS` | kiro 退路複製 skills 的目標目錄 | 用 `~/.kiro/skills` |
| `JSC_DEPLOY_DRYRUN` | 設為 `1` 等同 `deploy.sh -n`,只印指令不執行 | 照常執行 |
| `JSC_WIKI_REPO_CHECK` | 體檢頁 `CHECK_CONTENTS`、`CHECK_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`;兩個都沒有就略過寫入,並把這一項列進待修 |
| `JSC_CONFIG_SPEC` | 改讀別份設定規格表(測試 `scan-config.sh` 時用) | 用 `tools/config-spec.tsv` |
| `JSC_CLI_TEST_TIMEOUT` | `test-clis.sh` 單項 CLI 實測命令的逾時秒數 | 用 `10` |
| `JSC_HOME` | hook 資料目錄,兩份指引與重啟狀態檔都寫在這裡 | 用 `~/.jsc` |
| `JSC_RESTART_GATE` | 設成 `off` 可略過部署後的重啟提示閘門(判讀在 `jsc-hooks`,`jsc-cli` 只負責寫狀態檔) | 照常提示重啟 |
`JSC_LOCAL_PLUGINS` 的預設值刻意避開 `~/plugins`:那是維護者放技能組開發 checkout 的地方,`git pull` 下去會蓋掉未提交的工作。這個變數指到的目錄若是開發中的樹(有未提交變更,或有未推送的 commit),`deploy.sh` 只印一行 `skip` 並直接用現地內容安裝,不執行 `git pull`。
## 部署留在機器上的檔案
| 檔案 | 何時產生 | 用途 |
| --- | --- | --- |
| `$JSC_HOME/update-guide.md` | install、update 收尾 | 下次更新的依據:偵測到的 CLI、每支的安裝方式與實際指令、marketplace token、domain 清單 |
| `$JSC_HOME/remove-guide.md` | install、update 收尾 | 整組移除的依據:各 CLI 的移除指令,加上 `$JSC_HOME`、本地 clone、kiro 技能目錄、rc 檔 `# jsc-config` 段落這些殘留物 |
| `$JSC_HOME/restart-required.d/{cli}` | install、update 全數成功時 | 一支 CLI 一份,內容是四行 key=value(`at`、`mode`、`domains`、`cli`)。`jsc-hooks` 只讀當前 CLI 那一份來提示重啟,別支的不影響這一支;逃生門 `JSC_RESTART_GATE=off` 也在那邊判讀。路徑與格式的唯一來源是 `jsc-hooks/hooks/restart-gate.sh`,`deploy.sh` 只轉呼叫它的 `require` |
兩份指引一律整份覆寫,內容依產生當下的偵測結果生成,不寫死。`uninstall` 不產生指引、也不寫重啟狀態檔。
## 相關 domain ## 相關 domain
- [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):取得 domain 名單(`tools/gitea.sh repos plugins`) - [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):取得 domain 名單(`tools/gitea.sh repos plugins`)
+9 -3
View File
@@ -1,6 +1,12 @@
{ {
"name": "jsc-cli", "name": "jsc-cli",
"version": "0.0.6", "version": "0.2.3",
"description": "CLI 偵測、模型能力標籤與技能庫批次部署", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills/" "skills": "./skills/",
"jsc": {
"requires": {
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8"
}
}
} }
+3
View File
@@ -18,10 +18,13 @@
| --- | --- | | --- | --- |
| claude-fable-5 | reasoning-max, reasoning-high, coding, long-context, vision | | claude-fable-5 | reasoning-max, reasoning-high, coding, long-context, vision |
| claude-opus-5 | reasoning-max, reasoning-high, coding, long-context, vision | | claude-opus-5 | reasoning-max, reasoning-high, coding, long-context, vision |
| claude-opus-4.5 | reasoning-max, reasoning-high, coding, long-context, vision |
| claude-sonnet-5 | reasoning-high, coding, fast, long-context, vision | | claude-sonnet-5 | reasoning-high, coding, fast, long-context, vision |
| claude-sonnet-4.5 | reasoning-high, coding, long-context, vision |
| claude-haiku-4-5 | coding, fast, cheap, long-context, vision | | claude-haiku-4-5 | coding, fast, cheap, long-context, vision |
| gpt-5.x / o 系列(codex 預設) | reasoning-max, reasoning-high, coding, long-context, vision | | gpt-5.x / o 系列(codex 預設) | reasoning-max, reasoning-high, coding, long-context, vision |
| gpt-5.x-mini / codex-mini | coding, fast, cheap | | gpt-5.x-mini / codex-mini | coding, fast, cheap |
| gpt-5.4-mini | coding, fast, cheap |
| gemini-3-pro | reasoning-max, reasoning-high, coding, long-context, vision | | gemini-3-pro | reasoning-max, reasoning-high, coding, long-context, vision |
| gemini-3-flash | coding, fast, cheap, long-context, vision | | gemini-3-flash | coding, fast, cheap, long-context, vision |
| copilot 內建(gpt/claude 選項) | 依所選底層模型比照上表 | | copilot 內建(gpt/claude 選項) | 依所選底層模型比照上表 |
+37
View File
@@ -0,0 +1,37 @@
---
name: delegate
description: Delegate a single bounded task to another installed AI agent CLI as a subagent, with an explicit target CLI, required capability tags, or a forced model. Use when another CLI should do the work and return structured results; not for model inventory, plugin deployment, or tasks that must stay in the current agent.
---
# delegate — hand a task to another CLI
Use this skill when the work should move to another installed AI agent CLI instead of staying in the current agent.
## Steps
1. Split the request into one bounded goal. One goal means one subagent.
2. Detect the installed AI agent CLIs. Use only a target CLI that is actually present.
3. Choose the execution model.
- If the user names a CLI, keep only that CLI.
- If the user gives capability tags, keep only models that satisfy all tags.
- If the user forces a model, use that exact model and fail if it does not satisfy the tags.
4. Build the subagent prompt.
- Include the goal, the acceptance criteria, the target CLI, the model choice, and the minimum needed context.
- Include `/jsc-shared:spec-output`.
- State the write scope clearly. If no write scope is granted, say the subagent is read-only.
5. Spawn one subagent for the goal. Do not mix unrelated goals in the same subagent.
6. Verify the result before you report it.
- Check the exit status.
- Check that the return value is structured.
- Check that the result covers the acceptance criteria.
- Check that any writes stayed inside the allowed scope.
7. Report the verified result.
- State the CLI, the model, the capability tags, and the outcome.
- Separate success, failure, and anything that still needs user input.
## Do not use this skill
- Do not use it for model inventory.
- Do not use it for plugin deployment.
- Do not use it for tasks that must stay inside the current agent.
- Do not use it when the task is not bounded enough to hand off cleanly.
+16 -20
View File
@@ -1,32 +1,28 @@
--- ---
name: deploy name: deploy
description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs via detect-clis.sh, report every plugin's local-versus-published version first and recommend update when any one of them is behind, then ask the user for the mode via decision tree, then run each CLI's native plugin commands with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. Use for rollout or removal of the jsc plugins; not for a single skill. description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs via detect-clis.sh, report every plugin's local-versus-published version first and recommend update when any one of them is behind, then ask the user for the mode via decision tree, then run each CLI's native plugin commands with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. After install or update, write this machine's update and remove guides via write-guides.sh and demand a session restart. Use for rollout or removal of the jsc plugins; not for a single skill.
--- ---
# deploy — batch install, update, or uninstall the skill set # deploy — batch install, update, or uninstall the skill set
## Steps ## Steps
1. Run `tools/detect-clis.sh` to find the installed CLIs and their executable paths. 1. Run `tools/detect-clis.sh` to find the installed CLIs and their executable paths. Done when the TSV lists at least one CLI with an executable path.
2. **Check versions before asking anything**, so the recommendation is based on fact rather than a guess: 2. **Check versions before asking anything**, so the recommendation is based on fact rather than a guess:
1. Run `jsc-hooks/hooks/version-guard.sh report`. It prints one line per installed jsc plugin — `{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}` — and a final `behind<TAB>{落後個數}`. 1. Run `jsc-hooks/hooks/version-guard.sh report`. It prints one line per installed jsc plugin — `{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}` — and a final `behind<TAB>{落後個數}`.
2. Show that table to the user as-is. It is the evidence behind the recommendation, so never summarise it away. 2. **No local plugin registry → report this CLI as unverifiable, never as up to date.** Two forms of the same fact: a report carrying no `{domain}` row at all before the `behind` line, or the script's explicit no-registry line (`noregistry<TAB>{路徑}`). Both mean the version check could not run for this CLI, so `behind<TAB>0` here proves nothing. State that plainly and base no recommendation on it.
3. **`behind` ≥ 1 → mark `update` as the recommended option**, and name every domain that is behind together with its local and remote version. One domain behind is enough; do not wait for a majority. 3. Show that table to the user as-is. It is the evidence behind the recommendation, so never summarise it away.
4. `behind` = 0 → recommend nothing; present the three options neutrally. 4. **`behind` ≥ 1 → mark `update` as the recommended option**, and name every domain that is behind together with its local and remote version. One domain behind is enough; do not wait for a majority.
5. `查詢失敗` on any domain → say so explicitly. An unverified domain is not the same as an up-to-date one, and must not be counted as either. 5. `behind` = 0 **with at least one domain row** → recommend nothing; present the three options neutrally.
3. Ask the user for the mode per the `jsc-ask:ask` rules: `install` / `update` / `uninstall`. Every option states its impact scope: which CLIs it touches and which configs it writes. 6. `查詢失敗` on any domain → say so explicitly. An unverified domain is not the same as an up-to-date one, and must not be counted as either.
Done when the report is shown and either every domain row carries one of the four status literals `落後` `最新` `超前` `查詢失敗`, or the CLI is reported as having no local registry and therefore unverifiable.
3. Ask the user for the mode per the `jsc-ask:ask` rules: `install` / `update` / `uninstall`. Every option states its impact scope: which CLIs it touches and which configs it writes. Done when the user has named exactly one of `install`, `update` or `uninstall`.
4. Get the domain list (**never hardcode it**; this skill follows automatically when domains are added or removed): read `plugins[].name` from the unified marketplace via 4. Get the domain list (**never hardcode it**; this skill follows automatically when domains are added or removed): read `plugins[].name` from the unified marketplace via
`jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`. `jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`.
The marketplace is unified as `jsc` (`{MKT}` = `https://gitea.jsc.idv.tw/plugins/meta.git`); the install token is `jsc-{domain}@jsc`. The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. Each `plugins[].name` already carries the `jsc-` prefix (e.g. `jsc-ask`) — pass it to `tools/deploy.sh` as-is, prefixed or not; the script normalizes it. Done when the domain list comes from that response and holds at least one name.
5. Run the matching commands for each CLI (add the marketplace once per CLI, then install per domain). This step **MUST run as a sub agent** (one sub agent per CLI): 5. Run `tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent** (one sub agent per CLI). The script prints `cmd` and `exit` lines for every command, one `requires` line before each domain update, and one `result` line at the end; `-n` prints the commands without running them. On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. A missing or too-old required jsc plugin prints a `skip` line and leaves that domain untouched. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content. Done when every detected CLI has reported an exit status for every command it ran, and every skipped domain has a dependency reason or local-tree reason.
6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. The hook installer must refresh `$JSC_HOME/current/jsc-hooks` and must run `tools/wire-cli.sh smoke {cli}` for every detected CLI. Treat any `No such file` in those smoke results as a failed update and report it; do not let the deploy finish as successful when a rewritten hook path cannot execute. Done when hooks-install reports purge, wiring, smoke and scan results for each detected CLI, and every smoke result is either `status=ok` or explicitly reported as the update failure.
| CLI | install | update | uninstall | 7. After install or update, run `tools/write-guides.sh {mode} {domain}...` **once for the whole machine**, after every CLI in step 5 has finished. It rewrites `$JSC_HOME/update-guide.md` and `$JSC_HOME/remove-guide.md` from the live detection result, so the later update and removal runs have the real commands for this machine. Skip it for `uninstall`: the guides describe an installed skill set. Done when the script printed a `wrote` line for both files.
| --- | --- | --- | --- | 8. Report the result and any failure reason for every CLI × mode, plus every `skip` line and every CLI that could not be version-checked in step 2. Done when every detected CLI appears in the report with its `result` status.
| claude | `claude plugin marketplace add {MKT}`, then per domain `claude plugin install jsc-{domain}@jsc` | `claude plugin marketplace update jsc`, then per domain `claude plugin update jsc-{domain}@jsc` | per domain `claude plugin uninstall jsc-{domain}@jsc`, finally `claude plugin marketplace remove jsc` | 9. For install or update, close the report with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. Name the two guide paths from step 7 in the same closing block, so the operator knows where this machine's update and removal commands now live. Done when the restart instruction is printed and both guide paths are named.
| codex | `codex plugin marketplace add {MKT}`, then per domain `codex plugin add jsc-{domain}@jsc` | `codex plugin marketplace upgrade jsc` | per domain `codex plugin remove jsc-{domain}@jsc`, finally `codex plugin marketplace remove jsc` |
| copilot | `copilot plugin marketplace add {MKT}`, then per domain `copilot plugin install jsc-{domain}@jsc` | `copilot plugin marketplace update jsc`, then per domain `copilot plugin update jsc-{domain}@jsc` | per domain `copilot plugin uninstall jsc-{domain}@jsc`, finally `copilot plugin marketplace remove jsc` |
| antigravity | per domain `git clone https://gitea.jsc.idv.tw/plugins/{domain}.git ~/plugins/{domain}`, then `agy plugin install ~/plugins/{domain}` (agy cannot install from a gitea URL) | `git -C ~/plugins/{domain} pull`, then `agy plugin uninstall jsc-{domain}` and install again | `agy plugin uninstall jsc-{domain}` |
| kiro | same plugin commands as copilot (executable `kiro-cli`); if unsupported, copy each repo's `skills/` into the kiro skills directory | pull again, then copy again | delete the matching skills directories |
6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks.
7. Report the result and any failure reason for every CLI × mode.
+90
View File
@@ -0,0 +1,90 @@
---
name: doctor
description: Health-check the execution environment in one pass and record the result, changing nothing. Five checks - plugin versions from jsc-hooks/hooks/version-guard.sh report, hook wiring from jsc-hooks/tools/wire-cli.sh status, executable CLI tests from tools/test-clis.sh, global settings and current-directory settings from tools/scan-config.sh against tools/config-spec.tsv. Report one findings table per check, then write the whole run to wiki CHECK_{HASH} where HASH comes from {hostname}/{user}; the page keeps only the latest run. Use after installing or updating the skill set, when a skill fails on a settings, wiring or CLI runtime problem, or before handing a machine over; not for applying fixes, which is jsc-cli:setup.
---
# doctor - execution environment health check
Read-only. Every command below either reads local state, calls a read-only CLI command, or asks Gitea. None of them writes a setting. That is the contract with `jsc-cli:setup`: doctor states the facts, setup changes things.
Collection (steps 1 to 5) **MUST run as a sub agent** - one sub agent for all five, returning the raw TSV lines. Only the report and the wiki write stay in the main agent.
## 1. Skill versions
Run `jsc-hooks/hooks/version-guard.sh report`. It prints `{domain}<TAB>{local}<TAB>{remote}<TAB>{落後|最新|超前|查詢失敗}` per plugin, then `behind<TAB>{count}`.
A report with no `{domain}` row, or one carrying `noregistry<TAB>{path}`, means this CLI has no local plugin registry. Report it as `無法驗證` - never as `最新`. `behind<TAB>0` proves nothing when no domain row precedes it.
Done when every installed domain has a status literal, or the CLI is reported as unverifiable.
## 2. Hook wiring
Run `jsc-hooks/tools/wire-cli.sh status {cli}` for every CLI that `tools/detect-clis.sh` found. Use `status` and nothing else: `wire-cli.sh` without a subcommand rewires, `purge` deletes, and `smoke` executes hooks - all three break the read-only contract.
Exit codes: 0 wired, 1 degraded, 3 skipped because the CLI is not installed, 5 unwired. Each `item` line names one wiring point and whether it is present.
Only claude reaches `wired`. The other four have no pre-tool hook, so `degraded` is their healthy state. Report the degradation reason as-is and never present it as a defect to fix.
Done when every detected CLI has a status and its missing items are listed.
## 3. CLI runtime tests
Run `tools/test-clis.sh` with no CLI arguments. It calls `tools/detect-clis.sh`, then runs real read-only commands for every detected CLI.
Output:
- `test<TAB>{cli}<TAB>{test}<TAB>{command}<TAB>{exit-code}<TAB>{verdict}<TAB>{detail}`
- `summary<TAB>{ok}<TAB>{warn}<TAB>{fail}<TAB>{skipped}`
Verdicts: `ok`, `warn`, `fail`, `skipped`.
Exit codes: 0 completed, 2 usage error, 3 missing `detect-clis.sh`. Any other script exit code is itself a doctor finding.
Treat `fail` as a machine problem. Treat `warn` as degraded capability: name it in the report, but do not put it in the fix table unless the failing skill needs that feature. Treat `skipped` as no conclusion. Map the report labels to the template as `ok` -> `通過`, `warn` -> `降級`, `fail` -> `失敗`, and `skipped` -> `略過`.
Done when every detected CLI has at least a version test row and the summary line is read.
## 4. Global settings
Run `tools/scan-config.sh scan global`. It checks every `scope=global` row of `tools/config-spec.tsv` and prints `item<TAB>scope<TAB>required<TAB>actual<TAB>expect<TAB>fix<TAB>verdict`, closing with `summary<TAB>{missing}<TAB>{invalid}<TAB>{unset}<TAB>{skipped}`.
Verdicts: `ok`, `default`, `unset`, `missing`, `invalid`, `skipped`.
Map the report labels to the template as `ok` -> `通過`, `default` -> `走預設`, `unset` -> `未設定`, `missing` -> `缺漏`, `invalid` -> `設錯`, and `skipped` -> `略過`.
Add `-o` when Gitea is unreachable; the Gitea-dependent rows then come back `skipped`. Report those rows as inconclusive and never as passes.
Also run `tools/scan-config.sh orphans` - variables used in the source but absent from the spec table. They are a maintenance note for the skill set, not a fault on this machine.
Done when the summary line is read and every `missing` and `invalid` row is named.
## 5. Own settings
Run `tools/scan-config.sh scan project` from the current working directory. Same output format, `scope=project` rows only.
Say which directory was scanned in the report. A project-scope result is meaningless without it, because the answer changes with every `cd`.
When `.env` or `.envrc` exists, name the spec-table variables it overrides and state the value actually in effect. A global setting silently overridden here is the failure this check exists to catch.
Done when the scanned directory is stated and every project row has a verdict.
## 6. Report and record
Report all five tables per `templates/check-page.md`. Then build the `待修項目` table from every `missing`, `invalid`, `unwired` and CLI runtime `fail` item, plus every domain reported `落後`. Order them `missing` -> `invalid` -> `unwired` -> `runtime-fail` -> `落後`. Nothing wrong -> one row reading `無`.
Write the page through `jsc-gitea:wiki`:
- Wiki repo: `jsc-gitea/tools/gitea.sh wiki-repo CHECK`.
- Page name: `CHECK_` plus `gitea.sh hash-id "{hostname}/{user}"` - the host and the login account, not `{owner}/{repo}`. Doctor checks a machine, and it has to work in directories that are not repositories at all.
- Overwrite the whole page. This page type keeps only the latest run.
- Update `CHECK_CONTENTS` from `templates/check-contents.md` in the same pass.
`wiki-repo` exiting 3 means no wiki repo is configured for CHECK. Print the tables, skip the wiki write, and put `JSC_WIKI_REPO_CHECK` at the top of the `待修項目` table - that unset variable is itself a finding, so a failed write never fails the health check.
Done when either the wiki page URL is reported, or the skipped write is reported together with the reason.
## 7. Hand off
State the counts: required items missing, settings invalid, CLIs unwired, CLI runtime failures, domains behind. Recommend `/jsc-cli:setup` when any of those is above zero. Never fix anything here.
Done when the counts are stated and the recommendation is given or explicitly withheld.
+6 -16
View File
@@ -1,26 +1,16 @@
--- ---
name: models name: models
description: List every model usable by each installed AI CLI (claude, codex, copilot, antigravity, kiro) and attach capability tags from references/model-tags.md. Syncs the tag table to $JSC_HOME/model-tags.tsv via tools/model-tags.sh so jsc-sdlc gates can be enforced in code, states which tags each SDLC stage requires (plan and analyze need reasoning-max, implement needs coding, maintain any), and resolves each stage's preferred model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf) for switch suggestions only. Use when checking model fitness, inventorying models, or reviewing stage gating; not for switching models or editing the config files. description: List every model usable by each installed AI CLI (claude, codex, copilot, antigravity, kiro) and attach capability tags from references/model-tags.md. Syncs the tag table to $JSC_HOME/model-tags.tsv via tools/model-tags.sh, so jsc-sdlc gates are enforced in code. States each SDLC stage's required tags: plan and analyze need reasoning-max, implement needs coding, maintain any. Resolves each stage's preferred model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf), for switch suggestions only. Use when checking model fitness, inventorying models, or reviewing stage gating; not for switching models or editing the config files.
--- ---
# models — list CLI models with capability tags # models — list CLI models with capability tags
## Steps ## Steps
1. Run `jsc-cli/tools/detect-clis.sh` to get the installed CLIs. 1. Run `jsc-cli/tools/detect-clis.sh` to get the installed CLIs. Done when the TSV lists every detected CLI with its executable path.
2. For each CLI, read the available models and the model currently in use. This step **MUST run as a sub agent**: 2. Run `jsc-cli/tools/list-models.sh` to read each CLI's models and the model currently in use. It prints `cli<TAB>model<TAB>in-use` from each CLI's own config, and stays silent for a CLI whose config it cannot read. For every detected CLI it returns no rows for, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default). This step **MUST run as a sub agent**. Done when every detected CLI has a model list or is marked unreadable.
3. Attach capability tags to every model per `references/model-tags.md`. A model missing from that table is not tagged by guesswork: add it to the table from the vendor's documentation, or queue it as a `jsc-ask:ask` question. Done when every listed model carries at least one tag and every unlisted model is either added to the table or queued as a `jsc-ask:ask` question.
| CLI | Source | 4. Output a table with four columns: CLI, model, tags, currently in use. Done when the table holds one row per model from step 2.
| --- | --- |
| claude | `model` in `~/.claude/settings.json`; known families are in the claude rows of model-tags.md |
| codex | `model` in `~/.codex/config.toml` |
| copilot | model options listed in the copilot config (`~/.config/copilot/`) |
| antigravity | models listed in the agy config |
| kiro | models listed in the kiro config |
If a config is unreadable, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default).
3. Attach capability tags to every model per `references/model-tags.md`. Handle unlisted models per the closing rule of that file.
4. Output a table with four columns: CLI, model, tags, currently in use.
5. Run `tools/model-tags.sh sync` to write the tag table to `$JSC_HOME/model-tags.tsv`, and report the path. This file is what `jsc-hooks/hooks/sdlc-gate.sh` reads, so the SDLC gate stays broken until it exists. Done when the command prints the path. 5. Run `tools/model-tags.sh sync` to write the tag table to `$JSC_HOME/model-tags.tsv`, and report the path. This file is what `jsc-hooks/hooks/sdlc-gate.sh` reads, so the SDLC gate stays broken until it exists. Done when the command prints the path.
6. Append the SDLC stage requirement table (plan and analyze need `reasoning-max`; implement needs `coding`; maintain accepts any), and state that gating is done in code by `sdlc-gate.sh lock {stage}` against the transcript's actual model id — **the models listed here are never allowed to self-assess their own tags**. 6. Append the SDLC stage requirement table (plan and analyze need `reasoning-max`; implement needs `coding`; maintain accepts any), and state that gating is done in code by `sdlc-gate.sh lock {stage}` against the transcript's actual model id — **the models listed here are never allowed to self-assess their own tags**. Done when all four stages appear with their required tags.
7. Run `jsc-cli/tools/model-config.sh list` and append a 「階段偏好模型」 table right after the stage requirement table, with three columns: stage, chain, source (`project` / `global`). State below the table that the chain does **not** grant passage: it only names the model to suggest switching to when the gate blocks, and expresses preference among models that already satisfy the required tags. Done when the table shows all four stages, with `-` for unconfigured ones. 7. Run `jsc-cli/tools/model-config.sh list` and append a 「階段偏好模型」 table right after the stage requirement table, with three columns: stage, chain, source (`project` / `global`). State below the table that the chain does **not** grant passage: it only names the model to suggest switching to when the gate blocks, and expresses preference among models that already satisfy the required tags. Done when the table shows all four stages, with `-` for unconfigured ones.
+66
View File
@@ -0,0 +1,66 @@
---
name: setup
description: Fix what jsc-cli:doctor found, one confirmed item at a time. Read the 待修項目 table from wiki CHECK_{HASH}, or rebuild it with tools/scan-config.sh and jsc-hooks/tools/wire-cli.sh status when no page exists. Route each item by its fix column - auto writes it through tools/apply-config.sh, ask collects the value through the jsc-ask decision tree first, manual prints the steps for the operator. Delegate compound repairs to their owners: jsc-cli:deploy for a plugin whose version is behind, jsc-hooks:hooks-install for unwired hooks, jsc-cli:models for a missing model-tags.tsv. Re-verify every item after writing and rewrite the CHECK page; use when doctor reports something to fix, not for a read-only checkup.
---
# setup — guide or apply the fixes doctor found
This skill writes. Every write is confirmed first, backed up, and verified afterwards.
## 1. Get the work list
Read the 待修項目 table from wiki `CHECK_{HASH}` — repo from `jsc-gitea/tools/gitea.sh wiki-repo CHECK`, page name from `gitea.sh hash-id "{hostname}/{user}"`.
No page, or `wiki-repo` exits 3 → rebuild the list here: `tools/scan-config.sh scan all` for settings, `jsc-hooks/tools/wire-cli.sh status {cli}` per detected CLI for wiring, `jsc-hooks/hooks/version-guard.sh report` for versions. Rebuilding **MUST run as a sub agent**.
State which source the list came from. A stale page and a live scan can disagree, and the operator has to know which one is on screen.
Done when every item carries its scope, verdict and fix route.
## 2. Confirm each item
Ask per the `jsc-ask:ask` decision tree, one item at a time, in the table's order. Every option states its impact scope: which file gets written, which skills start working, what stays broken when skipped.
An `ask` item needs its value in the same question — the wiki repo as `{owner}/{repo}`, the Gitea host, the directory path. Never invent one.
Skipping is always an option and is recorded as skipped, not as fixed.
Done when every item is either confirmed with a value or recorded as skipped.
## 3. Apply
| Route | Action |
| --- | --- |
| `auto` on a variable | `tools/apply-config.sh set {KEY} {VALUE}` |
| `auto` on a directory | `tools/apply-config.sh mkdir {PATH}` |
| `ask` | same two commands, with the value the user just gave |
| `manual` | print the exact steps and the file to edit; the operator does it |
| domain 落後 | call `jsc-cli:deploy`, mode `update` |
| hook unwired | call `jsc-hooks:hooks-install` |
| `$JSC_HOME/model-tags.tsv` missing | call `jsc-cli:models` |
`apply-config.sh` writes into the `# jsc-config` block of every existing shell rc file, backs each one up to `$JSC_HOME/backup/config/{timestamp}/` before touching it, and rewrites the block whole. It never edits anything outside that block.
Report the `backup` path it prints. That path is the whole undo story for this run.
Done when every confirmed item has a `wrote`, `created` or delegated result.
## 4. Re-verify
Rerun the check that produced each item — `tools/scan-config.sh scan {scope}` for settings, `wire-cli.sh status {cli}` for wiring, `version-guard.sh report` for versions.
An item that still fails is reported as 未修好 with the reason. Never mark it fixed because the write succeeded: writing the variable and the variable verifying are two different facts.
A newly written rc block does not affect the running shell. Tell the operator to open a new shell or `source` the rc file, and give them the `export` line for the current session. A re-verify that reads the current environment will still show the variable unset — say so rather than reporting a false failure.
Done when every applied item has a fresh verdict from its own checker.
## 5. Record
Rewrite `CHECK_{HASH}` through `jsc-gitea:wiki` with the post-fix state, per `templates/check-page.md`, and refresh the `CHECK_CONTENTS` row. The page keeps only the latest run, so this overwrites the pre-fix picture on purpose.
No wiki repo configured → report the tables on screen and say the record was skipped.
Then state the counts: fixed, skipped, delegated, and 未修好. Recommend `/jsc-cli:doctor` for a clean re-check when anything was delegated.
Done when the page is written or the skip is reported, and the four counts are stated.
+7
View File
@@ -0,0 +1,7 @@
# 體檢目錄
> 由 `jsc-cli:doctor` 維護。每台執行環境一列;`HASH` 取 `{主機名}/{登入帳號}`,算法與其他頁面共用。
| 體檢頁 | 主機 | 帳號 | 必要項缺漏 | 設定錯誤 | CLI 實測失敗 | 最後體檢 |
| --- | --- | --- | --- | --- | --- | --- |
| [[CHECK_{HASH}]] | {hostname} | {使用者帳號} | {n} | {n} | {n} | {yyyy-MM-dd HH:mm} |
+66
View File
@@ -0,0 +1,66 @@
# 執行環境體檢 — {hostname}/{使用者帳號}
> 由 `jsc-cli:doctor` 維護。這是體檢頁 `CHECK_{HASH}`,只保留最新一次結果,重跑就整頁覆寫。
> 修復請執行 `/jsc-cli:setup`,它讀這頁的「待修項目」逐項處理。
- 體檢時間:{yyyy-MM-dd HH:mm}
- 工作目錄:{絕對路徑}
- 執行 CLI:{claude、codex、copilot、antigravity、kiro 五選一}
## 結論
| 分類 | 通過 | 待修 | 略過 |
| --- | --- | --- | --- |
| 技能版本 | {n} | {n} | {n} |
| Hook 接線 | {n} | {n} | {n} |
| CLI 實測 | {n} | {n} | {n} |
| 全域設定 | {n} | {n} | {n} |
| 自我設定 | {n} | {n} | {n} |
## 技能版本
| Domain | 本機 | 遠端 | 狀態 |
| --- | --- | --- | --- |
| {domain} | {版本} | {版本} | {落後、最新、超前、查詢失敗、無法驗證} |
## Hook 接線
| CLI | 狀態 | 缺漏項目 | 說明 |
| --- | --- | --- | --- |
| {cli} | {wired、degraded、unwired、skipped} | {項目名,逗號分隔;無則寫「無」} | {降級原因或未偵測到執行檔} |
## CLI 實測
| CLI | 測試 | 指令 | 結束碼 | 判定 | 說明 |
| --- | --- | --- | --- | --- | --- |
| {cli} | {version、help、plugin-list} | {實際命令} | {結束碼} | {通過、降級、失敗、略過} | {輸出摘要或錯誤原因} |
## 全域設定
| 項目 | 必要 | 現況 | 期望 | 修法 | 判定 |
| --- | --- | --- | --- | --- | --- |
| {變數或檔案} | {是、否} | {實際值或未設定} | {該是什麼} | {自動、詢問、手動} | {通過、走預設、未設定、缺漏、設錯、略過} |
## 自我設定
> 工作目錄:{絕對路徑}
| 項目 | 必要 | 現況 | 期望 | 修法 | 判定 |
| --- | --- | --- | --- | --- | --- |
| {變數或檔案} | {是、否} | {實際值或未設定} | {該是什麼} | {自動、詢問、手動} | {通過、走預設、未設定、缺漏、設錯、略過} |
## 待修項目
> `/jsc-cli:setup` 從這張表接手。沒有待修項目時整張表寫一列「無」。
| 順序 | 項目 | 範圍 | 判定 | 修法 | 影響 |
| --- | --- | --- | --- | --- | --- |
| {n} | {變數、檔案、hook、CLI 測試或 domain} | {全域、自我、CLI} | {缺漏、設錯、未接線、實測失敗、落後} | {自動、詢問、手動} | {不修的話哪些技能跑不動} |
## 未登錄變數
> 原始碼有用到、`config-spec.tsv` 沒登錄的變數。體檢不判定它們,只提醒維護者補登錄。
| 變數 | 出現次數 |
| --- | --- |
| {變數名} | {n} |
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env sh
# apply-config.sh — 把設定寫進 shell rc 檔或建立設定目錄(供 /jsc-cli:setup 呼叫)。
# 用法:
# apply-config.sh set {KEY} {VALUE} # 寫入或更新一個環境變數(export)
# apply-config.sh unset {KEY} # 從 jsc 段落移除一個環境變數
# apply-config.sh mkdir {PATH} # 建立目錄(值可帶 $VAR 與開頭的 ~)
# apply-config.sh show # 印出目前 jsc 段落的內容(每行 KEY<TAB>VALUE)
# apply-config.sh rcfiles # 印出這次會寫入的 rc 檔路徑
#
# 寫入位置:每個既有的 shell rc 檔(~/.bashrc、~/.zshrc、~/.config/fish/config.fish)
# 都寫一份,全部不存在時才建立 ~/.bashrc。內容一律收在標記段落之間:
# # jsc-config
# export KEY='值'
# # /jsc-config
# 段落整段重寫,重跑只取代不疊加。段落外的內容一律不動——rc 檔是使用者自己的檔案,
# jsc 只負責自己那一段。
#
# 動到任何檔案之前先原樣備份到 $JSC_HOME/backup/config/{yyyyMMdd_HHmmss}/,備份失敗就不寫入。
# fish 的語法與 POSIX shell 不同,寫進去的是 set -gx KEY 值。
#
# 結束碼: 0=成功 2=用法錯誤 4=備份或寫入失敗
set -eu
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
MARK_OPEN='# jsc-config'
MARK_SHUT='# /jsc-config'
TAB=$(printf '\t')
usage() {
echo "用法:apply-config.sh {set {KEY} {VALUE}|unset {KEY}|mkdir {PATH}|show|rcfiles}" >&2
exit 2
}
cmd="${1:-}"
case "$cmd" in set|unset|mkdir|show|rcfiles) ;; *) usage ;; esac
rc_files() {
found=""
for f in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/fish/config.fish"; do
[ -f "$f" ] && { printf '%s\n' "$f"; found=1; }
done
[ -n "$found" ] || printf '%s\n' "$HOME/.bashrc"
}
if [ "$cmd" = rcfiles ]; then rc_files; exit 0; fi
# 目前段落裡的設定,格式 KEY<TAB>VALUE。取第一個既有 rc 檔為準:寫入時每個檔案內容相同。
read_pairs() {
for f in $(rc_files); do
[ -f "$f" ] || continue
awk -v o="$MARK_OPEN" -v s="$MARK_SHUT" '
$0==o { inb=1; next }
$0==s { inb=0; next }
inb {
line=$0
sub(/^export /, "", line) # POSIX shell
sub(/^set -gx /, "", line) # fish
if (line ~ /^[A-Za-z_][A-Za-z0-9_]*=/) {
eq=index(line, "="); k=substr(line, 1, eq-1); v=substr(line, eq+1)
} else {
sp=index(line, " "); if (sp==0) next
k=substr(line, 1, sp-1); v=substr(line, sp+1)
}
gsub(/^'"'"'|'"'"'$/, "", v)
if (k != "") print k "\t" v
}
' "$f"
return 0
done
}
if [ "$cmd" = show ]; then read_pairs; exit 0; fi
if [ "$cmd" = mkdir ]; then
raw="${2:-}"; [ -n "$raw" ] || usage
case "$raw" in "~/"*) raw="$HOME/${raw#\~/}" ;; esac
path=$( set +u; eval "printf '%s' \"$raw\"" )
[ -n "$path" ] || { echo "路徑展開後是空的:${2:-}" >&2; exit 4; }
mkdir -p "$path" 2>/dev/null || { echo "無法建立目錄:$path" >&2; exit 4; }
printf 'created\t%s\n' "$path"
exit 0
fi
key="${2:-}"
[ -n "$key" ] || usage
case "$key" in
[A-Za-z_]*) ;;
*) echo "變數名不合法:$key" >&2; exit 2 ;;
esac
value="${3:-}"
if [ "$cmd" = set ] && [ -z "$value" ]; then echo "set 需要值" >&2; exit 2; fi
# 合併:既有設定 + 這次的異動,重寫整段。少了這一步,寫第二個變數會蓋掉第一個。
pairs=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 4; }
read_pairs > "$pairs" 2>/dev/null || true
merged=$(mktemp) || { rm -f "$pairs"; echo "無法建立暫存檔" >&2; exit 4; }
while IFS="$TAB" read -r k v; do
[ -n "${k:-}" ] || continue
[ "$k" = "$key" ] && continue
printf '%s\t%s\n' "$k" "$v" >> "$merged"
done < "$pairs"
rm -f "$pairs"
if [ "$cmd" = set ]; then printf '%s\t%s\n' "$key" "$value" >> "$merged"; fi
# 備份:動到的每個檔案先原樣複製一份,備份不了就整個不寫。
stamp=$(date +%Y%m%d_%H%M%S)
backup_dir="$JSC_HOME/backup/config/$stamp"
mkdir -p "$backup_dir" 2>/dev/null || { rm -f "$merged"; echo "無法建立備份目錄:$backup_dir" >&2; exit 4; }
# 依 rc 檔語法組出段落內容。fish 用 set -gx,其餘用 export。
block_for() {
_f="$1"
while IFS="$TAB" read -r k v; do
[ -n "${k:-}" ] || continue
case "$_f" in
*config.fish) printf "set -gx %s '%s'\n" "$k" "$v" ;;
*) printf "export %s='%s'\n" "$k" "$v" ;;
esac
done < "$merged"
}
rc_list=$(mktemp) || { rm -f "$merged"; echo "無法建立暫存檔" >&2; exit 4; }
rc_files > "$rc_list"
rc=0
while IFS= read -r f; do
[ -n "$f" ] || continue
if [ -f "$f" ]; then
cp -p "$f" "$backup_dir/$(basename "$f")" 2>/dev/null \
|| { echo "無法備份 $f,沒有備份就不寫入" >&2; rc=4; continue; }
else
mkdir -p "$(dirname "$f")" 2>/dev/null || { echo "無法建立 $(dirname "$f")" >&2; rc=4; continue; }
touch "$f" 2>/dev/null || { echo "無法建立 $f" >&2; rc=4; continue; }
fi
content=$(block_for "$f")
block=$(printf '%s\n%s\n%s' "$MARK_OPEN" "$content" "$MARK_SHUT")
if grep -qF "$MARK_OPEN" "$f" 2>/dev/null; then
awk -v o="$MARK_OPEN" -v s="$MARK_SHUT" -v b="$block" '
$0==o { print b; skip=1; next }
$0==s { skip=0; next }
skip { next }
{ print }
' "$f" > "$f.jsc-tmp" 2>/dev/null || { rm -f "$f.jsc-tmp"; echo "無法改寫 $f" >&2; rc=4; continue; }
mv "$f.jsc-tmp" "$f" 2>/dev/null || { rm -f "$f.jsc-tmp"; echo "無法覆寫 $f" >&2; rc=4; continue; }
else
( printf '\n%s\n' "$block" >> "$f" ) 2>/dev/null || { echo "無法寫入 $f" >&2; rc=4; continue; }
fi
# 寫完重讀驗證:說寫好了卻沒寫進去,是最難查的失敗
if [ "$cmd" = set ]; then
grep -qF "$key" "$f" 2>/dev/null || { echo "$f 寫入後讀不到 $key" >&2; rc=4; continue; }
fi
printf 'wrote\t%s\n' "$f"
done < "$rc_list"
rm -f "$rc_list" "$merged"
printf 'backup\t%s\n' "$backup_dir"
[ "$rc" = 0 ] || exit 4
printf 'note\t%s\n' "新的設定要開新的 shell 或重新 source rc 檔才生效;本輪工作階段可先手動 export"
exit 0
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env sh
# check-requires.sh — Check jsc.requires before updating one plugin.
set -u
usage() {
echo "用法:check-requires.sh {claude|codex|copilot|antigravity|kiro} {manifest}" >&2
exit 2
}
[ "$#" -eq 2 ] || usage
CLI=$1
MANIFEST=$2
case "$CLI" in
claude|codex|copilot|antigravity|kiro) ;;
*) usage ;;
esac
[ -f "$MANIFEST" ] || {
printf 'status=blocked reason=找不到 manifest:%s\n' "$MANIFEST"
exit 1
}
JSC_HOME_DIR="${JSC_HOME:-$HOME/.jsc}"
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-$JSC_HOME_DIR/plugins}"
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
python3 - "$CLI" "$MANIFEST" "$LOCAL_DIR" "$KIRO_SKILLS" <<'PY'
import glob
import json
import os
import re
import sys
cli, manifest, local_dir, kiro_skills = sys.argv[1:5]
try:
with open(manifest, encoding="utf-8") as fh:
data = json.load(fh)
except Exception as exc:
print(f"status=blocked reason=manifest 不是有效 JSON:{manifest}:{exc}")
sys.exit(1)
requires = ((data.get("jsc") or {}).get("requires") or {})
if not requires:
print("status=ok reason=沒有宣告相依版本")
sys.exit(0)
def version_tuple(value):
value = str(value or "").strip()
parts = value.split(".")
out = []
for part in parts[:3]:
match = re.match(r"^(\d+)", part)
out.append(int(match.group(1)) if match else 0)
while len(out) < 3:
out.append(0)
return tuple(out)
def current_version(plugin):
domain = plugin[4:] if plugin.startswith("jsc-") else plugin
candidates = []
patterns = []
if cli == "claude":
registry = os.path.expanduser("~/.claude/plugins/installed_plugins.json")
try:
with open(registry, encoding="utf-8") as fh:
entries = (json.load(fh).get("plugins") or {}).get(f"jsc-{domain}@jsc") or []
for entry in entries:
path = os.path.join(entry.get("installPath") or "", "plugin.json")
with open(path, encoding="utf-8") as fh:
version = json.load(fh).get("version", "")
if version:
return version, path
except Exception:
pass
patterns.append(os.path.expanduser(f"~/.claude/plugins/cache/jsc/jsc-{domain}/*/plugin.json"))
elif cli == "codex":
patterns.append(os.path.expanduser(f"~/.codex/plugins/cache/jsc/jsc-{domain}/*/plugin.json"))
elif cli == "copilot":
patterns.append(os.path.expanduser(f"~/.copilot/installed-plugins/jsc/jsc-{domain}/plugin.json"))
elif cli == "antigravity":
patterns.append(os.path.join(local_dir, domain, "plugin.json"))
patterns.append(os.path.expanduser(f"~/.antigravity/plugins/jsc-{domain}/plugin.json"))
elif cli == "kiro":
patterns.append(os.path.join(kiro_skills, f"jsc-{domain}", "plugin.json"))
for pattern in patterns:
for path in glob.glob(pattern):
try:
with open(path, encoding="utf-8") as fh:
version = json.load(fh).get("version", "")
except Exception:
continue
if version:
candidates.append((version_tuple(version), version, path))
if not candidates:
return "", ""
candidates.sort()
return candidates[-1][1], candidates[-1][2]
failures = []
for plugin, constraint in sorted(requires.items()):
required = str(constraint).strip()
minimum = required[2:].strip() if required.startswith(">=") else required
current, path = current_version(plugin)
if not current:
failures.append(f"{plugin} 需要 {required},目前未安裝")
continue
if version_tuple(current) < version_tuple(minimum):
failures.append(f"{plugin} 需要 {required},目前 {current}({path})")
if failures:
print("status=blocked reason=" + ";".join(failures))
sys.exit(1)
print("status=ok reason=相依版本符合:" + ", ".join(f"{k} {v}" for k, v in sorted(requires.items())))
sys.exit(0)
PY
+76
View File
@@ -0,0 +1,76 @@
# config-spec.tsv — jsc 技能組的設定規格表。體檢(/jsc-cli:doctor)與設定(/jsc-cli:setup)共用這一份。
#
# 這張表是「必要或選擇」的唯一判準。掃描原始碼分不出必要與選擇,也分不出哪些是執行期內部
# 變數,所以判準用手寫規格表,掃描只負責抓出漏登錄的項目(scan-config.sh orphans)。
# 新增環境變數或設定檔時,同時補一列進來,體檢才看得到它。
#
# 欄位:key<TAB>kind<TAB>scope<TAB>required<TAB>default<TAB>verify<TAB>fix<TAB>desc
# kind env=環境變數 file=檔案或目錄 internal=執行期內部變數(體檢略過,只為登錄而存在)
# scope global=整台機器 project=當前工作目錄 runtime=hook 執行當下才存在
# required yes=缺了就有技能跑不動 no=選擇性,缺了走預設或降級
# default 未設定時的實際值;沒有預設寫 -
# verify set=有值即可 dir=目錄要在 file=檔案要在 gitea-api=站台連得上
# gitea-auth=認證過得了 wiki-repo=值可解析成 {owner}/{repo} none=不驗
# fix auto=工具算得出,可直接寫入 ask=值要人給,問完才寫 manual=只能人手動處理 -=不需修
# desc 一句繁中說明,直接印給使用者看
#
GITEA_HOST env global yes - gitea-api ask Gitea 站台位址,所有 wiki 與 PR 操作的去處
GITEA_TOKEN env global yes - gitea-auth ask Gitea API token;未設定時退回 tea CLI 的登入金鑰
JSC_HOME env global no ~/.jsc dir auto hook 資料目錄,放工作階段計時、用量統計、版本快取
JSC_WIKI_REPO env global no - wiki-repo ask 未逐類設定時的共用 wiki {owner}/{repo}
JSC_WIKI_REPO_QUESTION env global no JSC_WIKI_REPO wiki-repo ask QUESTION_CONTENTS、QUESTION_{HASH} 所在存取庫
JSC_WIKI_REPO_PLAN env global no JSC_WIKI_REPO wiki-repo ask PLAN_CONTENTS、PLAN_{HASH} 所在存取庫
JSC_WIKI_REPO_ANALYZE env global no JSC_WIKI_REPO wiki-repo ask ANALYZE_CONTENTS、ANALYZE_{HASH} 所在存取庫
JSC_WIKI_REPO_DELIVER env global no JSC_WIKI_REPO wiki-repo ask DELIVER_CONTENTS、DELIVER_{HASH} 所在存取庫
JSC_WIKI_REPO_MAINTAIN env global no JSC_WIKI_REPO wiki-repo ask MAINTAIN_CONTENTS、MAINTAIN_{HASH} 所在存取庫
JSC_WIKI_REPO_REPO env global no JSC_WIKI_REPO wiki-repo ask REPO_CONTENTS、REPO_{HASH} 所在存取庫
JSC_WIKI_REPO_LOG env global no JSC_WIKI_REPO wiki-repo ask LOG_CONTENTS、LOG_{HASH} 所在存取庫
JSC_WIKI_REPO_LEARN env global no JSC_WIKI_REPO wiki-repo ask LEARN_CONTENTS、LEARN_{HASH} 所在存取庫
JSC_WIKI_REPO_ERROR env global no JSC_WIKI_REPO wiki-repo ask ERROR_CONTENTS、ERROR_{HASH} 所在存取庫
JSC_WIKI_REPO_CHECK env global no JSC_WIKI_REPO wiki-repo ask CHECK_CONTENTS、CHECK_{HASH} 所在存取庫,體檢紀錄寫在這裡
JSC_WIKI_REPO_REPORT env global no JSC_WIKI_REPO wiki-repo ask REPORT_CONTENTS、REPORT_{HASH} 所在存取庫,年月週日報表寫在這裡
JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前置檢查,離線工作時用
JSC_VERSION_TTL env global no 600 set ask 版本查詢快取秒數
JSC_RESTART_GATE env global no on set ask 設成 off 可略過部署後的重啟提示閘門,判讀在 jsc-hooks
JSC_WIKI_REPO_SKILLSET env global no JSC_WIKI_REPO wiki-repo ask SKILLSET_CONTENTS、SKILLSET_{HASH} 所在的 {owner}/{repo},技能組異動報告寫在這裡
JSC_LANG_GUARD env global no on set ask 設成 off 可關閉繁中編碼與簡體字守門,誤判時用
JSC_COMMENT_SCOPE env global no on set ask 設成 off 可關閉註解夾帶文件編號的守門,誤判時用
JSC_CHANGED_FILE internal runtime no - none - 非 Claude CLI 傳入的變更檔路徑,註解範圍與繁中編碼守門讀它
JSC_SIMPLIFIED_FILE internal runtime no - none - 自訂簡體字表路徑,ste100-lint.sh 讀它取代內建字表
JSC_PR_WATCH_INTERVAL env global no 60 set ask pr-watch.sh 輪詢 PR 狀態的間隔秒數,實作在 jsc-gitea/tools/pr-watch.sh
JSC_LOCAL_PLUGINS env global no $JSC_HOME/plugins dir auto antigravity 安裝來源的本地 plugin 目錄
JSC_PLUGINS_ROOT env global no - dir ask 技能組工作目錄根位置,jsc-meta 的工具用它找各 domain
JSC_CLAUDE_SETTINGS_DIR env global no ~/.claude dir ask claude 使用者層設定檔目錄,測試 purge 時才需覆寫
JSC_COPILOT_INSTRUCTIONS env global no ~/.config/copilot/copilot-instructions.md file ask copilot 指引檔位置,STE100 規則段落寫在這裡
JSC_ANTIGRAVITY_RULES env global no ~/.antigravity/AGENTS.md file ask antigravity 全域規則檔位置
JSC_KIRO_SKILLS env global no - dir ask kiro 技能目錄,接線與部署都會用到
JSC_GITEA_TOOLS env global no - dir ask jsc-gitea/tools 的位置,跨 domain 呼叫 gitea.sh 時用
$JSC_HOME/model-tags.tsv file global yes - file auto SDLC 階段閘門讀的能力標籤表,由 /jsc-cli:models 產生
$JSC_HOME/models.conf file global no - file ask 各 SDLC 階段的偏好模型鏈,專案 .jsc/models 可覆寫
$JSC_HOME/html-styles.conf file global no - file ask 各類頁面的 HTML 匯出版型與樣式,專案 .jsc/html-styles 可覆寫
$JSC_HOME/update-guide.md file global no - file manual 這台機器的更新指引,由 jsc-cli/tools/write-guides.sh 產生;缺了就重跑 /jsc-cli:deploy
$JSC_HOME/remove-guide.md file global no - file manual 這台機器的移除指引,由 jsc-cli/tools/write-guides.sh 產生;缺了就重跑 /jsc-cli:deploy
$JSC_HOME/restart-required.d file global no - none - 部署收尾寫下的重啟狀態檔目錄,一支 CLI 一份,執行期暫態;該 CLI 那份不存在代表這支沒有待重啟的部署,jsc-hooks 讀它提示重啟
.jsc/models file project no - file ask 本專案的 SDLC 偏好模型鏈,覆寫 $JSC_HOME/models.conf
.jsc/html-styles file project no - file ask 本專案的 HTML 匯出版型與樣式,覆寫 $JSC_HOME/html-styles.conf
.claude/settings.json file project no - file manual claude 的專案層設定;jsc hook 由 plugin 的 hooks.json 接線,這裡不該有 jsc 殘留接線
AGENTS.md file project no - file manual codex 與 antigravity 讀的專案層指引檔
.env file project no - none manual 專案層環境變數;覆寫全域設定時,體檢會標出實際生效值
.envrc file project no - none manual direnv 設定檔;覆寫全域設定時,體檢會標出實際生效值
JSC_CLI internal runtime no - none - 接線時帶入的 CLI 代號,hook 用來分辨宿主
JSC_SKILL internal runtime no - none - 目前呼叫的技能名,版本前置檢查與用量統計用
JSC_SESSION_ID internal runtime no - none - 工作階段代號,計時與 token 統計用
JSC_TOOL_NAME internal runtime no - none - 目前的工具名,PreToolUse hook 用來比對 matcher
JSC_SCRIPT_DIR internal runtime no - none - 呼叫端腳本所在目錄,由 lib.sh 算出
JSC_HOOKS_DIR internal runtime no - none - hooks 目錄位置,包裝啟動器用
JSC_MODEL internal runtime no - none - 目前模型 id,SDLC 閘門用來比對能力標籤
JSC_GITEA_OWNER internal runtime no - none - 批次同步存取庫時鎖定的 owner
JSC_DEPLOY_DRYRUN internal runtime no - none - 部署試跑旗標,只印指令不執行
JSC_WP_GATE internal runtime no - none - 工作包閘門的逃生門,實作在 jsc-sdlc/tools/wp-gate.sh
JSC_CONFIG_SPEC internal runtime no - none - 改讀別份設定規格表,測試 scan-config.sh 時用
JSC_SYNC_DRY_RUN internal runtime no - none - 同步 domain 存取庫的試跑旗標,只印不動檔案
JSC_MK_DOMAIN internal runtime no - none - sync-marketplace.sh 傳給 python 的 domain 名
JSC_MK_URL internal runtime no - none - sync-marketplace.sh 傳給 python 的存取庫網址
JSC_MK_DESC internal runtime no - none - sync-marketplace.sh 傳給 python 的 plugin 描述
JSC_MK_IN internal runtime no - none - sync-marketplace.sh 讀入的 marketplace 檔路徑
JSC_MK_OUT internal runtime no - none - sync-marketplace.sh 寫出的 marketplace 檔路徑
1 # config-spec.tsv — jsc 技能組的設定規格表。體檢(/jsc-cli:doctor)與設定(/jsc-cli:setup)共用這一份。
2 #
3 # 這張表是「必要或選擇」的唯一判準。掃描原始碼分不出必要與選擇,也分不出哪些是執行期內部
4 # 變數,所以判準用手寫規格表,掃描只負責抓出漏登錄的項目(scan-config.sh orphans)。
5 # 新增環境變數或設定檔時,同時補一列進來,體檢才看得到它。
6 #
7 # 欄位:key<TAB>kind<TAB>scope<TAB>required<TAB>default<TAB>verify<TAB>fix<TAB>desc
8 # kind env=環境變數 file=檔案或目錄 internal=執行期內部變數(體檢略過,只為登錄而存在)
9 # scope global=整台機器 project=當前工作目錄 runtime=hook 執行當下才存在
10 # required yes=缺了就有技能跑不動 no=選擇性,缺了走預設或降級
11 # default 未設定時的實際值;沒有預設寫 -
12 # verify set=有值即可 dir=目錄要在 file=檔案要在 gitea-api=站台連得上
13 # gitea-auth=認證過得了 wiki-repo=值可解析成 {owner}/{repo} none=不驗
14 # fix auto=工具算得出,可直接寫入 ask=值要人給,問完才寫 manual=只能人手動處理 -=不需修
15 # desc 一句繁中說明,直接印給使用者看
16 #
17 GITEA_HOST env global yes - gitea-api ask Gitea 站台位址,所有 wiki 與 PR 操作的去處
18 GITEA_TOKEN env global yes - gitea-auth ask Gitea API token;未設定時退回 tea CLI 的登入金鑰
19 JSC_HOME env global no ~/.jsc dir auto hook 資料目錄,放工作階段計時、用量統計、版本快取
20 JSC_WIKI_REPO env global no - wiki-repo ask 未逐類設定時的共用 wiki {owner}/{repo}
21 JSC_WIKI_REPO_QUESTION env global no JSC_WIKI_REPO wiki-repo ask QUESTION_CONTENTS、QUESTION_{HASH} 所在存取庫
22 JSC_WIKI_REPO_PLAN env global no JSC_WIKI_REPO wiki-repo ask PLAN_CONTENTS、PLAN_{HASH} 所在存取庫
23 JSC_WIKI_REPO_ANALYZE env global no JSC_WIKI_REPO wiki-repo ask ANALYZE_CONTENTS、ANALYZE_{HASH} 所在存取庫
24 JSC_WIKI_REPO_DELIVER env global no JSC_WIKI_REPO wiki-repo ask DELIVER_CONTENTS、DELIVER_{HASH} 所在存取庫
25 JSC_WIKI_REPO_MAINTAIN env global no JSC_WIKI_REPO wiki-repo ask MAINTAIN_CONTENTS、MAINTAIN_{HASH} 所在存取庫
26 JSC_WIKI_REPO_REPO env global no JSC_WIKI_REPO wiki-repo ask REPO_CONTENTS、REPO_{HASH} 所在存取庫
27 JSC_WIKI_REPO_LOG env global no JSC_WIKI_REPO wiki-repo ask LOG_CONTENTS、LOG_{HASH} 所在存取庫
28 JSC_WIKI_REPO_LEARN env global no JSC_WIKI_REPO wiki-repo ask LEARN_CONTENTS、LEARN_{HASH} 所在存取庫
29 JSC_WIKI_REPO_ERROR env global no JSC_WIKI_REPO wiki-repo ask ERROR_CONTENTS、ERROR_{HASH} 所在存取庫
30 JSC_WIKI_REPO_CHECK env global no JSC_WIKI_REPO wiki-repo ask CHECK_CONTENTS、CHECK_{HASH} 所在存取庫,體檢紀錄寫在這裡
31 JSC_WIKI_REPO_REPORT env global no JSC_WIKI_REPO wiki-repo ask REPORT_CONTENTS、REPORT_{HASH} 所在存取庫,年月週日報表寫在這裡
32 JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前置檢查,離線工作時用
33 JSC_VERSION_TTL env global no 600 set ask 版本查詢快取秒數
34 JSC_RESTART_GATE env global no on set ask 設成 off 可略過部署後的重啟提示閘門,判讀在 jsc-hooks
35 JSC_WIKI_REPO_SKILLSET env global no JSC_WIKI_REPO wiki-repo ask SKILLSET_CONTENTS、SKILLSET_{HASH} 所在的 {owner}/{repo},技能組異動報告寫在這裡
36 JSC_LANG_GUARD env global no on set ask 設成 off 可關閉繁中編碼與簡體字守門,誤判時用
37 JSC_COMMENT_SCOPE env global no on set ask 設成 off 可關閉註解夾帶文件編號的守門,誤判時用
38 JSC_CHANGED_FILE internal runtime no - none - 非 Claude CLI 傳入的變更檔路徑,註解範圍與繁中編碼守門讀它
39 JSC_SIMPLIFIED_FILE internal runtime no - none - 自訂簡體字表路徑,ste100-lint.sh 讀它取代內建字表
40 JSC_PR_WATCH_INTERVAL env global no 60 set ask pr-watch.sh 輪詢 PR 狀態的間隔秒數,實作在 jsc-gitea/tools/pr-watch.sh
41 JSC_LOCAL_PLUGINS env global no $JSC_HOME/plugins dir auto antigravity 安裝來源的本地 plugin 目錄
42 JSC_PLUGINS_ROOT env global no - dir ask 技能組工作目錄根位置,jsc-meta 的工具用它找各 domain
43 JSC_CLAUDE_SETTINGS_DIR env global no ~/.claude dir ask claude 使用者層設定檔目錄,測試 purge 時才需覆寫
44 JSC_COPILOT_INSTRUCTIONS env global no ~/.config/copilot/copilot-instructions.md file ask copilot 指引檔位置,STE100 規則段落寫在這裡
45 JSC_ANTIGRAVITY_RULES env global no ~/.antigravity/AGENTS.md file ask antigravity 全域規則檔位置
46 JSC_KIRO_SKILLS env global no - dir ask kiro 技能目錄,接線與部署都會用到
47 JSC_GITEA_TOOLS env global no - dir ask jsc-gitea/tools 的位置,跨 domain 呼叫 gitea.sh 時用
48 $JSC_HOME/model-tags.tsv file global yes - file auto SDLC 階段閘門讀的能力標籤表,由 /jsc-cli:models 產生
49 $JSC_HOME/models.conf file global no - file ask 各 SDLC 階段的偏好模型鏈,專案 .jsc/models 可覆寫
50 $JSC_HOME/html-styles.conf file global no - file ask 各類頁面的 HTML 匯出版型與樣式,專案 .jsc/html-styles 可覆寫
51 $JSC_HOME/update-guide.md file global no - file manual 這台機器的更新指引,由 jsc-cli/tools/write-guides.sh 產生;缺了就重跑 /jsc-cli:deploy
52 $JSC_HOME/remove-guide.md file global no - file manual 這台機器的移除指引,由 jsc-cli/tools/write-guides.sh 產生;缺了就重跑 /jsc-cli:deploy
53 $JSC_HOME/restart-required.d file global no - none - 部署收尾寫下的重啟狀態檔目錄,一支 CLI 一份,執行期暫態;該 CLI 那份不存在代表這支沒有待重啟的部署,jsc-hooks 讀它提示重啟
54 .jsc/models file project no - file ask 本專案的 SDLC 偏好模型鏈,覆寫 $JSC_HOME/models.conf
55 .jsc/html-styles file project no - file ask 本專案的 HTML 匯出版型與樣式,覆寫 $JSC_HOME/html-styles.conf
56 .claude/settings.json file project no - file manual claude 的專案層設定;jsc hook 由 plugin 的 hooks.json 接線,這裡不該有 jsc 殘留接線
57 AGENTS.md file project no - file manual codex 與 antigravity 讀的專案層指引檔
58 .env file project no - none manual 專案層環境變數;覆寫全域設定時,體檢會標出實際生效值
59 .envrc file project no - none manual direnv 設定檔;覆寫全域設定時,體檢會標出實際生效值
60 JSC_CLI internal runtime no - none - 接線時帶入的 CLI 代號,hook 用來分辨宿主
61 JSC_SKILL internal runtime no - none - 目前呼叫的技能名,版本前置檢查與用量統計用
62 JSC_SESSION_ID internal runtime no - none - 工作階段代號,計時與 token 統計用
63 JSC_TOOL_NAME internal runtime no - none - 目前的工具名,PreToolUse hook 用來比對 matcher
64 JSC_SCRIPT_DIR internal runtime no - none - 呼叫端腳本所在目錄,由 lib.sh 算出
65 JSC_HOOKS_DIR internal runtime no - none - hooks 目錄位置,包裝啟動器用
66 JSC_MODEL internal runtime no - none - 目前模型 id,SDLC 閘門用來比對能力標籤
67 JSC_GITEA_OWNER internal runtime no - none - 批次同步存取庫時鎖定的 owner
68 JSC_DEPLOY_DRYRUN internal runtime no - none - 部署試跑旗標,只印指令不執行
69 JSC_WP_GATE internal runtime no - none - 工作包閘門的逃生門,實作在 jsc-sdlc/tools/wp-gate.sh
70 JSC_CONFIG_SPEC internal runtime no - none - 改讀別份設定規格表,測試 scan-config.sh 時用
71 JSC_SYNC_DRY_RUN internal runtime no - none - 同步 domain 存取庫的試跑旗標,只印不動檔案
72 JSC_MK_DOMAIN internal runtime no - none - sync-marketplace.sh 傳給 python 的 domain 名
73 JSC_MK_URL internal runtime no - none - sync-marketplace.sh 傳給 python 的存取庫網址
74 JSC_MK_DESC internal runtime no - none - sync-marketplace.sh 傳給 python 的 plugin 描述
75 JSC_MK_IN internal runtime no - none - sync-marketplace.sh 讀入的 marketplace 檔路徑
76 JSC_MK_OUT internal runtime no - none - sync-marketplace.sh 寫出的 marketplace 檔路徑
+369
View File
@@ -0,0 +1,369 @@
#!/usr/bin/env sh
# deploy.sh — 對單一 CLI 執行 jsc 技能組的安裝、更新或解除安裝。
# 用法:
# deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...]
# -n 或 --dry-run(或 JSC_DEPLOY_DRYRUN=1):只印指令,不執行。
# {domain} 裸名或帶 jsc- 前綴皆可(例:ask 或 jsc-ask),腳本會自動去掉前綴再組
# jsc-{domain}@jsc;marketplace.json 的 plugins[].name 本身就帶前綴,不必事先剝掉。
# 輸出(TSV,一行一筆):
# cmd<TAB>{指令} 即將執行的指令
# exit<TAB>{結束碼}<TAB>{指令} 該指令的結束碼;dry-run 時結束碼印「-」
# skip<TAB>{domain}<TAB>{原因} 本地 clone 是開發中的樹,略過 git pull
# note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由)
# restart<TAB>{路徑} 這次寫下的重啟狀態檔
# requires<TAB>{domain}<TAB>{檢查結果} update 前的 jsc.requires 檢查
# result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail}
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、note 不算失敗,但呼叫端要據實回報。
# marketplace 指令一輪只跑一次:install 與 update 先跑,uninstall 最後跑。
# 各 CLI 的細節都收在這裡,SKILL.md 只描述何時呼叫與參數:
# antigravity 不接受 gitea URL,先 clone 到本地再從路徑安裝,更新時 pull 同一份。
# kiro 的執行檔是 kiro-cli;先探測這個版本認不認得 plugin 子指令(部分版本已經完全
# 移除,例:2.18.1),認得才逐一嘗試、失敗才退回複製,不認得就整批直接走複製,
# 不逐一撞一次「unrecognized subcommand」才退回。
# install 或 update 全數成功時,收尾轉呼叫 jsc-hooks 的 restart-gate.sh require,掛上這支 CLI
# 的重啟閘門(狀態檔一支 CLI 一份,落在 $JSC_HOME/restart-required.d/{cli})。擋人邏輯不在
# 這裡:由 jsc-hooks 讀該 CLI 那一份提示使用者重啟,逃生門 JSC_RESTART_GATE=off 也由那邊判讀。
# 更新指引與移除指引由同目錄的 write-guides.sh 產生,一輪部署跑一次,不在這支腳本裡:
# 這支腳本的職責是「對單一 CLI 部署」,指引寫的是整台機器的樣貌。
# 環境變數:
# JSC_HOME hook 資料目錄,重啟狀態檔寫在這裡(預設 ~/.jsc)
# GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw)
# JSC_GITEA_OWNER 存取庫的 owner(預設 plugins)
# JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄
# 預設 $JSC_HOME/plugins(即 ~/.jsc/plugins),刻意不用 ~/plugins:
# 那是維護者放開發 checkout 的地方,pull 下去會蓋掉未提交的工作。
# 指到開發中的樹(有未提交變更或未推送的 commit)時只印 skip,不 pull。
# JSC_KIRO_SKILLS kiro 退路用的 skills 目錄(預設 ~/.kiro/skills)
# JSC_DEPLOY_DRYRUN 設為 1 等同 -n
set -u
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
# Gitea 站台一律讀 GITEA_HOST(技能準則指定的變數),未設定才用正本站台。
HOST="${GITEA_HOST:-https://gitea.jsc.idv.tw}"
case "$HOST" in http://*|https://*) ;; *) HOST="https://$HOST" ;; esac
HOST="${HOST%/}"
OWNER="${JSC_GITEA_OWNER:-plugins}"
MKT="$HOST/$OWNER/meta.git"
REPO_BASE="$HOST/$OWNER"
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-${JSC_HOME:-$HOME/.jsc}/plugins}"
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
DRYRUN="${JSC_DEPLOY_DRYRUN:-0}"
FAILED=0
# CLI 代號 → 實際執行檔。唯一真實來源是 jsc-hooks 的 hooks/lib.sh cli_bin()。
# 這裡保留一份副本,因為這支腳本是整組技能的安裝入口:jsc-hooks 還沒裝上來時
# 也要能跑,不能 source 一個可能不存在的檔案。lib.sh 的對應表改了就同步改這裡。
cli_bin() { # $1=CLI 代號
case "$1" in
antigravity) printf 'agy' ;;
kiro) printf 'kiro-cli' ;;
*) printf '%s' "$1" ;;
esac
}
usage() {
echo "用法:deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...]" >&2
exit 2
}
# 找 jsc-hooks 的 restart-gate.sh。優先用穩定連結與安裝後複製目錄,再找並排工作樹,
# 最後才掃各 CLI 的 plugin 快取。找不到就回非零,由呼叫端據實回報。
restart_gate_sh() {
if [ -n "${JSC_HOOKS_DIR:-}" ] && [ -f "$JSC_HOOKS_DIR/restart-gate.sh" ]; then
printf '%s\n' "$JSC_HOOKS_DIR/restart-gate.sh"; return 0
fi
_root=$(cd "$(dirname "$0")/.." 2>/dev/null && pwd) || return 1
for _c in \
"${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks/hooks/restart-gate.sh" \
"${JSC_LOCAL_PLUGINS:-${JSC_HOME:-$HOME/.jsc}/plugins}/hooks/hooks/restart-gate.sh" \
"${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}/jsc-hooks/hooks/restart-gate.sh" \
"$_root/../hooks/hooks/restart-gate.sh" \
"$_root/../jsc-hooks/hooks/restart-gate.sh"
do
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
done
_c=$(ls -d \
"$HOME"/.claude/plugins/cache/*/jsc-hooks/*/hooks/restart-gate.sh \
"$HOME"/.codex/plugins/cache/*/jsc-hooks/*/hooks/restart-gate.sh \
"$HOME"/.kiro/skills/jsc-hooks/hooks/restart-gate.sh \
2>/dev/null | sort | tail -n1)
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
return 1
}
check_requires() { # $1=domain;0=可更新,1=略過這個 domain
[ "$MODE" = update ] || return 0
sync_local "$1"
manifest="$LOCAL_DIR/$1/plugin.json"
out=$(sh "$HERE/check-requires.sh" "$CLI" "$manifest" 2>&1)
code=$?
printf 'requires\t%s\t%s\n' "$1" "$out"
if [ "$code" -eq 0 ]; then
return 0
fi
if [ "$code" -eq 1 ]; then
printf 'skip\t%s\t%s\n' "$1" "相依版本不符,未更新:$out"
return 1
fi
FAILED=1
printf 'skip\t%s\t%s\n' "$1" "相依版本檢查失敗,未更新:$out"
return 1
}
# 掛上部署後的重啟閘門。狀態檔的路徑、格式與判讀全在 jsc-hooks 的 restart-gate.sh,
# 這裡只轉呼叫它的 require 子命令,比照 jsc-sdlc 轉呼叫 sdlc-gate.sh wp-lock 的慣例。
# 兩邊各拼一份格式就會對不上:2026-08-27 這裡曾自己寫四欄 TSV,而 hooks 那端讀的是
# key=value,狀態檔存在卻解不出欄位。格式只能有一個真實來源。
# 找不到 restart-gate.sh 時不要自己補寫一份:閘門本來就由 jsc-hooks 判讀,它不在就沒有
# 判定點,寫下去只是留一個沒人讀的檔案,還會讓下一輪誤以為閘門掛上了。
mark_restart() {
[ "$DRYRUN" = 1 ] && return 0
case "$MODE" in install|update) ;; *) return 0 ;; esac
if ! rg=$(restart_gate_sh); then
printf 'note\t%s\t%s\n' "$CLI" "找不到 jsc-hooks 的 restart-gate.sh,這次沒有掛上重啟閘門"
return 0
fi
if JSC_CLI="$CLI" sh "$rg" require "$MODE" $DOMAINS </dev/null; then
printf 'restart\t%s\n' "${JSC_HOME:-$HOME/.jsc}/restart-required.d/$CLI"
else
printf 'note\t%s\t%s\n' "$CLI" "restart-gate.sh require 失敗,這次沒有掛上重啟閘門"
fi
}
# 執行一個指令,並印出指令本身與結束碼。失敗就記進 FAILED。
run() { # $@=指令
printf 'cmd\t%s\n' "$*"
if [ "$DRYRUN" = 1 ]; then
printf 'exit\t-\t%s\n' "$*"
return 0
fi
"$@"
code=$?
printf 'exit\t%s\t%s\n' "$code" "$*"
[ "$code" -eq 0 ] || FAILED=1
return "$code"
}
# 同 run,但失敗不記進 FAILED——留給「失敗還有退路」的指令用。
run_soft() { # $@=指令
printf 'cmd\t%s\n' "$*"
if [ "$DRYRUN" = 1 ]; then
printf 'exit\t-\t%s\n' "$*"
return 0
fi
"$@"
code=$?
printf 'exit\t%s\t%s\n' "$code" "$*"
return "$code"
}
# 這份 clone 是不是「開發中的樹」。有原因就印出原因,沒有就不印。
# 判斷兩件事:有未提交變更,或有還沒推上去的 commit。兩者被 pull 蓋掉都救不回來。
local_hold() { # $1=存取庫路徑
if [ -n "$(git -C "$1" status --porcelain 2>/dev/null)" ]; then
printf '有未提交變更'
return 0
fi
up=$(git -C "$1" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null) || return 0
[ -n "$up" ] || return 0
ahead=$(git -C "$1" rev-list --count "$up..HEAD" 2>/dev/null) || return 0
[ "${ahead:-0}" -eq 0 ] || printf '有 %s 個未推送的 commit' "$ahead"
}
# 把某 domain 的存取庫抓到本地:有 .git 就 pull,沒有就 clone。
# 目標是開發中的樹時只印 skip,改用現地內容安裝,不 pull:這支腳本可以被指到任何
# 目錄,蓋掉維護者未提交或未推送的工作救不回來,安裝一份舊內容還能重跑。
sync_local() { # $1=domain
dir="$LOCAL_DIR/$1"
if [ -d "$dir/.git" ]; then
hold=$(local_hold "$dir")
if [ -n "$hold" ]; then
printf 'skip\t%s\t%s %s,未執行 git pull\n' "$1" "$dir" "$hold"
return 0
fi
run git -C "$dir" pull
else
run git clone "$REPO_BASE/$1.git" "$dir"
fi
}
# claude、copilot、kiro-cli 共用的 plugin 指令組。
marketplace_cli() { # $1=執行檔
case "$MODE" in
install)
run "$1" plugin marketplace add "$MKT"
for d in $DOMAINS; do run "$1" plugin install "jsc-$d@jsc"; done
;;
update)
run "$1" plugin marketplace update jsc
for d in $DOMAINS; do check_requires "$d" && run "$1" plugin update "jsc-$d@jsc"; done
;;
uninstall)
for d in $DOMAINS; do run "$1" plugin uninstall "jsc-$d@jsc"; done
run "$1" plugin marketplace remove jsc
;;
esac
}
# codex 沒有 plugin update 子指令,只有 add、list、marketplace、remove。
# marketplace upgrade 只重抓 marketplace 快照,而 jsc 的 marketplace.json 只列各網域的
# git URL、不含版本,所以那份檔案內容不會變,codex 一律回「already up to date」並結束碼 0。
# 已安裝外掛的版本是 plugin add 當下決定的,快取不會被連帶重抓——換句話說,只跑
# marketplace upgrade 的話,指令全部成功而版本一個都沒動,是最難察覺的那種失敗。
# 正確做法是照樣逐網域 plugin add:codex 的 add 會就地升級到快照裡的最新版。
deploy_codex() {
bin=$(cli_bin codex)
case "$MODE" in
install)
run "$bin" plugin marketplace add "$MKT"
for d in $DOMAINS; do run "$bin" plugin add "jsc-$d@jsc"; done
;;
update)
run "$bin" plugin marketplace upgrade jsc
for d in $DOMAINS; do check_requires "$d" && run "$bin" plugin add "jsc-$d@jsc"; done
;;
uninstall)
for d in $DOMAINS; do run "$bin" plugin remove "jsc-$d@jsc"; done
run "$bin" plugin marketplace remove jsc
;;
esac
}
deploy_antigravity() {
bin=$(cli_bin antigravity)
for d in $DOMAINS; do
case "$MODE" in
install)
sync_local "$d"
run "$bin" plugin install "$LOCAL_DIR/$d"
;;
update)
check_requires "$d" || continue
sync_local "$d"
run "$bin" plugin uninstall "jsc-$d"
run "$bin" plugin install "$LOCAL_DIR/$d"
;;
uninstall)
run "$bin" plugin uninstall "jsc-$d"
;;
esac
done
}
# plugin 指令不支援時的退路:從本地 clone 複製整個 plugin 的可用內容。
#
# 技能文件會直接引用同伴目錄,例如 jsc-sdlc 的 implement 要跑 jsc-sdlc/tools/wp-gate.sh
# 與 jsc-gitea/tools/pr-watch.sh,jsc-sdlc 的 analyze 要讀 references/consensus.md。
# 這些引用在 SKILL.md 裡是完成條件,不是選配。只複製 skills 目錄,kiro 會拿到一份
# 要求跑腳本、腳本卻不在機器上的技能,比不更新更糟——所以 tools、references、
# templates、hooks 與 plugin.json 一併複製。
#
# 目標路徑刻意維持 $KIRO_SKILLS/jsc-{domain}/,跨 plugin 的引用(jsc-gitea/tools/…)
# 以 $KIRO_SKILLS 為根就解析得到。
#
# 每個子目錄都用「先 mkdir,再複製 src/. 到 dst/」的寫法:目標目錄已存在時,
# cp -R src dst/ 會把來源塞進 dst/{名稱}/{名稱},第二次更新就多一層。
kiro_copy() { # $1=domain
sync_local "$1"
dest="$KIRO_SKILLS/jsc-$1"
run mkdir -p "$dest"
run cp -R "$LOCAL_DIR/$1/skills/." "$dest/"
for sub in tools references templates hooks; do
if [ -d "$LOCAL_DIR/$1/$sub" ]; then
run mkdir -p "$dest/$sub"
run cp -R "$LOCAL_DIR/$1/$sub/." "$dest/$sub/"
fi
done
# plugin.json 讓 version-guard.sh 之類的呼叫端查得到本機版本。缺了不算錯誤,
# 只是查不到版本而已,所以不進 run、也不影響整體結束碼。
[ -f "$LOCAL_DIR/$1/plugin.json" ] && run cp "$LOCAL_DIR/$1/plugin.json" "$dest/plugin.json"
return 0
}
# kiro-cli 是否認得 plugin 子指令:一次性偵測,探測本身不印 cmd/exit(不是部署動作,
# 印出來只會讓人誤以為那也是一次失敗的部署嘗試)。部分版本(例:2.18.1)完全沒有這個
# 子指令,逐一嘗試再退回複製,會先洗出一長串看似失敗、實則設計內的錯誤訊息。
kiro_has_plugin_cmd() {
"$bin" --help-all 2>/dev/null | grep -qE '^ plugin( |$)'
}
deploy_kiro() {
bin=$(cli_bin kiro)
if kiro_has_plugin_cmd; then
case "$MODE" in
install) run_soft "$bin" plugin marketplace add "$MKT" ;;
update) run_soft "$bin" plugin marketplace update jsc ;;
esac
for d in $DOMAINS; do
case "$MODE" in
install)
run_soft "$bin" plugin install "jsc-$d@jsc" || kiro_copy "$d"
;;
update)
check_requires "$d" || continue
run_soft "$bin" plugin update "jsc-$d@jsc" || kiro_copy "$d"
;;
uninstall)
run_soft "$bin" plugin uninstall "jsc-$d@jsc" || run rm -rf "$KIRO_SKILLS/jsc-$d"
;;
esac
done
return 0
fi
printf 'note\tkiro\t%s\n' "此版本 kiro-cli 沒有 plugin 子指令,改走本地複製(git pull 或 clone 後,複製 skills、tools、references、templates、hooks 與 plugin.json)"
for d in $DOMAINS; do
case "$MODE" in
install) kiro_copy "$d" ;;
update) check_requires "$d" && kiro_copy "$d" ;;
uninstall) run rm -rf "$KIRO_SKILLS/jsc-$d" ;;
esac
done
[ "$MODE" = uninstall ] && run_soft "$bin" plugin marketplace remove jsc
return 0
}
case "${1:-}" in
-n|--dry-run) DRYRUN=1; shift ;;
esac
[ $# -ge 3 ] || usage
MODE=$1
CLI=$2
shift 2
# 每個 domain 一律去掉開頭的 jsc-:往下每處都自己組 jsc-$d@jsc,收到已帶前綴的名字
# (marketplace.json 的 plugins[].name 就是這樣,例如 jsc-ask)會兜成 jsc-jsc-ask@jsc,
# 讓 claude/copilot/antigravity/kiro 全部裝不上、更新不了。這裡正規化一次,
# 呼叫端不管傳哪種格式都能正常動作,不必每個呼叫端各自記得先去前綴。
_domains=""
for _d in "$@"; do
case "$_d" in
jsc-*) _d="${_d#jsc-}" ;;
esac
_domains="$_domains $_d"
done
DOMAINS="${_domains# }"
case "$MODE" in
install|update|uninstall) ;;
*) usage ;;
esac
case "$CLI" in
claude) marketplace_cli "$(cli_bin claude)" ;;
copilot) marketplace_cli "$(cli_bin copilot)" ;;
codex) deploy_codex ;;
antigravity) deploy_antigravity ;;
kiro) deploy_kiro ;;
*) usage ;;
esac
if [ "$FAILED" -eq 0 ]; then
mark_restart
printf 'result\t%s\t%s\t%s\tok\n' "$CLI" "$MODE" "$DOMAINS"
exit 0
fi
printf 'result\t%s\t%s\t%s\tfail\n' "$CLI" "$MODE" "$DOMAINS"
exit 1
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env sh
# list-models.sh — 讀各 AI CLI 的設定檔,列出模型與目前使用中的模型。
# 輸出(TSV,一行一個模型):cli<TAB>model<TAB>in-use(in-use 為 yes 或 no)
# 設定檔不存在或裡面沒寫模型,就不輸出該 CLI 的列;一律 exit 0。
# 設定檔位置只寫在這裡,SKILL.md 不再抄一份:
# claude ${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json 的 model;ANTHROPIC_MODEL 可覆寫使用中的模型
# codex ${CODEX_HOME:-~/.codex}/config.toml:頂層 model 為使用中,[profiles.*] 的 model 併入清單
# copilot ~/.copilot/settings.json,退回 ~/.config/copilot/settings.json
# antigravity ~/.antigravity/settings.json,退回 ~/.config/antigravity/settings.json
# kiro ~/.kiro/settings/cli.json,退回 ~/.config/kiro/settings.json
# 五個 CLI 都沒有「列出可用模型」的指令,所以清單只到設定檔寫出來的模型。
# 沒有列的 CLI 由 skills/models 的 sub agent 補上預設模型並標註「預設推定」。
set -u
# 取第一個存在的檔案;都不存在就不印。
first_file() { # $@=候選路徑
for f in "$@"; do
[ -f "$f" ] && { printf '%s\n' "$f"; return 0; }
done
return 0
}
# 取 JSON 檔裡某個鍵的所有字串值,一行一個,順序照檔案。
json_val() { # $1=檔案 $2=鍵名
[ -n "$1" ] || return 0
[ -f "$1" ] || return 0
grep -oE "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" "$1" 2>/dev/null \
| sed -e 's/^[^:]*:[[:space:]]*"//' -e 's/"$//'
return 0
}
# 取 TOML 檔裡所有 model 設定值,一行一個,順序照檔案。
# 只認行首的 model=,所以 model_reasoning_effort 這類鍵不會誤中。
toml_val() { # $1=檔案
[ -n "$1" ] || return 0
[ -f "$1" ] || return 0
grep -E '^[[:space:]]*model[[:space:]]*=' "$1" 2>/dev/null \
| sed -e 's/^[^=]*=[[:space:]]*//' -e 's/[[:space:]]*#.*$//' \
-e 's/^"//' -e 's/"[[:space:]]*$//' -e "s/^'//" -e "s/'[[:space:]]*$//"
return 0
}
# 印出某 CLI 的列:使用中的模型排第一,重複的只留一筆。
emit() { # $1=cli $2=使用中的模型(可空) $3=模型清單(換行分隔,可空)
printf '%s\n%s\n' "$2" "$3" | awk -v cli="$1" -v cur="$2" '
{ gsub(/^[ \t]+|[ \t]+$/, "") }
$0 == "" { next }
seen[$0]++ { next }
{ printf "%s\t%s\t%s\n", cli, $0, ($0 == cur ? "yes" : "no") }'
}
# claude
cfg=$(first_file "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json")
list=$(json_val "$cfg" model)
cur="${ANTHROPIC_MODEL:-}"
[ -n "$cur" ] || cur=$(printf '%s\n' "$list" | head -n1)
emit claude "$cur" "$list"
# codex
cfg=$(first_file "${CODEX_HOME:-$HOME/.codex}/config.toml")
list=$(toml_val "$cfg")
cur=$(printf '%s\n' "$list" | head -n1)
emit codex "$cur" "$list"
# copilot
cfg=$(first_file "$HOME/.copilot/settings.json" "$HOME/.config/copilot/settings.json")
list=$(json_val "$cfg" model)
cur=$(printf '%s\n' "$list" | head -n1)
emit copilot "$cur" "$list"
# antigravity
cfg=$(first_file "$HOME/.antigravity/settings.json" "$HOME/.config/antigravity/settings.json")
list=$(json_val "$cfg" model)
cur=$(printf '%s\n' "$list" | head -n1)
emit antigravity "$cur" "$list"
# kiro
cfg=$(first_file "$HOME/.kiro/settings/cli.json" "$HOME/.config/kiro/settings.json")
list=$(json_val "$cfg" model)
cur=$(printf '%s\n' "$list" | head -n1)
emit kiro "$cur" "$list"
exit 0
+7 -7
View File
@@ -65,13 +65,13 @@ current_cli() {
} }
# resolve 子指令:印出「目前 CLI 可用的第一個模型」單一名稱。 # resolve 子指令:印出「目前 CLI 可用的第一個模型」單一名稱。
# 簡化說明:這個 repo 目前沒有「列出某 CLI 實際安裝/可用模型」的 shell 級機制 # 簡化說明:可用性一律簡化為「直接取模型鏈的第一個模型」,不逐一檢查該模型是否真的能用。
# ——現有的 skills/models 是靠 sub agent 讀各 CLI 的設定檔(~/.claude/settings.json、 # tools/list-models.sh 已經能讀各 CLI 設定檔列出模型,但五個 CLI 都沒有
# ~/.codex/config.toml 等),屬於 LLM 才能做的判讀,沒辦法在這支 POSIX sh 腳本裡重現。 # 「列出可用模型」的指令,設定檔沒寫出來的模型仍然查不到;拿這份清單當白名單過濾,
# 因此這裡只用 current_cli() 抓到的「目前 CLI 名稱」這個既有訊號做記錄, # 會把合法但沒寫進設定檔的模型誤判成不可用,比不過濾更糟。
# 實際判斷可用性一律簡化為「直接取模型鏈的第一個模型」,不逐一檢查該模型是否真的能用。 # 因此這裡只用 current_cli() 抓到的「目前 CLI 名稱」這個既有訊號做記錄。
# 之後若要做到依 CLI 實際可用模型過濾,可以在這裡比對 current_cli 的結果與 # 之後若要依 CLI 實際可用模型過濾,等各 CLI 提供列出模型的指令,再比對
# references/model-tags.md/detect-clis.sh 的輸出,逐一嘗試鏈上模型直到找到可用的。 # list-models.sh 的輸出,逐一嘗試鏈上模型直到找到可用的。
resolve_first_usable() { # $1=階段 resolve_first_usable() { # $1=階段
chain=$(resolve "$1" | cut -f1) chain=$(resolve "$1" | cut -f1)
[ -n "$chain" ] || return 0 [ -n "$chain" ] || return 0
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env sh
# scan-config.sh — 依 config-spec.tsv 盤點 jsc 技能組的設定現況。唯讀,不寫任何設定。
# 用法:
# scan-config.sh spec [global|project|all] # 印規格表(去掉註解與 internal 列)
# scan-config.sh scan [global|project|all] # 逐項檢查現況,印 TSV
# scan-config.sh orphans # 掃原始碼,找出沒登錄進規格表的變數
# 選項:
# -o 離線模式:需要連 Gitea 的檢查一律標 skipped,不發送請求
#
# scan 的輸出(TSV,每行一項):
# item<TAB>scope<TAB>required<TAB>actual<TAB>expect<TAB>fix<TAB>verdict
# verdict = ok 設定妥當
# default 未設定,走預設值,可以正常運作
# unset 選擇性項目未設定,沒有預設值,相關功能會降級
# missing 必要項目缺了,相關技能跑不動
# invalid 有值但驗不過(目錄不在、認證失敗、格式不對)
# skipped 離線模式略過,未取得結論
# 最後固定一行 summary<TAB>{missing 數}<TAB>{invalid 數}<TAB>{unset 數}<TAB>{skipped 數}
#
# 帶 TOKEN 的項目一律只印 set 或 unset,不印值:體檢報告會寫進 wiki,憑證不能跟著上去。
#
# 結束碼: 0=掃描完成(有沒有問題都算完成,判斷交給呼叫端) 2=用法錯誤 3=找不到規格表
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
SPEC="${JSC_CONFIG_SPEC:-$HERE/config-spec.tsv}"
TAB=$(printf '\t')
OFFLINE=0
[ -f "$SPEC" ] || { echo "找不到規格表:$SPEC(可用 JSC_CONFIG_SPEC 指定)" >&2; exit 3; }
usage() {
echo "用法:scan-config.sh [-o] {spec|scan|orphans} [global|project|all]" >&2
exit 2
}
case "${1:-}" in
-o) OFFLINE=1; shift ;;
esac
cmd="${1:-}"
scope_want="${2:-all}"
case "$cmd" in spec|scan|orphans) ;; *) usage ;; esac
case "$scope_want" in global|project|all) ;; *) usage ;; esac
# 找出 jsc-gitea 的 tools/gitea.sh。所有 Gitea 操作一律經由它(技能準則),不自行拼 API 呼叫。
# 找不到就回傳 1,呼叫端把需要連線的檢查標成 skipped,不讓整份體檢失敗。
gitea_sh() {
if [ -n "${JSC_GITEA_TOOLS:-}" ] && [ -f "$JSC_GITEA_TOOLS/gitea.sh" ]; then
printf '%s\n' "$JSC_GITEA_TOOLS/gitea.sh"; return 0
fi
_root="${CLAUDE_PLUGIN_ROOT:-$HERE/..}"
for _c in "$_root/../gitea/tools/gitea.sh" "$_root/../jsc-gitea/tools/gitea.sh"; do
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
done
_c=$(ls -d "$_root"/../../jsc-gitea/*/tools/gitea.sh \
"$_root"/../../gitea/*/tools/gitea.sh \
"$HOME"/.claude/plugins/cache/*/jsc-gitea/*/tools/gitea.sh 2>/dev/null \
| sort | tail -n1)
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
_c=$(command -v gitea.sh 2>/dev/null || true)
[ -n "$_c" ] && { printf '%s\n' "$_c"; return 0; }
return 1
}
# 規格表的資料列(去註解、去空行)
spec_rows() { grep -v '^#' "$SPEC" | grep -v '^[[:space:]]*$'; }
# 這一列要不要納入本次掃描。$1=kind $2=scope
# internal 列只為登錄而存在(讓 orphans 認得出它們不是漏網之魚),永遠不進體檢報告。
row_wanted() {
[ "$1" != internal ] || return 1
[ "$scope_want" = all ] || [ "$2" = "$scope_want" ]
}
if [ "$cmd" = spec ]; then
spec_rows | while IFS="$TAB" read -r key kind scope required def verify fix desc; do
row_wanted "$kind" "$scope" || continue
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$key" "$scope" "$required" "$def" "$verify" "$fix" "$desc"
done
exit 0
fi
if [ "$cmd" = orphans ]; then
# 規格表沒有的變數 = 有人加了設定卻忘了登錄。體檢照樣印出來,維護者才補得上。
root="${JSC_PLUGINS_ROOT:-$(CDPATH= cd -- "$HERE/../.." && pwd)}"
[ -d "$root" ] || { echo "找不到 plugins 根目錄:$root(可用 JSC_PLUGINS_ROOT 指定)" >&2; exit 3; }
known=$(spec_rows | cut -f1 | sed 's/^\$//' | sort -u)
# 前面加詞邊界,否則帶別種前綴的變數(例如 PERSONA_ 開頭那批)會被從中間切出一段誤報。
# 這行註解本身也不寫出完整變數字面:掃描連自己的原始碼一起掃,寫了就會掃到自己。
grep -rhoE '\b(JSC|GITEA)_[A-Z0-9_]+' \
--include='*.sh' --include='*.md' --include='*.json' \
--exclude-dir=.git --exclude-dir=.jsc-monorepo-archive "$root" 2>/dev/null \
| sort | uniq -c | sort -rn \
| while read -r count name; do
# 原始碼寫的是樣板字面(JSC_WIKI_REPO_{TYPE}),抓出來會多一條尾巴底線;
# 去掉再比對,否則每次體檢都會多報一個不存在的變數。
name=${name%_}
printf '%s\n' "$known" | grep -qx "$name" && continue
case "$name" in JSC_WIKI_REPO_TYPE) continue ;; esac
printf 'orphan\t%s\t%s\n' "$name" "$count"
done
exit 0
fi
# --- scan ---
n_missing=0; n_invalid=0; n_unset=0; n_skipped=0
GITEA=$(gitea_sh 2>/dev/null || true)
# 連線類檢查的共用前置:離線、或找不到 gitea.sh,都直接標 skipped。
online_ready() {
[ "$OFFLINE" = 0 ] || return 1
[ -n "$GITEA" ] || return 1
}
# 值展開:規格表的檔案類 key 會帶 $JSC_HOME 這種變數,照字面找檔案永遠找不到。
# 開頭的 ~ 要先換成 $HOME:雙引號裡的 ~ 不展開,留著會讓 ~/.jsc 這種預設值一律驗不過。
# set +u 是必要的:預設值裡的 $JSC_HOME 常常正是「還沒設定」的那一個,
# 展開它在 set -u 底下會直接中斷整份掃描,體檢就停在半路。
expand() {
_e="$1"
case "$_e" in "~/"*) _e="$HOME/${_e#\~/}" ;; esac
# JSC_HOME 常常正是還沒設定的那一個,展開別列的 $JSC_HOME/... 卻要它有值;
# 只在這個子 shell 套用 JSC_HOME 規格列自己的預設值,不外流到主流程,
# 免得 JSC_HOME 那一列自己的檢查被連帶改成「已設定」。
( set +u; : "${JSC_HOME:=$HOME/.jsc}"; eval "printf '%s' \"$_e\"" ) 2>/dev/null
}
emit() { # item scope required actual expect fix verdict
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$6" "$7"
case "$7" in
missing) n_missing=$((n_missing + 1)) ;;
invalid) n_invalid=$((n_invalid + 1)) ;;
unset) n_unset=$((n_unset + 1)) ;;
skipped) n_skipped=$((n_skipped + 1)) ;;
esac
}
# 對一個已取得的值跑 verify。印出 verdict(ok/invalid/skipped)。$1=verify $2=值
run_verify() {
case "$1" in
set|none) echo ok ;;
dir) [ -d "$2" ] && echo ok || echo invalid ;;
file) [ -f "$2" ] && echo ok || echo invalid ;;
gitea-api)
online_ready || { echo skipped; return; }
GITEA_HOST="$2" sh "$GITEA" api GET /version >/dev/null 2>&1 && echo ok || echo invalid ;;
gitea-auth)
online_ready || { echo skipped; return; }
sh "$GITEA" owners >/dev/null 2>&1 && echo ok || echo invalid ;;
wiki-repo)
case "$2" in
*/*)
online_ready || { echo ok; return; }
sh "$GITEA" default-branch "$2" >/dev/null 2>&1 && echo ok || echo invalid ;;
*) echo invalid ;;
esac ;;
*) echo ok ;;
esac
}
# 迴圈不可以放在管線右邊:那會變成子 shell,計數加不回來,summary 永遠是 0。
rows=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 3; }
spec_rows > "$rows"
while IFS="$TAB" read -r key kind scope required def verify fix desc; do
[ -n "${key:-}" ] || continue
row_wanted "$kind" "$scope" || continue
if [ "$kind" = env ]; then
eval "val=\${$key:-}"
secret=0
case "$key" in *TOKEN*) secret=1 ;; esac
if [ -n "$val" ]; then
verdict=$(run_verify "$verify" "$val")
if [ "$secret" = 1 ]; then actual=set; else actual="$val"; fi
emit "$key" "$scope" "$required" "$actual" "$desc" "$fix" "$verdict"
continue
fi
# 未設定:有預設值就用預設值再驗一次,驗得過才算走得下去。
if [ "$def" != "-" ]; then
# 預設值寫成另一個變數名(JSC_WIKI_REPO_LOG 退回 JSC_WIKI_REPO)時,要跟去看那一個。
# 退路自己也空著卻回報「走預設值」,會讓體檢說得過去、實際上功能整個不能用。
case "$def" in
[A-Z]*)
if printf '%s' "$def" | grep -qx '[A-Z][A-Z0-9_]*'; then
eval "fallback=\${$def:-}"
if [ -z "$fallback" ]; then
emit "$key" "$scope" "$required" "未設定(退路 $def 也未設定)" "$desc" "$fix" unset
continue
fi
emit "$key" "$scope" "$required" "未設定(退回 $def=$fallback)" "$desc" "$fix" default
continue
fi ;;
esac
dval=$(expand "$def")
case "$verify" in
# 預設路徑還沒建立,算「尚未啟用」而不是「設錯了」:invalid 專指有值卻驗不過。
dir|file) if [ "$(run_verify "$verify" "$dval")" = ok ]; then verdict=default; else verdict=unset; fi ;;
*) verdict=default ;;
esac
emit "$key" "$scope" "$required" "未設定(預設 $def)" "$desc" "$fix" "$verdict"
continue
fi
[ "$required" = yes ] && verdict=missing || verdict=unset
emit "$key" "$scope" "$required" 未設定 "$desc" "$fix" "$verdict"
continue
fi
# kind=file:規格表的 key 本身就是路徑
path=$(expand "$key" 2>/dev/null || printf '%s' "$key")
if [ -e "$path" ]; then
emit "$key" "$scope" "$required" "$path" "$desc" "$fix" ok
elif [ "$required" = yes ]; then
emit "$key" "$scope" "$required" 不存在 "$desc" "$fix" missing
else
emit "$key" "$scope" "$required" 不存在 "$desc" "$fix" unset
fi
done < "$rows"
rm -f "$rows"
printf 'summary\t%s\t%s\t%s\t%s\n' "$n_missing" "$n_invalid" "$n_unset" "$n_skipped"
exit 0
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env sh
# test-clis.sh — 實際呼叫已安裝的 AI CLI,找出靜態設定看不出的環境問題。
# 用法:
# test-clis.sh [cli...]
# 輸出(TSV):
# test<TAB>{cli}<TAB>{test}<TAB>{指令}<TAB>{結束碼}<TAB>{判定}<TAB>{說明}
# summary<TAB>{ok}<TAB>{warn}<TAB>{fail}<TAB>{skipped}
# 判定:
# ok 實際命令成功
# warn CLI 可用,但該功能在這支 CLI 或這個版本不是必要功能
# fail 命令失敗、逾時,或必要功能不存在
# skipped 未安裝或無法執行該項測試
# 結束碼:0=測試完成;2=用法錯誤;3=找不到 detect-clis.sh
set -u
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
DETECT="$HERE/detect-clis.sh"
TIMEOUT_SECONDS="${JSC_CLI_TEST_TIMEOUT:-10}"
ok_count=0
warn_count=0
fail_count=0
skipped_count=0
[ -f "$DETECT" ] || { echo "找不到 detect-clis.sh:$DETECT" >&2; exit 3; }
usage() {
echo "用法:test-clis.sh [claude|codex|copilot|antigravity|kiro ...]" >&2
exit 2
}
cli_bin() {
case "$1" in
antigravity) printf 'agy' ;;
kiro) printf 'kiro-cli' ;;
claude|codex|copilot) printf '%s' "$1" ;;
*) return 1 ;;
esac
}
emit() { # cli test command code verdict detail
printf 'test\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$6"
case "$5" in
ok) ok_count=$((ok_count + 1)) ;;
warn) warn_count=$((warn_count + 1)) ;;
fail) fail_count=$((fail_count + 1)) ;;
skipped) skipped_count=$((skipped_count + 1)) ;;
esac
}
run_capture() { # $@=command
out_file=$(mktemp) || return 125
err_file=$(mktemp) || { rm -f "$out_file"; return 125; }
if command -v timeout >/dev/null 2>&1; then
timeout "$TIMEOUT_SECONDS" "$@" >"$out_file" 2>"$err_file"
else
"$@" >"$out_file" 2>"$err_file"
fi
code=$?
text=$(cat "$out_file" "$err_file" 2>/dev/null | tr '\r\n\t' ' ' | sed 's/[[:space:]][[:space:]]*/ /g; s/^ //; s/ $//; s/.*TOKEN[^ ]*/[secret]/g' | cut -c1-180)
rm -f "$out_file" "$err_file"
RUN_CODE=$code
RUN_TEXT=${text:-無輸出}
return 0
}
run_required() { # cli test command...
cli=$1
test_name=$2
shift 2
cmd_text="$*"
run_capture "$@"
code=$RUN_CODE
detail=$RUN_TEXT
if [ "$code" -eq 0 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" ok "$detail"
elif [ "$code" -eq 124 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" fail "命令逾時(${TIMEOUT_SECONDS} 秒)"
else
emit "$cli" "$test_name" "$cmd_text" "$code" fail "$detail"
fi
}
run_optional() { # cli test command...
cli=$1
test_name=$2
shift 2
cmd_text="$*"
run_capture "$@"
code=$RUN_CODE
detail=$RUN_TEXT
if [ "$code" -eq 0 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" ok "$detail"
elif [ "$code" -eq 124 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" fail "命令逾時(${TIMEOUT_SECONDS} 秒)"
else
emit "$cli" "$test_name" "$cmd_text" "$code" warn "$detail"
fi
}
has_cli() {
name=$1
"$DETECT" | awk -F '\t' -v name="$name" '$1 == name { found = 1 } END { exit found ? 0 : 1 }'
}
test_one() {
cli=$1
bin=$(cli_bin "$cli") || usage
path=$(command -v "$bin" 2>/dev/null || true)
if [ -z "$path" ]; then
emit "$cli" executable "$bin" "-" skipped "未偵測到執行檔"
return 0
fi
case "$cli" in
antigravity)
run_required "$cli" version "$path" --version
run_optional "$cli" help "$path" --help
run_required "$cli" plugin-list "$path" plugin list
;;
kiro)
run_required "$cli" version "$path" --version
run_optional "$cli" help "$path" --help-all
run_optional "$cli" plugin-list "$path" plugin list
;;
*)
run_required "$cli" version "$path" --version
run_optional "$cli" help "$path" --help
run_required "$cli" plugin-list "$path" plugin list
;;
esac
}
if [ "$#" -eq 0 ]; then
set -- $("$DETECT" | cut -f1)
fi
if [ "$#" -eq 0 ]; then
emit all executable "-" "-" skipped "未偵測到任何支援的 CLI"
printf 'summary\t%s\t%s\t%s\t%s\n' "$ok_count" "$warn_count" "$fail_count" "$skipped_count"
exit 0
fi
for cli in "$@"; do
case "$cli" in
claude|codex|copilot|antigravity|kiro)
if has_cli "$cli"; then
test_one "$cli"
else
bin=$(cli_bin "$cli") || usage
emit "$cli" executable "$bin" "-" skipped "未偵測到執行檔"
fi
;;
*) usage ;;
esac
done
printf 'summary\t%s\t%s\t%s\t%s\n' "$ok_count" "$warn_count" "$fail_count" "$skipped_count"
exit 0
+264
View File
@@ -0,0 +1,264 @@
#!/usr/bin/env sh
# write-guides.sh — 產生這台機器專屬的更新指引與移除指引。
# 用法:
# write-guides.sh [-n] {install|update} {domain} [domain...]
# -n 或 --dry-run:只印會寫到哪兩個檔案,不寫入。
# {domain} 裸名或帶 jsc- 前綴皆可,交給 deploy.sh 正規化。
# 產出(兩個檔案,整份覆寫):
# $JSC_HOME/update-guide.md 下次更新照著做的指引
# $JSC_HOME/remove-guide.md 要整組移除時照著做的指引
# 輸出(TSV,一行一筆):
# cli<TAB>{cli}<TAB>{path}<TAB>{version} 這台機器上偵測到的 CLI
# plan<TAB>{path} dry-run 時會寫入的檔案
# wrote<TAB>{path} 實際寫入的檔案
# note<TAB>{原因} 非致命的說明(例:一個 CLI 都沒偵測到)
# 結束碼:兩份都寫成 0;參數錯誤 2;目錄或檔案寫不進去 4。
#
# 指引內容一律依實際偵測結果生成,不寫死:
# CLI 清單來自 detect-clis.sh;每支 CLI 的實際指令來自 deploy.sh 的 dry-run(-n)輸出。
# 指令字面因此與 deploy.sh 真正會跑的完全一致——各 CLI 的差異只有 deploy.sh 一個真實來源,
# 這裡再抄一份就會有兩套指令,改了一邊忘了另一邊,指引就開始騙人。
# kiro 走不走本地複製退路,也是讀 deploy.sh 的 note 行判斷,不自己再探測一次。
# 環境變數:
# JSC_HOME 指引寫入的目錄(預設 ~/.jsc)
# GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw)
# JSC_GITEA_OWNER 存取庫的 owner(預設 plugins)
# JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄(預設 $JSC_HOME/plugins)
# JSC_KIRO_SKILLS kiro 退路用的 skills 目錄(預設 ~/.kiro/skills)
set -u
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
DETECT="$HERE/detect-clis.sh"
DEPLOY="$HERE/deploy.sh"
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
HOST="${GITEA_HOST:-https://gitea.jsc.idv.tw}"
case "$HOST" in http://*|https://*) ;; *) HOST="https://$HOST" ;; esac
HOST="${HOST%/}"
OWNER="${JSC_GITEA_OWNER:-plugins}"
MKT="$HOST/$OWNER/meta.git"
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-$JSC_HOME/plugins}"
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
UPDATE_GUIDE="$JSC_HOME/update-guide.md"
REMOVE_GUIDE="$JSC_HOME/remove-guide.md"
DRYRUN=0
TAB=$(printf '\t')
usage() {
echo "用法:write-guides.sh [-n] {install|update} {domain} [domain...]" >&2
exit 2
}
case "${1:-}" in
-n|--dry-run) DRYRUN=1; shift ;;
esac
[ $# -ge 2 ] || usage
MODE=$1
shift
case "$MODE" in install|update) ;; *) usage ;; esac
[ -x "$DETECT" ] || [ -f "$DETECT" ] || { echo "找不到 detect-clis.sh:$DETECT" >&2; exit 4; }
[ -f "$DEPLOY" ] || { echo "找不到 deploy.sh:$DEPLOY" >&2; exit 4; }
DOMAINS=""
for _d in "$@"; do
case "$_d" in jsc-*) _d="${_d#jsc-}" ;; esac
DOMAINS="$DOMAINS $_d"
done
DOMAINS="${DOMAINS# }"
if [ "$DRYRUN" = 1 ]; then
printf 'plan\t%s\n' "$UPDATE_GUIDE"
printf 'plan\t%s\n' "$REMOVE_GUIDE"
fi
# 偵測到的 CLI,一行一筆 name<TAB>path<TAB>version。
CLIS=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 4; }
NOTES=$(mktemp) || { rm -f "$CLIS"; echo "無法建立暫存檔" >&2; exit 4; }
CMDS=$(mktemp) || { rm -f "$CLIS" "$NOTES"; echo "無法建立暫存檔" >&2; exit 4; }
cleanup() { rm -f "$CLIS" "$NOTES" "$CMDS"; }
sh "$DETECT" > "$CLIS" 2>/dev/null || true
while IFS="$TAB" read -r name path ver; do
[ -n "${name:-}" ] || continue
printf 'cli\t%s\t%s\t%s\n' "$name" "$path" "$ver"
done < "$CLIS"
[ -s "$CLIS" ] || printf 'note\t%s\n' "一個 CLI 都沒偵測到,指引只會寫下 marketplace 與 domain 清單"
# 跑一次 deploy.sh 的 dry-run,把 cmd 行與 note 行分開收好。
# $1=mode $2=cli;cmd 行寫進 $CMDS,note 行寫進 $NOTES,兩個檔案每次都重寫。
harvest() {
: > "$CMDS"
: > "$NOTES"
JSC_DEPLOY_DRYRUN=1 sh "$DEPLOY" -n "$1" "$2" $DOMAINS 2>/dev/null \
| while IFS="$TAB" read -r kind a b; do
case "$kind" in
cmd) printf '%s\n' "$a" >> "$CMDS" ;;
note) printf '%s\n' "$b" >> "$NOTES" ;;
esac
done
}
# 這支 CLI 的 plugin 安裝方式,一句話。kiro 讀 $NOTES 判斷走不走複製退路。
# codex 分模式講:它的 update 有陷阱(沒有 plugin update),移除段落講這件事只會讓人分心。
cli_method() { # $1=cli $2=mode
case "$1" in
claude|copilot)
printf '原生 plugin 指令,來源是統一 marketplace `jsc`' ;;
codex)
if [ "$2" = uninstall ]; then
printf '原生 plugin 指令;移除用 `plugin remove`,不是 `plugin uninstall`'
else
printf '原生 plugin 指令;沒有 `plugin update` 子指令,更新一律重跑 `plugin add` 就地升級'
fi ;;
antigravity)
printf '不接受 Gitea URL,先把存取庫 clone 到 `%s`,再從本地路徑安裝' "$LOCAL_DIR" ;;
kiro)
if [ -s "$NOTES" ]; then
printf '這個版本沒有 `plugin` 子指令,整批走本地複製退路(複製到 `%s`)' "$KIRO_SKILLS"
else
printf '原生 plugin 指令;單一 domain 失敗才退回本地複製(複製到 `%s`)' "$KIRO_SKILLS"
fi ;;
*)
printf '原生 plugin 指令' ;;
esac
}
# 指令區塊:一支 CLI 一個 sh 圍欄,內容就是 deploy.sh 會跑的每一行。
emit_cmds() { # $1=mode $2=cli
harvest "$1" "$2"
printf '### %s\n\n' "$2"
printf '%s\n\n' "$(cli_method "$2" "$1")"
if [ -s "$NOTES" ]; then
while IFS= read -r n; do
[ -n "$n" ] || continue
printf '> %s\n\n' "$n"
done < "$NOTES"
fi
printf '```sh\n'
if [ -s "$CMDS" ]; then
cat "$CMDS"
else
printf '# deploy.sh 這一輪沒有要對 %s 執行的指令\n' "$2"
fi
printf '```\n\n'
}
domain_list() {
_out=""
for d in $DOMAINS; do
[ -z "$_out" ] && _out="\`jsc-$d\`" || _out="$_out、\`jsc-$d\`"
done
printf '%s' "$_out"
}
domain_count() {
set -- $DOMAINS
printf '%s' "$#"
}
STAMP=$(date '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date)
# 主機與帳號分兩欄寫。併成一欄要用斜線隔開,那是 STE100 的並列斜線違規。
HOSTNAME_NOW=$(hostname 2>/dev/null || echo 未知主機)
USER_NOW=$(id -un 2>/dev/null || echo 未知帳號)
# 兩份指引共用的抬頭:說清楚這份檔案是誰產生的、什麼時候產生的、依據是什麼。
header() { # $1=標題 $2=一句用途
printf '# %s\n\n' "$1"
printf '%s\n\n' "$2"
printf '本檔由 `jsc-cli/tools/write-guides.sh` 產生,內容依產生當下這台機器的偵測結果生成。重跑 `/jsc-cli:deploy` 會整份覆寫。\n\n'
printf '| 項目 | 內容 |\n| --- | --- |\n'
printf '| 產生時間 | %s |\n' "$STAMP"
printf '| 產生時機 | `/jsc-cli:deploy` 的 %s 收尾 |\n' "$MODE"
printf '| 主機 | %s |\n' "$HOSTNAME_NOW"
printf '| 登入帳號 | %s |\n' "$USER_NOW"
printf '| Marketplace | `jsc`(`%s`) |\n' "$MKT"
printf '| 安裝 token | `jsc-{domain}@jsc` |\n'
printf '| Domain 數量 | %s |\n' "$(domain_count)"
printf '| Domain 清單 | %s |\n' "$(domain_list)"
printf '| 資料目錄 | `%s` |\n\n' "$JSC_HOME"
}
cli_table() {
printf '## 這台機器偵測到的 CLI\n\n'
if [ -s "$CLIS" ]; then
printf '| CLI | 執行檔 | 版本 | plugin 安裝方式 |\n| --- | --- | --- | --- |\n'
while IFS="$TAB" read -r name path ver; do
[ -n "${name:-}" ] || continue
harvest "$MODE" "$name"
printf '| %s | `%s` | %s | %s |\n' "$name" "$path" "${ver:-未知}" "$(cli_method "$name" "$MODE")"
done < "$CLIS"
printf '\n'
else
printf '一個 CLI 都沒偵測到。裝好任一支 CLI 之後重跑 `/jsc-cli:deploy`,這份指引才會有指令可循。\n\n'
fi
}
# 各 CLI 的指令段落。$1=mode
cli_sections() {
[ -s "$CLIS" ] || return 0
while IFS="$TAB" read -r name path ver; do
[ -n "${name:-}" ] || continue
emit_cmds "$1" "$name"
done < "$CLIS"
}
write_update_guide() {
header 'jsc 技能組更新指引' '整組 jsc plugins 要更新時,照這份指引走。首選一律是 `/jsc-cli:deploy` 選 `update`;下面的指令是同一件事的手動版本,CLI 壞掉或不想開工作階段時用。'
cli_table
printf '## 更新指令\n\n'
printf '每支 CLI 各自一組,指令與 `jsc-cli/tools/deploy.sh update {cli} {domain}...` 實際會跑的完全相同。\n\n'
cli_sections update
printf '## 更新完要做的事\n\n'
printf '1. 重新接線 hooks:`/jsc-hooks:hooks-install`。plugin 換版後接線檔會過期。\n'
printf '2. 關閉目前的工作階段並重新啟動。新的技能內容要重開工作階段才載入得到。\n'
printf '3. 體檢一次:`/jsc-cli:doctor`。確認版本、接線與設定都對得上。\n\n'
printf '`%s/{cli}` 這份檔案存在,就代表那一支 CLI 有一輪部署還沒重啟。狀態檔一支 CLI 一份,重啟只清自己那份,別支的閘門不受影響。重啟提示由 `jsc-hooks` 判讀,逃生門是 `JSC_RESTART_GATE=off`。\n\n' "$JSC_HOME/restart-required.d"
printf '## 移除\n\n'
printf '要整組移除看 `%s`。\n' "$REMOVE_GUIDE"
}
write_remove_guide() {
header 'jsc 技能組移除指引' '整組 jsc plugins 要移除時,照這份指引走。首選一律是 `/jsc-cli:deploy` 選 `uninstall`;下面的指令是同一件事的手動版本。'
cli_table
printf '## 移除指令\n\n'
printf '每支 CLI 各自一組,指令與 `jsc-cli/tools/deploy.sh uninstall {cli} {domain}...` 實際會跑的完全相同。marketplace 指令一輪只跑一次,排在各 domain 之後。\n\n'
cli_sections uninstall
printf '## 移除後的殘留物\n\n'
printf 'plugin 指令只管 plugin 自己。下面這些是 jsc 另外寫在機器上的東西,要不要清掉自己決定:\n\n'
printf '| 路徑 | 內容 | 清掉的影響 |\n| --- | --- | --- |\n'
printf '| `%s` | hook 資料目錄:工作階段計時、用量統計、版本快取、模型標籤表、備份 |' "$JSC_HOME"
printf ' 用量統計與設定備份一起消失,救不回來 |\n'
printf '| `%s` | antigravity 與 kiro 用的本地 clone | 下次安裝要重新 clone |\n' "$LOCAL_DIR"
printf '| `%s` | kiro 複製退路的技能目錄 | kiro 的 jsc 技能完全消失 |\n' "$KIRO_SKILLS"
printf '| shell rc 檔的 `# jsc-config` 段落 | `/jsc-cli:setup` 寫入的環境變數 | jsc 相關環境變數失效,段落外的內容不受影響 |\n\n'
printf 'rc 檔那一段用 `jsc-cli/tools/apply-config.sh unset {KEY}` 逐項移除,或直接手動刪掉 `# jsc-config` 與 `# /jsc-config` 之間的內容。動手前先備份。\n\n'
printf '## 移除完要做的事\n\n'
printf '1. 關閉目前的工作階段並重新啟動。已載入的技能要重開工作階段才會消失。\n'
printf '2. 確認殘留:`/jsc-cli:doctor` 若還列得出 jsc 版本,代表某支 CLI 還留著 plugin。\n'
}
[ "$DRYRUN" = 1 ] && { cleanup; exit 0; }
mkdir -p "$JSC_HOME" 2>/dev/null || { cleanup; echo "無法建立目錄:$JSC_HOME" >&2; exit 4; }
rc=0
tmp="$JSC_HOME/.update-guide.md.tmp"
if write_update_guide > "$tmp" 2>/dev/null && mv "$tmp" "$UPDATE_GUIDE" 2>/dev/null; then
printf 'wrote\t%s\n' "$UPDATE_GUIDE"
else
rm -f "$tmp"
echo "無法寫入 $UPDATE_GUIDE" >&2
rc=4
fi
tmp="$JSC_HOME/.remove-guide.md.tmp"
if write_remove_guide > "$tmp" 2>/dev/null && mv "$tmp" "$REMOVE_GUIDE" 2>/dev/null; then
printf 'wrote\t%s\n' "$REMOVE_GUIDE"
else
rm -f "$tmp"
echo "無法寫入 $REMOVE_GUIDE" >&2
rc=4
fi
cleanup
exit "$rc"