Files
cli/references/behaviors.md
T
jiantw83andClaude Opus 5 cd832460ee fix(deploy): 本機複本一個 domain 一把鎖,重啟閘門不等整輪判定
實測踩到兩件事,同一輪、同一個根因。

那一份本機複本一台機器只有一份,而技能規定五支 CLI 平行部署——平行是對的,
它們寫的是不同的外掛目錄。但複本不是:五支都會來 pull 同一個目錄,連只需要
讀 manifest 的那幾支也會(相依檢查從那裡讀)。git 對同一個存取庫的併發寫入
沒有保護,於是同一輪裡兩支撞在一起,一支拿不到 ORIG_HEAD.lock、一支的遠端
refs 換不上去。

後果是最難查的那一種:兩支的整輪判定都變成 fail,而外掛其實全部裝好了——
報告說失敗、實際成功,而真正的原因跟部署無關。

改成一個 domain 一把 mkdir 鎖:那是檔案系統這一層唯一原子的建立動作。等不到
就印一行 warn 改用磁碟上的內容,別人正在拉同一份,硬等下去只是排隊。上一輪
中途死掉留下的鎖用年紀判,門檻放寬到等待秒數的四倍。

複本已經在磁碟上而 pull 拉不動的那一種,也改成只印 warn、不判整輪失敗:
內容在,只是可能比遠端舊。但一定要印出來——安靜地裝一份舊內容,是這一組
工具最怕的那種失效。clone 不存在那一種照舊算失敗,磁碟上根本沒東西可裝。

第二件事更嚴重。原本的寫法是「整輪判定成功才掛重啟閘門」,於是那一輪的
fail 把閘門一起跳過了:外掛換了一半,而唯一沒有被告知要重啟的,剛好就是
正在跑那份剛被換掉的程式碼的那一支 CLI。一道只在成功時才生效的提醒,在最
需要它的那一次不會出現。改成 install 與 update 一律先掛,再判 result。

順帶補一支安全截斷:訊息截長度用的是 cut -c,那數的是位元組,多位元組字
剛好被切成兩半會留一個替代字元,而亂碼不影響結束碼、沒有人會來報。

乾跑那一路一步都不動,連鎖都不取——取鎖是建目錄,那已經是寫入。原本改完
之後乾跑會真的去 pull,這一版修回來了。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 12:35:27 +08:00

54 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# jsc-cli 技能行為清單
本頁記錄 jsc-cli 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
## delegate
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 一件邊界清楚的工作要交給另一支已安裝的 AI agent CLI 執行時用。盤點模型(models)、部署技能組(deploy)、必須留在目前 agent 手上的工作都不用;給不出通過或失敗判準的目標也不用 |
| 關鍵步驟 | 寫下一句目標與可判定通過或失敗的驗收條件、同時起跑 detect-clis.sh 與 list-models.sh 取得 CLI 清單與模型清單、先選定目標 CLI 再用 model-tags.sh 的 gate 或 model 驗證模型能力標籤、組出帶目標、驗收條件、目標 CLI、模型代號、最小脈絡、寫入範圍與 TSV 輸出合約的提示、開一個 sub agent 執行、查驗回傳的結束碼與 result、summary、criterion、wrote 各行、把成功、失敗、需要使用者補充分三段回報,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:delegate 寫下這一輪的結果。收尾那一筆走每一條出口,連停在能力閘門那一條也要寫;status 五選一,sub agent 回 0 且每條驗收條件都 pass、wrote 也都在範圍內是 ok,能力鎖擋下所有候選模型、或一支 CLI 都沒偵測到、整輪沒開 sub agent 是 blocked,sub agent 非零退出、回傳格式不符、有驗收條件 fail、或 wrote 越界是 failed,sub agent 回 needs-input、工作只做一半等使用者補資料是 degraded,目標給不出通過或失敗判準、或請求裡包了兩個以上獨立目標而主動停手是 aborted。detail 只放目標 CLI 與模型代號,不放 sub agent 的輸出。腳本不在這台機器就安靜跳過,回報失敗不得改變這支技能的結果 |
| 外部呼叫 | jsc-cli/tools/detect-clis.sh、jsc-cli/tools/list-models.sh、jsc-cli/tools/model-tags.sh(gate 與 model 兩個子命令)、jsc-hooks/tools/report-status.sh skill-end、一個代跑工作的 sub agent |
| 完成條件 | 目標 CLI 與模型各只有一個,且模型通過能力要求;sub agent 回傳的每一條驗收條件都有判定;每個 wrote 路徑都落在寫入範圍內;報告分三段列出結果。中途停下時,停下的原因要寫在報告裡。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
| 可驗證跡象 | 技能自己只寫這一筆事件。$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:delegate,status 與 exit 就是這一輪的結果。另一項可檢查的是 sub agent 依寫入範圍實際改動的檔案,逐條列在回傳的 wrote 行上,照那些路徑去看即可比對;寫入範圍為空的唯讀委派沒有其他寫入跡象,只有回報內容與那一筆事件 |
## deploy
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 整組 jsc 技能要在這台機器的每一支已安裝 CLI 上安裝、更新或解除安裝時用。只處理單一技能不用;只想知道版本落後與否,看 doctor 就夠 |
| 關鍵步驟 | 先跑一次 `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,解到那一層就停,不再往下解成帶版本號的快取路徑——那種路徑放不進允許清單,版本號寫成萬用字元也對不上;同一步再跑 `[ -d "{剛印出來的路徑}" ]` 確認目錄存在,`JSC_HOME` 沒設時它印的是 `/current`、結束碼 0,非空又是絕對路徑,只看那兩項擋不下來。整輪只解這一次,之後每一次跨外掛腳本呼叫都填成那個字面絕對路徑,不留 `$JSC_HOME` 也不留波浪號;技能自己那四支 `tools/*.sh` 一律不走 `current`,即使那裡擺著 `jsc-cli` 那一條也一樣——第四步會為每個部署到的 domain 刷新連結,所以跑過一輪之後那一條通常在,但第一次建起它的正是這一輪,沒部署過的機器、或上一輪 link 回 fail 的機器,那一條不在或還停在舊版,四支腳本會解到不存在的路徑或自己的舊副本;根目錄取自 CLI 載入這支技能時講明的外掛基底目錄,原樣當字面絕對路徑用,一個指令都不跑,四支一律寫成 `{外掛根目錄}/tools/{腳本}`;那個基底目錄帶版本號,四次呼叫都會跳權限詢問,這一支有人在現場(第三步要問模式)所以按得掉,無人值守的技能不得照抄,叫用文字沒講明基底目錄就回報外掛根目錄不明並停手,不猜前綴——權限層靜態比對路徑,帶未展開變數的呼叫一律要人核准,無人看管的輪次會停在第一支腳本,補權限規則也擋不住,因為規則字面同樣是靜態比對;解不出來就停手回報。接著同時取得三項事實(detect-clis.sh 的 CLI 清單、version-guard.sh 的 report 版本表與 recommend 結論、marketplace.json 的 domain 清單)、把版本表原樣秀出並定出建議、依 jsc-ask 決策樹問出模式(呼叫端已帶模式就沿用並標明來源)、每支 CLI 各開一個 sub agent 同時跑 tools/deploy.sh {mode} {cli} {domain}...、讀 deploy.sh 收尾印出的 link 行確認 `current` 連結農場刷新到位(install 與 update 把每一條指到這次裝的版本目錄,uninstall 清掉指向已消失的那幾條;一台機器只有一組農場而五支 CLI 各有副本,所以只有基準 CLI 那一輪會動它,基準是 claude、codex、copilot、kiro 之中這台機器第一支裝得到的,其餘四支各印一行 link 標明基準是誰,平行覆寫會讓最後指到哪一份變成隨機;狀態 ok 要把指向抄進收尾報告,removed 不必處置,skip 帶 domain 是那個路徑上擺著非符號連結的東西、腳本刻意不覆寫而要人工處理,fail 是版本目錄取不到或連結建不起來、部署本身仍然成立但那條文件路徑可能還停在舊版,整輪判 degraded,連結失敗一律不記成部署失敗,否則會連重啟閘門與 result 行一起跳過,把一台裝好的機器講成失敗)、安裝或更新後把 CLI 清單交給 jsc-hooks:hooks-install、整台機器跑一次 tools/write-guides.sh、彙整每支 CLI 的結果並要求重新啟動工作階段,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:deploy 寫下這一輪的結果。收尾那一筆接在彙整回報之後,不取代它;走每一條出口,連停在偵測不到 CLI 那一條也要寫。status 五選一,每支 CLI 都回 0、hooks-install 判定乾淨、兩份指引都寫成、基準 CLI 的 link 行全是 ok 或 removed 是 ok,偵測不到任何 CLI、整輪沒下過任何外掛命令是 blocked,marketplace 讀不到或每支 CLI 都失敗、冒煙結果出現 No such file 是 failed,部分 CLI 成功部分失敗、有 domain 被 skip、write-guides.sh 回 4 讓機器沒有最新指引、或有 link 行回 fail 與帶 domain 的 skip 是 degraded,使用者沒選模式或在第一支 CLI 開跑前停手是 aborted。detail 只放模式與各項筆數,cmd 與 exit 行留在回報裡 |
| 外部呼叫 | `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,加上同一步的 `[ -d ]` 確認,是整輪唯一容許帶變數的兩個指令;跨外掛腳本一律用它組成的字面絕對路徑呼叫:{current 目錄}/jsc-hooks/hooks/version-guard.sh 的 report 與 recommend、{current 目錄}/jsc-gitea/tools/gitea.sh 讀 plugins/meta 的 marketplace.json、{current 目錄}/jsc-hooks/tools/report-status.sh skill-end。技能自己那四支腳本走外掛根目錄組成的字面絕對路徑:{外掛根目錄}/tools/detect-clis.sh、{外掛根目錄}/tools/deploy.sh、{外掛根目錄}/tools/write-guides.sh、{外掛根目錄}/tools/check-requires.sh(由 deploy.sh 在每個 domain 更新前轉呼叫)。第四步那段內文提到的 `jsc-hooks/hooks/version-guard.sh` 是在講擋人發生在哪一層,不是這支技能要下的呼叫,整輪只有第一步那一次真的跑它。另有 jsc-hooks/hooks/restart-gate.sh require(由 deploy.sh 收尾轉呼叫,install 與 update 一律呼叫,整輪判定成 fail 也照呼叫——外掛已經換了一部分,那時候更需要重啟;原本只在成功時呼叫,實測有一輪被一條與部署無關的 git pull 判成 fail,那一支 CLI 就沒有被掛上閘門)、jsc-ask:ask、jsc-hooks:hooks-install。`current` 連結農場的刷新不是另一支腳本,是 deploy.sh 自己收尾做的,技能只讀它印的 link 行,不自己下 ln 或 rm |
| 完成條件 | `current` 那個目錄在第一步就解出一條存在的絕對路徑(用 `[ -d ]` 查過,而且沒有再往下解成帶版本號的快取路徑),技能自己那四支腳本也有一條字面絕對的外掛根目錄可用,後續每一支腳本都用這兩條之一組成的字面絕對路徑呼叫;每一支偵測到的 CLI 都回報結束碼與 result 行,每個 skip、warn、compat、link 行都照實列出;基準 CLI 那一輪每個 domain 都有一條 link 行,狀態 ok 的把指向抄進收尾報告,狀態 fail 與帶 domain 的 skip 都點名外掛與原因並整輪判 degraded,非基準的那幾支各有一行標明基準是誰;安裝或更新還要拿到 hooks-install 對每支 CLI 的總結,兩份指引都印出 wrote,收尾印出重啟指示、兩份指引路徑,以及每個外掛的連結現在指到哪一個版本目錄。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
| 可驗證跡象 | 各 CLI 的外掛目錄多出或少掉 jsc-{domain}:claude 與 codex 在各自的 plugin 快取、copilot 在 installed-plugins、antigravity 與 kiro 走 $JSC_LOCAL_PLUGINS 的本地 clone 與 $JSC_KIRO_SKILLS 的複製;那一份本地 clone 一台機器只有一份而五支 CLI 平行跑,所以 deploy.sh 對每個 domain 取一把 mkdir 鎖再 pull,拿不到鎖或 pull 回非零時印一行 warn 並改用磁碟上現有的內容、不判整輪失敗(clone 不存在那一種照舊算失敗,磁碟上沒東西可裝)。$JSC_HOME/plugins/.lock-{domain} 在一輪跑完之後一個都不該留著。$JSC_HOME/current/ 底下每個外掛一條符號連結,安裝或更新之後拿 `readlink` 讀出來的指向,就是基準 CLI 這一輪裝到的那個帶版本號目錄,跟 link 行第五欄逐字相同,也跟 version-guard.sh report 那張表上該外掛的本機版本對得起來——這一項驗得到連結指向正確:連結上的版本號與剛裝上的版本號不一致,就是刷新沒做到;解除安裝之後,指向已消失的那幾條不再留在目錄裡,`readlink -e` 對每一條都解得出存在的目錄,沒有一條是斷的;那個目錄底下也不會出現實體目錄,非符號連結的項目腳本刻意不動並留下一行 skip。$JSC_HOME/restart-required.d/{cli} 出現這次的重啟狀態檔;$JSC_HOME/update-guide.md 與 $JSC_HOME/remove-guide.md 被重寫;各 CLI 的 hook 設定檔由 hooks-install 改寫;$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:deploy,status 與 exit 就是這一輪的結果。回報與逐行紀錄裡出現的腳本路徑全是字面絕對路徑,找不到 `$JSC_HOME`、`$` 開頭或波浪號開頭的呼叫,唯一的例外是開頭那一次 `readlink -f "$JSC_HOME/current"` 與同一步的 `[ -d ]` 確認;跨外掛那幾支的路徑中段是 `current`,不是 `cache/jsc/{外掛}/{版本}`,技能自己那四支則一律是外掛根目錄接 `tools/`,沒有一支寫成裸的相對路徑 |
## doctor
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 裝完或更新完技能組、技能因設定或接線問題失敗、機器要交接前用。要動手修不用這支,那是 jsc-cli:setup |
| 關鍵步驟 | 先跑一次 `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,解到那一層就停,不再往下解成帶版本號的快取路徑——那種路徑放不進允許清單,版本號寫成萬用字元也對不上;同一步再跑 `[ -d "{剛印出來的路徑}" ]` 確認目錄存在,`JSC_HOME` 是有預設值的選擇性項目,沒設時它印的是 `/current`、結束碼 0,非空又是絕對路徑,只看那兩項擋不下來。這一支跟 deploy 不同,解不出來不停手:`JSC_HOME` 直接記成全域設定與待修項目上的一項發現,用得到跨外掛腳本的檢查記成無法驗證,其餘照跑到完——唯讀體檢中途停掉,操作者什麼都拿不到,而路徑解不開的機器正是最需要體檢的那一台。整輪只解這一次,之後每一次跨外掛腳本呼叫都填成那個字面絕對路徑,不留 `$JSC_HOME` 也不留波浪號,也不留裸的相對路徑——相對路徑會對著操作者的專案目錄解,那裡從來不是外掛根目錄,每個這種呼叫點都是前綴被猜出來的地方。技能自己的 `tools/` 與 `templates/` 一律不走 `current`,即使那裡擺著 `jsc-cli` 那一條也一樣:這一支正是機器可疑時才跑的技能,它的觸發時機自己就寫著裝完或更新完、技能因設定或接線失敗、機器要交接,而 deploy 判 degraded 那一輪留下的正是回 fail 或被 skip、還指著舊版的連結;從那條連結拿到的 scan-config.sh 與 config-spec.tsv 是這台機器沒在跑的版本,掃出來的每一列都像真的發現,舊掃描器跑得完,一句錯誤訊息都不會有。改走外掛根目錄就沒有這個問題,連結壞掉、過期或從來沒建起來的機器上,那三支腳本照樣跑得動,`JSC_HOME` 本身也才掃得到、報得出來。根目錄取自 CLI 載入這支技能時講明的外掛基底目錄,原樣當字面絕對路徑用,一個指令都不跑,寫成 `{外掛根目錄}/tools/{腳本}` 與 `{外掛根目錄}/templates/{檔案}`;那個基底目錄帶版本號,這幾次呼叫都會跳權限詢問,這一支有操作者在現場讀五個區塊與待修項目表所以按得掉,無人值守的技能不得照抄,叫用文字沒講明基底目錄就回報外掛根目錄不明並停手,不猜前綴。接著同時開四個 sub agent 收版本、hook 接線、設定與未登錄變數,每個 sub agent 回傳原始輸出行、把四份輸出各存成檔、依 templates/check-page.md 印出五個區塊並寫明掃描的專案目錄、用 tools/build-todo.sh 把三份輸出合成待修項目表、用程式取短主機名與登入帳號(主機名切掉第一個點之後的網域)交給 hash-id 算出 HASH、用 wiki-repo CHECK 解出的存取庫透過 jsc-gitea:wiki 整頁覆寫 CHECK_{HASH}、寫完再用 wiki-url 取該頁絕對網址、把要寫進兩頁的每個連結交給 jsc-gitea/tools/link-check.sh 驗證且只有結束碼 0 才往下寫、「體檢頁」那一條的連結寫成 `[CHECK_{HASH}]({絕對網址})`、用 wiki-contents.sh upsert CHECK 1 CHECK_{HASH} 以 H2 標題也就是體檢頁頁名當鍵,把本機那一個區塊寫進 CONTENTS 存取庫的 CHECK_CONTENTS,區塊檔是 `## CHECK_{HASH}` 那一行、一個空行,再照 templates/check-contents.md 的欄位順序每欄一條 `- {欄位名}:{值}`、報出四項計數並視情況建議 /jsc-cli:setup,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:doctor 寫下這一輪的結果。這一筆是這支技能唯一的寫入動作,記的是查到什麼,不動設定、不動接線、不動版本,唯讀合約照樣成立;走每一條出口都要寫。status 五選一,四項檢查都有結論、五個區塊與待修項目表都在畫面上、兩頁都寫成是 ok,任何一項報成無法驗證、離線讓 Gitea 相關列變成 skipped、或 wiki-repo 回 3 而略過寫頁是 degraded——唯讀技能讀不到來源就是這一種,金鑰失效回 7 或其他 API 失敗回 8 讓紀錄寫不成是 failed,使用者在寫頁之前喊停是 aborted。blocked 這支用不到:沒 CLI、沒登錄檔、沒 wiki 存放庫的機器一樣查得出四項發現,報成 blocked 會把做完的一輪講成沒做事。detail 只放四項計數 |
| 外部呼叫 | `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,加上同一步的 `[ -d ]` 確認,是整輪唯一容許帶變數的兩個指令;跨外掛腳本一律用它組成的字面絕對路徑呼叫:{current 目錄}/jsc-hooks/hooks/version-guard.sh report、{current 目錄}/jsc-hooks/tools/wire-cli.sh status(一律帶 JSC_READONLY=1)、{current 目錄}/jsc-gitea/tools/gitea.sh 的 wiki-repo、hash-id 與 wiki-url、{current 目錄}/jsc-gitea/tools/link-check.sh、{current 目錄}/jsc-gitea/tools/wiki-contents.sh upsert、{current 目錄}/jsc-hooks/tools/report-status.sh skill-end。技能自己的檔案走外掛根目錄組成的字面絕對路徑:{外掛根目錄}/tools/detect-clis.sh、{外掛根目錄}/tools/scan-config.sh 的 scan all 與 orphans、{外掛根目錄}/tools/build-todo.sh,比對用的 {外掛根目錄}/tools/config-spec.tsv 與兩份版型 {外掛根目錄}/templates/check-page.md、{外掛根目錄}/templates/check-contents.md 也一樣。另有 jsc-gitea:wiki |
| 完成條件 | `current` 那個目錄在第一步就解出一條存在的絕對路徑(用 `[ -d ]` 查過,而且沒有再往下解成帶版本號的快取路徑),解不出來就記成 `JSC_HOME` 那一項發現而不是停手;技能自己的 `tools/` 與 `templates/` 也有一條字面絕對的外掛根目錄可用,後續每一支腳本、每一份版型都用這兩條之一組成的字面絕對路徑呼叫。四項檢查各有結論,或明寫無法驗證與原因;五個區塊與待修項目表都在畫面上;每個要寫進頁面的連結都經 link-check.sh 驗過,結束碼 0 才寫,1 就兩頁都不寫並列出 DEAD 那幾筆,3 把 GITEA_HOST 排進待修項目最前面,7 停下來回報金鑰問題而不判成死連結;連結一律寫成文字加絕對網址的形式,H2 標題本身不放連結;兩頁各自寫成功,或寫入略過連同結束碼一起回報,wiki-contents.sh 宣告的 0、1、2、3、4、7、8 每一碼都有分流,結束碼 1 是組不出頁面內容或寫入失敗,頁上找不到本機那一個區塊不算錯、腳本改成附加,建不建新頁的判斷留在腳本裡,技能不自己建;必要項缺漏、設定錯誤、CLI 未接線、domain 落後四項計數都講出來。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
| 可驗證跡象 | wiki 的 CHECK_{HASH} 頁(雜湊來源是 {短主機名}/{登入帳號})被整頁覆寫成這次的結果;另一個存取庫的 CHECK_CONTENTS 多出本機那一個 H2 區塊,或該區塊的缺漏數與最後體檢時間被更新;區塊標題是 `## CHECK_{HASH}`,也就是比對用的鍵,標題上沒有連結也沒有網址,「體檢頁」那一條是 `[CHECK_{HASH}]({絕對網址})` 這種文字加連結的寫法,點下去連得到體檢頁,頁面上找不到同 wiki 的雙括號連結;欄位一律是 `- {欄位名}:{值}` 的條列,頁上沒有 markdown 表格,同一台機器重跑幾次都只有這一個區塊,別台機器的區塊一個位元組都沒變;連結驗不過的那一輪,兩頁都維持上一輪的內容。$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:doctor,status 與 exit 就是這一輪的結果,那也是這支技能唯一寫得出來的檔案痕跡。機器本身的設定、接線與版本都不動:這支技能不寫任何設定。回報與逐行紀錄裡出現的腳本路徑全是字面絕對路徑,找不到 `$JSC_HOME`、`$` 開頭或波浪號開頭的呼叫,也沒有一支寫成裸的相對路徑,唯一的例外是開頭那一次 `readlink -f "$JSC_HOME/current"` 與同一步的 `[ -d ]` 確認;跨外掛那幾支的路徑中段是 `current`,不是 `cache/jsc/{外掛}/{版本}`,技能自己那三支腳本與兩份版型則一律是外掛根目錄接 `tools/` 或 `templates/` |
## models
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 要盤點各 CLI 可用模型、確認模型合不合 SDLC 階段的能力要求、或檢視階段閘門設定時用。切換模型不用,改 .jsc/models 與 models.conf 也不用 |
| 關鍵步驟 | 同時起跑三個收集器(detect-clis.sh、list-models.sh 以 sub agent 執行、model-config.sh list)、對讀不到設定的 CLI 補上標「預設推定」的預設模型、依 references/model-tags.md 為每個模型掛能力標籤、印出 CLI、模型、標籤、使用中四欄表、跑 tools/model-tags.sh sync 把標籤表寫進 $JSC_HOME/model-tags.tsv、附上 SDLC 階段需求表與階段偏好模型表,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:models 寫下這一輪的結果。收尾那一筆走每一條出口,連停在偵測不到 CLI 那一條也要寫;status 五選一,每支 CLI 都有模型清單、每個模型都掛到標籤、sync 回 0 印出路徑、兩張階段表都在是 ok,偵測不到任何 CLI、盤點那半段整個沒開始是 blocked,sync 非零而 model-tags.tsv 沒寫成、SDLC 閘門判不出來是 failed,讀不到某支 CLI 的設定而改用預設推定、有模型查不到標籤只能排進發問、或 model-config.sh 失敗讓偏好表變成未取得是 degraded,使用者在 sync 寫檔之前停手是 aborted。detail 只放 CLI 與模型筆數 |
| 外部呼叫 | jsc-cli/tools/detect-clis.sh、jsc-cli/tools/list-models.sh、jsc-cli/tools/model-config.sh list、jsc-cli/tools/model-tags.sh sync、jsc-hooks/tools/report-status.sh skill-end、references/model-tags.md;標籤表上查不到的模型改用 jsc-ask:ask 發問 |
| 完成條件 | 每支偵測到的 CLI 都有模型清單或一組預設推定;每個模型都掛到標籤,或已排進發問;sync 印出寫入路徑,失敗則連同結束碼回報;兩張階段表都列滿 plan、analyze、implement、maintain 四個階段。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
| 可驗證跡象 | $JSC_HOME/model-tags.tsv 被重寫,內容就是這次掛好的標籤表;jsc-hooks/hooks/sdlc-gate.sh 讀的正是這份檔,檔案不在,SDLC 階段閘門就判不出來。$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:models,status 與 exit 就是這一輪的結果。各 CLI 的模型設定檔不動 |
## setup
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | doctor 報出待修項目、要實際動手修這台機器時用。只想做唯讀體檢不用這支,那是 jsc-cli:doctor |
| 關鍵步驟 | 先跑一次 `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,解到那一層就停,不再往下解成帶版本號的快取路徑——那種路徑放不進允許清單,版本號寫成萬用字元也對不上;同一步再跑 `[ -d "{剛印出來的路徑}" ]` 確認目錄存在。全技能組裡最常碰到 `JSC_HOME` 沒設的就是這一支:它是有預設值的選擇性項目,在待修項目表上是一列 auto,而把它寫進去正是這支技能要做的修復,所以那種機器本來就是叫用這支技能的常態理由;沒設時那道指令印的是 `/current`、結束碼 0,非空又是絕對路徑,兩項都擋不下來。解不出來既不停這一輪,也不擋那一項修復:寫 `JSC_HOME` 要用的 apply-config.sh、scan-config.sh、build-todo.sh 全掛在外掛根目錄底下,根本不靠 `JSC_HOME`,先把那一項修好、再重解一次 `current` 就能往下走,仍然構不到的(第五步的 wiki 紀錄、轉呼叫出去的修復)記成未修好並寫明原因,不用猜的。整輪只解這一次,之後每一次跨外掛腳本呼叫都填成那個字面絕對路徑,不留 `$JSC_HOME` 也不留波浪號,也不留裸的相對路徑——相對路徑會對著操作者的專案目錄解,那裡從來不是外掛根目錄。技能自己的 `tools/` 與 `templates/` 一律不走 `current`,即使那裡擺著 `jsc-cli` 那一條也一樣,理由有兩條:一是上面那條,要修的機器往往正是 `JSC_HOME` 壞掉的機器,修復工具走連結農場,就會被它們存在的理由本身擋住;二是這一支會拿舊腳本去寫檔,第三步把每個落後的 domain 交給 jsc-cli:deploy,正因為這台機器的版本可能落後,而連結農場受同一份落後管轄,從那裡拿 apply-config.sh,等於拿這台機器剛被判定已經跟不上的工具去修它,而且結果是寫進 rc 檔,不只是印在畫面上——舊腳本會照它那一版的鍵集重寫每個 rc 檔的 `# jsc-config` 區塊,把舊的當成正確版本備份起來,第四步再用同一份舊的 show 重驗,當然對得上,於是整輪報成已修。根目錄取自 CLI 載入這支技能時講明的外掛基底目錄,原樣當字面絕對路徑用,一個指令都不跑,寫成 `{外掛根目錄}/tools/{腳本}` 與 `{外掛根目錄}/templates/{檔案}`;那個基底目錄帶版本號,這幾次呼叫都會跳權限詢問,這一支有操作者在現場(第二步逐項問到答案才寫)所以按得掉,無人值守的技能不得照抄,叫用文字沒講明基底目錄就在寫任何東西之前回報外掛根目錄不明並停手,不猜前綴。接著用程式取短主機名與登入帳號算出 HASH,從 wiki-repo CHECK 解出的存取庫讀 CHECK_{HASH} 的待修項目表,讀不到就以 sub agent 同時重跑設定、接線、版本三個檢查器再用 build-todo.sh 合併、依 jsc-ask 決策樹逐項循序確認、依 fix 欄分流(auto 與 ask 走 apply-config.sh 的 set 或 mkdir、manual 印出步驟交給操作者、domain 落後轉呼叫 jsc-cli:deploy 並附上手上的版本報告、hook 未接線轉呼叫 jsc-hooks:hooks-install、缺 model-tags.tsv 轉呼叫 jsc-cli:models)、同時重驗每個已套用項目、重寫 CHECK_{HASH}、再用 wiki-url 取它的絕對網址、把要寫進兩頁的每個連結交給 jsc-gitea/tools/link-check.sh 驗證且只有結束碼 0 才往下寫、「體檢頁」那一條的連結寫成 `[CHECK_{HASH}]({絕對網址})`、以 wiki-contents.sh upsert CHECK 1 CHECK_{HASH} 用 H2 標題也就是體檢頁頁名當鍵,更新 CONTENTS 存取庫的 CHECK_CONTENTS 上本機那一個區塊,區塊檔是 `## CHECK_{HASH}` 那一行、一個空行,再照 templates/check-contents.md 的欄位順序每欄一條 `- {欄位名}:{值}`、報出已修、略過、轉呼叫、未修好四項計數,最後呼叫 jsc-hooks/tools/report-status.sh skill-end jsc-cli:setup 寫下這一輪的結果。收尾那一筆走每一條出口,連停在讀不到待修項目那一條也要寫;status 五選一,每一項都修好也重驗過、沒有略過、兩頁都寫成是 ok,環境不允許寫入、apply-config.sh 每一項都回 4 而一個 rc 檔都沒動到是 blocked,已套用的項目重驗仍失敗、apply-config.sh 回 2 是這支技能自己下錯命令、或金鑰失效回 7 與其他 API 失敗回 8 讓紀錄改寫不成是 failed,使用者否決某一項而那一項記成略過、或轉呼叫出去的修正沒收尾是 degraded,使用者在逐項確認到一半喊停、剩下的項目沒問到是 aborted。detail 只放四項計數,不放使用者輸入的值 |
| 外部呼叫 | `readlink -f "$JSC_HOME/current"` 解出 `current` 這個目錄的絕對路徑,加上同一步的 `[ -d ]` 確認,是整輪唯一容許帶變數的兩個指令;跨外掛腳本一律用它組成的字面絕對路徑呼叫:{current 目錄}/jsc-hooks/tools/wire-cli.sh status(一律帶 JSC_READONLY=1)、{current 目錄}/jsc-hooks/hooks/version-guard.sh report、{current 目錄}/jsc-gitea/tools/gitea.sh 的 wiki-repo、hash-id 與 wiki-url、{current 目錄}/jsc-gitea/tools/link-check.sh、{current 目錄}/jsc-gitea/tools/wiki-contents.sh upsert、{current 目錄}/jsc-hooks/tools/report-status.sh skill-end。技能自己的檔案走外掛根目錄組成的字面絕對路徑:{外掛根目錄}/tools/scan-config.sh、{外掛根目錄}/tools/detect-clis.sh、{外掛根目錄}/tools/build-todo.sh、{外掛根目錄}/tools/apply-config.sh 的 set、mkdir 與 show,比對用的 {外掛根目錄}/tools/config-spec.tsv 與兩份版型 {外掛根目錄}/templates/check-page.md、{外掛根目錄}/templates/check-contents.md 也一樣。另有 jsc-ask:ask、jsc-gitea:wiki、jsc-cli:deploy、jsc-hooks:hooks-install、jsc-cli:models |
| 完成條件 | `current` 那個目錄在第一步就解出一條存在的絕對路徑(用 `[ -d ]` 查過,而且沒有再往下解成帶版本號的快取路徑),解不出來就把它記成 `JSC_HOME` 那一項待修並照修,不停手也不擋住其他項;技能自己的 `tools/` 與 `templates/` 也有一條字面絕對的外掛根目錄可用,後續每一支腳本、每一份版型都用這兩條之一組成的字面絕對路徑呼叫。每一項都有已修、略過、轉呼叫或未修好的結果;每個已套用項目都由自己那一列指定的檢查器重驗過;每個寫進去的環境變數都附上 export 那一行;每個要寫進頁面的連結都經 link-check.sh 驗過,結束碼 0 才寫,1 就兩頁都不寫並列出 DEAD 那幾筆,3 回報 GITEA_HOST 仍未修好,7 停下來回報金鑰問題而不判成死連結;連結一律寫成文字加絕對網址的形式,H2 標題本身不放連結;兩頁各自寫好或略過都有回報,wiki-contents.sh 宣告的 0、1、2、3、4、7、8 每一碼都有分流,結束碼 1 是組不出頁面內容或寫入失敗,頁上找不到本機那一個區塊不算錯、腳本改成附加,建不建新頁的判斷留在腳本裡,技能不自己建;四項計數都講出來。這一輪還要留下一筆 skill-end 事件,或是腳本不在而略過,兩者都算收好;略過不影響這支技能的結束碼 |
| 可驗證跡象 | 各 shell rc 檔的 `# jsc-config` 區塊被改寫,改寫前的備份落在 $JSC_HOME/backup/config/{時間戳}/;auto 路線建立的目錄實際出現在磁碟上;wiki CHECK_{HASH} 被改寫成修完後的狀態,另一個存取庫的 CHECK_CONTENTS 只有本機那一個 H2 區塊跟著更新,區塊標題是當鍵用的 `## CHECK_{HASH}`,標題上沒有連結也沒有網址,「體檢頁」那一條是 `[CHECK_{HASH}]({絕對網址})` 這種文字加連結的寫法、點下去連得到體檢頁,頁面上找不到同 wiki 的雙括號連結,欄位一律是 `- {欄位名}:{值}` 的條列、頁上沒有 markdown 表格;連結驗不過的那一輪,兩頁都維持上一輪的內容;轉呼叫出去的項目留下各自技能的跡象,也就是 deploy 的重啟狀態檔、hooks-install 改寫的接線設定、models 產生的 model-tags.tsv;$JSC_HOME/usage/events.jsonl 會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-cli:setup,status 與 exit 就是這一輪的結果。回報與逐行紀錄裡出現的腳本路徑全是字面絕對路徑,找不到 `$JSC_HOME`、`$` 開頭或波浪號開頭的呼叫,也沒有一支寫成裸的相對路徑,唯一的例外是開頭那一次 `readlink -f "$JSC_HOME/current"` 與同一步的 `[ -d ]` 確認;跨外掛那幾支的路徑中段是 `current`,不是 `cache/jsc/{外掛}/{版本}`,技能自己那四支腳本與兩份版型則一律是外掛根目錄接 `tools/` 或 `templates/`;備份目錄 $JSC_HOME/backup/config/{時間戳}/ 底下那幾份 rc 檔,是由外掛根目錄那一份 apply-config.sh 寫出來的,跟這台機器目前跑的 jsc-cli 版本同一份 |