Files
assist/references/behaviors.md
jiantw83andClaude Opus 5 98dd0761fa docs(assistant): 被權限擋下的指令,回報要帶完整指令列
實測踩到一輪:它報「寫入監控頁失敗——系統的核准機制擋下這個寫入指令,重試
一次仍被擋下,不是 Gitea 回傳的錯誤碼」,然後照規則中止、釋放鎖、不寫心跳。
中止的行為完全正確,寫不成頁就不該假裝跑完。

但那一輪沒說是哪一支工具、哪一條指令。事後把明顯的嫌疑一個一個排除——那一支
腳本實測在無人值守下放行、暫存檔路徑落在檔案規則的範圍內、條目也帶著寫入
確認旗標——真正被擋的那一條始終沒找到。那一輪到現在還是原因不明。

問題在於「擋下」不是工具回的錯誤:沒有結束碼、沒有 stderr、工具自己的日誌上
也沒有痕跡。指令字面是唯一的證據,也是唯一能拿去對允許清單的東西,而清單
比對的是還沒展開的指令字面。所以少了那一行,事後就只能猜。

兩處都加:寫監控頁那一步的失敗分流加一條,界線那一段再加一條通則,涵蓋整輪
任何一次被擋。要求含環境變數前綴——那算指令字面的一部分,今天量過。

三份 manifest 版號從 0.2.9 升到 0.3.1。原本寫 0.2.10,同步 manifest 那一支
把它正規化掉了——那一支不收兩位數的修訂號。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:13:31 +08:00

38 KiB
Raw Permalink Blame History

jsc-assist 技能行為清單

本頁記錄 jsc-assist 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。

assistant

項目 內容
觸發時機 要啟動助理、要停止助理、要跑一輪巡檢,或要問助理現在還在不在跑、待辦簿剩下哪幾筆時用。四個操作 start、status、patrol、stop 都走這一支。排程每一輪叫起來的也是這一支的 patrol。委派清單改過之後要讓助理的內建定期檢查項跟著重建,也走這一支的 start;只想知道差在哪、不要動待辦簿就走 status。執行環境健檢不走這支,走 jsc-cli:doctor。技能使用次數不走這支,走 jsc-log:stats
關鍵步驟 四個操作都先跑同一個前置步驟,取得工具根目錄(本頁記成 {CURRENT}),根目錄一律由外面餵進來:排程那一輪從叫用文字裡的「工具根目錄=」那一段取字面絕對路徑,一個指令都不跑;人在現場叫用時,叫用文字帶那一段就取那一段,沒帶才跑一次 readlink -f "${JSC_HOME:-$HOME/.jsc}/current" 自己解,那一次會跳一次權限詢問,人按一下就過。無人值守那一輪取不到根目錄就停下回報:說明條目是舊版 schedule.sh 裝的、沒有把根目錄寫進提示文字,叫人重跑一次 start 或 schedule.sh install patrol 把條目重寫,收尾狀態取 aborted;一律不跑 readlink、不跑 ls、不退回帶變數的路徑、不拿技能提示或上一次轉錄裡的路徑、也不猜。整次叫用只取這一次,之後每一次腳本呼叫都填那一個字面絕對路徑,不是每一次呼叫各取一次,也不另外加印路徑的工具,更不另外跑指令去驗那一個路徑。取到的是空的、不是絕對路徑、或那條路徑不是存在的目錄,就回報根目錄不見了、叫人跑 jsc-cli:deploy,收尾狀態取 aborted;第四項要單獨查:解析式帶了預設值之後,JSC_HOME 沒設不再解成 /current,但 JSC_HOME 指到已經不存在的目錄、HOME 在精簡環境裡沒設、連結農場根本還沒部署,三種都一樣會交出一條非空又絕對、前三項全過得了關的路徑,所以人在現場那一次要在同一步再跑 [ -d "{剛印出來的路徑}" ],目錄存在才算取到根目錄;排程那一輪不查,它的根目錄是裝排程的人寫進條目的,根目錄不對就會在第一支腳本呼叫上失敗。除了人在現場那一次 readlink,任何指令列都不得出現 $JSC_HOME、${JSC_HOME} 或 ~:權限層比對的是還沒展開的指令字面。實測歸納出兩條判準:一、無人值守時只有允許清單上的完整字面指令跑得動,沒有「預設安全的唯讀指令」這回事,連 readlink -f "$JSC_HOME/current"、ls -d "$JSC_HOME/current" 與沒有規則的 ls -d /root/.jsc/current 都被擋;二、路徑中段的萬用字元不匹配,版本號寫成 * 的快取路徑規則一樣擋,規則與指令都必須是完整字面。排程那一輪沒有人可以按同意,被擋就是停在第一支腳本,什麼都不記,心跳也寫不出來。接著認出使用者要的是哪一個操作,patrol 那一路全程不問人。start:先跑 tools/seed-tasks.sh apply --root {CURRENT} 把內建定期檢查項對齊委派清單(清單在 {CURRENT}/jsc-meta/tools/delegate-spec.tsv,根目錄一律用 --root 餵進去、那一支自己不解),清單上可交而待辦簿沒有的就加一筆、待辦簿有而清單上已經沒有或改成不交的就移除、origin 是 user 的一律不動、判定是 cond 的預設保留不種入並印出條件原文、trigger、recur 或 action 變了只印 drift= 不改那一筆(上游把 probe 那一欄合併進來的那一輪,四筆指令型就是走這一條,要換值得人親自帶 --refresh);種入與重建是同一個呼叫、冪等,所以每一次 start 都跑,第二次跑不會重複建;結束碼 1、2、3 都是「一筆都沒動」,不中止啟動,照實記進收尾回報再往下走,4 是部分失敗、成功的那幾筆算數,一律不帶 --force 也不自己帶 --allow-cond;接著照 patrol 的每一步跑完一輪巡檢,第一次心跳由那一輪寫、不另外寫、跑不完就不算啟動、跑 heartbeat.sh report 確認 state=fresh、跑 tools/schedule.sh install patrol 裝巡檢那一筆排程、把它印的 allow_rule= 每一行、patrol_root=(條目寫進去的字面根目錄,之後每一輪都從那裡讀)、環境快照提醒與 current 連結缺漏的警告原樣轉給人、依結束碼選一段收尾訊息印出——排程接上、排程寫進去了但 cron 沒在跑、排程沒接上三種各一段。心跳那一筆不裝了,install heartbeat 一律回 6。patrol:跑 tools/patrol.sh collect 取鎖並讀五項來源(那一輪另外會自己叫一次 tools/due.sh scan,把待辦簿的事件偵測與到期判定寫成「待辦簿到期與逾期」那一節,技能本文一律不自己再叫一次——scan 會推進事件快照,同一輪叫第二次就比不出任何事件,而那一次會回報零事件、看起來完全正常;要看下一輪會判出什麼就叫 due.sh events,那個子命令一律唯讀)(第五項是執行狀態事件:collect 自己叫 jsc-hooks/tools/report-status.sh drain 排空,緊接著跑 rotate,再把非 ok 的事件與「有 start 沒有配對 end」的技能彙整成監控頁那一節;技能本文一律不自己再跑一次 drain)、結束碼 4 就讓開不寫任何東西、結束碼 1 與 3 照樣把這一輪寫上監控頁、hash 是空的就 abort、經 jsc-gitea:wiki 讀回 MONITOR_{HASH} 舊頁、基本資料原樣留著、最新一輪那一塊整塊換成 latest_file、summary_file 的本輪那一列擺最上面(五欄:巡檢時間、本輪判定、各項成敗、待人處理、警示來源)、舊的資料列接在下面並截到 24 列、三塊重組成整頁、寫回之前先把這一頁要放進去的每一個連結交給 jsc-gitea/tools/link-check.sh(結束碼 0 才整頁寫回,結束碼 1 就把 DEAD 那幾筆原樣回報並 abort,2、3、7 同樣 abort,一個連結都沒有就跳過這一次驗證並照實說明)、頁不存在(唯有結束碼 4)才用 newpage_file 建頁、讀不回舊頁就不寫、監控頁寫成之後跑 gitea.sh wiki-url 取那一頁的絕對網址並依結束碼分流(4 回步驟三重寫、5 沒有 html_url、7 與 8 走 abort,其餘非 0 也走 abort,網址取不到就不寫那一個區塊)、換掉 contents_file 那個 H2 區塊裡 {監控頁絕對網址} 那個佔位、換完再用 link-check.sh 驗那一個網址(結束碼 0 才寫那一個區塊;非 0 一律不寫,比照目錄頁結束碼 3 當成那一個區塊沒更新、這一輪照樣往下寫心跳,並把連不到的那一筆列進待人處理)、用 jsc-gitea/tools/wiki-contents.sh upsert MONITOR 1 "MONITOR_{HASH}" {區塊檔} 以 H2 標題(也就是內容頁頁名,取 collect 印的 page=)當鍵更新 MONITOR_CONTENTS 自己那一個區塊並一律帶上 templates/monitor-contents.md 當範本(第三個參數 1 是 key-col,只在舊頁還是 markdown 表格時用得到:舊表格第 1 欄「監控頁」持有身分,那一格是 [MONITOR_{HASH}](網址),轉檔時只取文字當標題;頁面已經是條列格式時這個參數被忽略,照樣固定給 1)、目錄頁回 3(CONTENTS 存取庫沒設定)不中止這一輪,照樣往下寫心跳,並把「設 JSC_WIKI_REPO_CONTENTS 或 JSC_WIKI_REPO」列進待人處理、監控頁任一失敗或目錄頁其餘非 0 才 abort 且不寫心跳、跑 tools/patrol.sh finish 寫心跳、最後印出各項結果、本輪事件數與非 ok 事件數、非 ok 事件的明細(kind、name、status、exit、detail)、以及有 start 沒有配對 end 的那幾支技能(單獨列,那代表那一輪中止了)、兩次寫入各自的連結驗證結果(通過、無連結而跳過、或被擋下並附結束碼與 DEAD 明細)、判成警示時的警示來源與待人處理列。status:跑 heartbeat.sh report 取心跳現況、把 state 對映成新鮮、過期、心跳檔損壞、不存在、不自己解析心跳檔也不自己判定、從 file= 解出助理目錄後列出 tasks/ 底下每一個檔案並解析 state、title、next_run、fail_count、跑 tools/schedule.sh status 取排程現況與週期、印成心跳、排程、待辦三塊、fail_count 大於 0 的列標上「已連續失敗 N 次」、心跳與排程兜起來會誤讀的四種組合各補一句話、最後跑 tools/seed-tasks.sh plan --root {CURRENT} 唯讀比對內建項與委派清單並印出差在哪(該加幾筆、還剩幾筆孤兒、保留的 cond 各是哪一支、drift= 各要換什麼值),一律不跑 apply,並說明要套用差異就跑 start;那一支回 1、2、3 就照實說比不出來、不說成已對齊。stop:先跑 heartbeat.sh report 留下原本的狀態、再跑 tools/schedule.sh remove all 移除排程與舊版遺留的心跳條目、最後才跑 heartbeat.sh clear 清掉心跳、印出停止訊息並說明心跳清掉之後閘門會擋人、同時說明閘門還沒接線所以現在擋不到人。四個操作最後都一樣:回報印完之後跑一次 jsc-hooks/tools/report-status.sh skill-end jsc-assist:assistant {status} {結束碼},start 由 hook 記、end 由這裡寫,不寫就等於這一次自己看起來中止了
外部呼叫 工具一律走前置步驟取得的根目錄底下那一組不帶版本的路徑(本頁記成 {CURRENT},實際填的是像 /root/.jsc/current 這種字面絕對路徑):{CURRENT}/jsc-assist/tools/patrol.sh、{CURRENT}/jsc-assist/tools/schedule.sh、{CURRENT}/jsc-assist/tools/due.sh、{CURRENT}/jsc-hooks/hooks/heartbeat.sh,wiki 那一支是 {CURRENT}/jsc-gitea/tools/gitea.sh,目錄頁那一支是 {CURRENT}/jsc-gitea/tools/wiki-contents.sh,連結驗證那一支是 {CURRENT}/jsc-gitea/tools/link-check.sh,執行狀態事件那一支是 {CURRENT}/jsc-hooks/tools/report-status.sh,範本是 {CURRENT}/jsc-assist/templates/monitor-contents.md;JSC_HOME 沒設時,人在現場那一次 readlink 自己退回 ~/.jsc 再解,排程那一輪則直接用條目餵進來的值,指令列上不留變數也不留波浪號;不拿技能提示給的快取基底目錄組工具路徑——權限只放行 current 那一組,快取路徑帶版本號,規則寫成萬用字元也對不上,用錯路徑會被靜靜擋掉。jsc-hooks/hooks/heartbeat.sh 的 write、report、clear 三個子命令,六個結束碼各有處置:0 往下走、1 與 3 印「助理未運行」、2 回報判不出狀態並停下、4 當成不新鮮並回報心跳檔損壞、5 是嚴重狀況要吵出來且不得回報成功、6 是呼叫寫錯要更正後重跑。write 只由 tools/patrol.sh finish 呼叫,技能自己不呼叫。本 domain 的 tools/schedule.sh 的 install、remove、status 三個子命令:install 會把安裝當下偵測到的 CLI 代號記進條目(判法照組 PATH 那一套逐支 command -v,不另叫外部腳本;那是往後每一輪判「有沒有少跑一支」的唯一參照基準)、會查 {CURRENT}/jsc-assist 與 {CURRENT}/jsc-gitea 兩個連結在不在、不在就警告且不代建,會把巡檢的 CLI 用 command -v 解成絕對路徑、把只放得下 CLI 執行檔那幾個目錄的 PATH(不是整條照抄——實測整條 2160 字元,crontab 一行有長度上限,照抄會回「command too long」而裝不起來;帶會過期的安裝目錄時重跑一次 install 就重新拍一份)、GITEA_HOST、GITEA_TOKEN、JSC_HOME、JSC_ASSISTANT_HEARTBEAT_TTL 與所有已設定的 JSC_WIKI_REPO 系列快照進條目(PATH 一定要帶:cron 給的只有 /usr/bin 與 /bin,而 CLI 執行檔多半裝在家目錄底下,少了它那一輪偵測 CLI 會回結束碼 0 加一份空清單,看起來像這台機器沒裝 CLI;巡檢要跑的指令本身走字面絕對路徑,不靠這一條)(含內容頁的 JSC_WIKI_REPO_MONITOR 與目錄頁的 JSC_WIKI_REPO_CONTENTS,名單當下從環境撈、不寫死,新頁型自動涵蓋)、條目自帶 JSC_GITEA_CONFIRM=yes、刻意不寫死工作階段代號(寫死會讓那一輪的 start 與 end 落在不同的代號上:前者由 hook 從標準輸入的 JSON 讀 CLI 真正的代號,後者由工具腳本只讀得到環境變數,兩半永遠配不起來,每一輪都被算成一支中止的技能)、把自己解好的字面根目錄寫進條目的提示文字(固定格式 工具根目錄={字面絕對路徑},那一輪就是從這裡讀根目錄)並印成 patrol_root=、--patrol-cmd 或 JSC_ASSIST_PATROL_CMD 給的自訂指令沒帶那一段時只警告不中止、並印出這一輪要開的 allow_rule= 規則(七支腳本各三種呼叫形式,含 gitea.sh、wiki-contents.sh、link-check.sh 與 jsc-hooks/tools/report-status.sh——Skill(jsc-gitea:wiki) 只放行叫用技能,技能內部的 Bash 呼叫仍各自受檢;路徑是 current 那一組確切路徑,不用萬用字元);七個結束碼各有處置:0 往下走、1 是條目裝了但 cron 沒在跑要照實講不會執行、2 是缺 jsc-hooks 導致門檻讀不到、3 是這台機器沒有排程機制、4 是排程操作失敗要原樣引用 stderr、5 是回讀驗證失敗要叫人自己去看 crontab -l、6 是呼叫寫錯,含 install heartbeat、週期塞不進門檻、判不出 CLI、那一支 CLI 的執行檔不在 PATH 上,以及 JSC_HOME 解不出絕對路徑(條目寫不出字面根目錄)。本 domain 的 tools/patrol.sh 的 collect、finish、abort、rebuild 四個子命令(collect 除了 due_file= 也轉出 due_rows_file=,那是機器可讀的到期清單,執行那一步要拿它當 --rows;rebuild 拿讀回來的舊頁把三塊接成要寫回去的整頁,--old 指舊頁檔、印 rebuilt_file= 與 old_format=;接法固定所以寫死在腳本裡,不留給呼叫端臨場挑工具——實測某一輪挑了 awk 被權限擋掉兩次,那一輪有人在場才繞得過去,而暫存目錄已經累積 124 個同一件事的檔案;基本資料那一塊逐字照抄,連尾端空行都不動),七個結束碼各有處置:0 往下走、1 部分失敗照樣寫頁、2 是 finish 找不到 heartbeat.sh 要回報「記下來了但沒有心跳」、3 是各項全失敗照樣寫頁且判定異常、4 是讓開或鎖被搶走一律不寫心跳、5 是檔案系統失敗要吵出來、6 是呼叫寫錯。巡檢那五項讀 jsc-log/tools/usage-stats.sh、jsc-hooks/hooks/version-guard.sh report、jsc-hooks/hooks/restart-gate.sh report、$JSC_HOME/sessions/*.stage、$JSC_HOME/wp/*.pr、heartbeat.sh report、jsc-hooks/tools/report-status.sh drain 與 rotate,除了排空會把事件流的位移往前推之外全部只讀,任一項失敗不影響其餘各項。report-status.sh 三個結束碼各有處置:0 是排空到新事件、3 是沒有新事件(正常狀態,不是失敗)、2 是呼叫寫錯;找不到這一支、drain 回 0 與 3 以外的碼、或 rotate 回非 0,都只讓這一項標成失敗或記一筆警示,一律不中止那一輪——回報鏈自己壞掉不可以把被回報的那一輪拖下去。rotate 只在 drain 成功時緊接著跑:中間隔越久,那段時間新寫進來的事件被搬進備份檔而從此排不到的機會越大;排空失敗時位移狀態未知,這時候輪替會直接吃掉還沒排空的那一批。配對以 session 加 name 為鍵,不只看 name:五支 CLI 併發時同一支技能會有好幾個工作階段同時在跑。沒配對到的 start 留在 $JSC_HOME/assistant/events-open.tsv 跨輪繼續配對,開超過心跳門檻才算疑似中止,未達門檻的算還在跑,超過一天沒配對到就丟掉。wiki 讀寫一律經 jsc-gitea:wiki,技能自己不拼 API 呼叫;只有目錄頁那一個 H2 區塊例外,走 jsc-gitea/tools/wiki-contents.sh upsert,它自己解 CONTENTS 存取庫、自己讀回整頁比對標題,舊頁還是 markdown 表格時自己先整頁轉成 H2 區塊再寫,七個結束碼各有處置:0 已更新或已新增、1 組不出頁面內容或寫入失敗要 abort(找不到同名標題不算錯,那是附加)、2 參數錯就改正重跑(範本路徑不存在也回這一碼,代表 plugin 沒裝齊)、3 是 CONTENTS 存取庫未設定且不中止這一輪、4 是頁不存在又沒給範本,本技能一律帶第五個參數所以不會出現、7 金鑰失效要 abort、8 其他 API 失敗要 abort。比對鍵取 H2 標題,也就是內容頁頁名 MONITOR_{HASH},不取「監控頁」那一條的連結:連結含 GITEA_HOST 與頁名的網址編碼,那三樣一變鍵就對不上,同一台機器每輪多附一個區塊;頁名只由 {主機名}/{登入帳號} 決定,那三樣都動不到它。連結一律寫成 [{文字}]({絕對網址}),網址只取 gitea.sh wiki-url 印的那一個、不自己組路徑,那一支的結束碼 4、5、7、8 與其餘非 0 各有處置;每一個要放進頁面的連結在寫入前先過 jsc-gitea/tools/link-check.sh,它每個網址印一行 `{OK
完成條件 四個操作都要先取得工具根目錄,之後每一支腳本都拿那一個字面絕對路徑呼叫;排程那一輪只從叫用文字取,取不到就回報條目沒帶根目錄並中止,收尾狀態取 aborted,不得改跑 readlink 或任何解析指令,也不得改用帶變數的路徑硬跑;人在現場叫用時取不到才自己解一次,解出來的要是一條存在的絕對路徑(同一步用 [ -d ] 查過),解不出來或目錄不存在就回報缺 current 並中止,同樣取 aborted。start 要先跑過一次 seed-tasks.sh apply 並把它的結束碼、added=、removed=、kept=、drift=、held=、bad=、probe=、pending=、probe_bad= 各數字與逐列 held=、bad=、drift=、dup=、skip_user=、probe=、pending=、probe_bad= 記進收尾回報(pending= 那幾筆要寫成「有唯讀盤點入口、還沒接上」並附清單上的理由原文,不得跟本來就只提醒的那幾筆混成一句;probe= 那幾筆的 holes= 不是減號時要寫明還留著哪幾個代入點)(回 1、2、3 時要寫成「內建項原樣沒動」並附原因,不得寫成「沒有可交項目」),然後要那一輪巡檢的 finish 回 0 且 report 回 state=fresh,才算啟動成功;巡檢沒寫成心跳一律回報失敗並停下,不得宣稱啟動;schedule.sh install patrol 回 1 要講明條目不會被執行與 sudo service cron start,不得宣稱排程會定時執行;回 0 或 1 都要把 allow_rule= 各行、「條目含金鑰快照、變數改了要重裝」這句提醒,以及 current 連結缺漏的警告轉出去。patrol 要五項各自有 status、「待辦簿到期與逾期」那一節要有逐筆判定表(due.sh 回 0、1、2、3、4 都算判過,其中 1、2、3 各記一筆警示與一列待人處理;回別的碼就照實寫「這一輪判不出到期」並說明那不代表沒有任何一筆到期,不留空白也不寫成「都沒到期」)、執行狀態事件那一項要印出本輪事件數、非 ok 事件數與未配對的 start(drain 回 3 是沒有新事件,照樣算這一項讀到底)、監控頁那一頁要放的連結全部通過 link-check.sh(或整頁本來就沒有連結)、監控頁三塊重組寫成、目錄頁那一個 H2 區塊的網址通過 link-check.sh 後更新成功,或以目錄頁結束碼 3、或以連結驗證非 0 回報成沒更新、finish 回 0,才算一輪跑完;collect 回 4 是讓開,不算失敗也不寫任何東西;舊頁讀不回來就不寫,回報「這一輪沒有結果」;連結驗證沒過就不寫那一頁,監控頁沒寫成就 abort,心跳一定不寫;目錄頁除了結束碼 3 之外的非 0 也一樣 abort,結束碼 3 只少一筆索引,那一輪的結果已經在監控頁上,照樣寫心跳並把缺的變數列進待人處理;目錄頁那一個區塊的連結驗不過同樣只少一筆索引,照樣寫心跳並把那一筆列進待人處理。status 要印出現況表,或印出「助理未運行」並說明原因,並且要多印一段內建項與委派清單的差異(該加幾筆、還剩幾筆孤兒、保留的 cond 各是哪一支、drift= 各要換什麼值,以及「要套用就跑 start」這句話),那一段比不出來就照實說比不出來;心跳不存在、待辦簿目錄不存在、待辦簿零筆、排程沒裝、清單讀不到,五種都算正常結束。stop 要 schedule.sh remove all 先回 0、clear 再回 0,並印出帶三段話的停止訊息;remove 非 0 就回報排程還在、助理停不掉,不清心跳也不印停止訊息;clear 回 5 就回報心跳檔還在、助理沒有確實停掉,不印停止訊息。四個操作都要在回報之後寫一筆 skill-end,status 取 ok、blocked、failed、degraded、aborted 五選一,要與回報出去的結果一致;那一支回非 0 只回報成回報鏈的缺陷,不改寫這一次操作的成敗
可驗證跡象 四個操作的轉錄裡,每一條指令列都是字面絕對路徑,開頭是 /,沒有 $JSC_HOME、${JSC_HOME} 或 ~,也沒有任何一次因為路徑帶變數而跳出來的權限詢問;排程那一輪從頭到尾一次 readlink、一次 ls 都沒有,根目錄直接取自叫用文字;人在現場那一路才可能有 readlink,而且同一次叫用只出現一次。start 之後 $JSC_HOME/assistant/heartbeat 存在,ts 是剛才那一輪的時間,crontab -l 找得到一筆帶 # jsc-assist:assistant patrol 的條目,而且只有一筆,帶 # jsc-assist:assistant heartbeat 的舊條目一筆都不剩;那一筆條目裡的 CLI 是絕對路徑,前面帶著 JSC_GITEA_CONFIRM=yes 與環境變數快照,提示文字裡有「工具根目錄=」接一個字面絕對路徑,那個值與 install 印的 patrol_root= 和 allow_rule= 用的根目錄完全相同,不是變數也不是快取實體路徑;install 印出的 allow_rule= 都是 current 那一組展開後的字面絕對路徑,沒有變數、沒有波浪號、沒有萬用字元,也沒有 Write(...),而且 jsc-gitea/tools/link-check.sh 與 jsc-hooks/tools/report-status.sh 那三種呼叫形式都在裡面。patrol 跑完之後 wiki 的 MONITOR_{HASH} 只有三塊:基本資料一字未改、最新一輪換成本輪、摘要表最上面一列是本輪且總列數不超過 24,頁名的 {HASH} 是 40 碼大寫十六進位,雜湊來源那一列寫的是不含網域的短主機名;CONTENTS 存取庫裡的 MONITOR_CONTENTS 只有自己那一個 H2 區塊變動,同一台機器從頭到尾只有一個區塊,標題是 MONITOR_ 接 40 碼大寫十六進位、標題上不帶連結也不帶網址,區塊裡「監控頁」那一條是 [{頁名}]({絕對網址}) 這種連結、點下去開得起那一頁,「HASH」那一條是裸 HASH、40 碼大寫十六進位、不帶連結,八條欄位一條都不缺、格式是 - {欄位名}:{值},頁上一個 markdown 表格都不剩,兩頁上點得到的連結沒有一個是死的——把頁上的網址抓出來重跑一次 link-check.sh,應該全部是 OK、結束碼 0,別台機器的區塊一字不動,$JSC_HOME/assistant/patrol/ 底下有本輪的 latest.md、summary.md、summary-row.md、newpage.md、contents-entry.md,摘要列是五欄、警示來源那一欄有值或寫「無」;兩支腳本不是從 current 那一組路徑跑起來時,stderr 會有一行 [WARN] 點出實際路徑與應該用的路徑,$JSC_HOME/assistant/usage-prev.tsv 換成本輪的累計數,$JSC_HOME/assistant/events-prev.tsv 換成本輪的狀態快照(三欄定位字元分隔,每一個來源另有一列 meta)、$JSC_HOME/assistant/events-seen.tsv 是每一個事件名的累計次數與最後發生時間,$JSC_HOME/assistant/patrol/due/ 底下有本輪的 due.md 與 rows.txt(rows.txt 是十一欄定位字元分隔,欄位順序印在 rows_columns=;要逐筆取值就讀它,不要切 task= 那幾行,那幾行的四個欄位都可能帶空白),第一次跑那一輪的 due.sh 印 first_run=1、事件數為 0,且 tasks/ 底下一個檔案都沒被改動,$JSC_HOME/assistant/patrol.lock 已經放掉;監控頁的最新一輪有「執行狀態事件」那一節,節裡有本輪事件數、非 ok 事件數,以及非 ok 明細與未配對 start 兩張表(一筆都沒有時寫明「沒有」,不留空表格);$JSC_HOME/usage/scan-state/events.offset 的數字往前推到本輪排空的位置,$JSC_HOME/assistant/events-open.tsv 只剩下還沒配對到 end 的那幾筆。讓開的那一輪沒有任何寫入跡象。stop 之後心跳路徑不存在,crontab -l 找不到任何 # jsc-assist:assistant 條目。以上都不動別人的排程條目,條目數量前後相同。status 無寫入跡象,只有回報內容。四個操作跑完,$JSC_HOME/usage/events.jsonl 最後都多一筆 name 是 jsc-assist:assistant、phase 是 end 的事件,status 與回報出去的結果一致,而且同一個 session 下它與 hook 記的那一筆 phase=start 配得起來。patrol、status、stop 三個操作都不動 tasks/ 底下的檔案;start 只在第一步經 tasks.sh 動內建項,動完之後 tasks/ 底下每一個 origin=user 的檔案內容與修改時間都一字未改,origin=assistant 且帶 spec_key 的那幾筆與委派清單上非 none、非 cond 的那幾列一對一對得上(同一個 spec_key 只有一個檔案),spec_key 是空的那幾筆一筆都沒被加也沒被刪,判定是 cond 的那幾支在 tasks/ 底下找不到對應檔案而回報裡逐支有一行 held=;way 只有 patrol 或 remind、而 probe 是一行指令的那幾筆,action= 那一行是完整字面絕對指令,開頭是 / 或一個大寫環境變數指派,路徑中段沒有版本號、沒有金錢符號、沒有波浪號,代入點只可能剩 {cli} 或 {repo},而且那幾行在回報裡各有一行 probe= 對得上;probe 是 pending: 的那幾筆 action=remind、待辦檔上看不出跟本來就只提醒的那幾筆有什麼不同,差別只在回報裡的 pending= 那幾行;清單只有十一欄時 probe=、pending=、probe_bad= 三個數字全是 0,而且 stderr 有一行 note 講明那一份清單沒有第十二欄。四個操作都不動 worktree 與程式碼存取庫。排程的 log 一律在 $JSC_HOME/assistant/schedule.log,不落在任何存取庫