Commit Graph
6 Commits
Author SHA1 Message Date
jiantw83andClaude Opus 5 465bd9d40a feat(assistant): 逾期判定接上來,逾期與連續失敗每一輪都提
逾期本來沒實作,監控頁上就寫著「上面的判定只講到期,不講逾期」。

到期與逾期分兩欄,不合成一個判定:到期是「該跑了」,逾期是「due 欄那個
截止時間過了」。一筆七天一次的檢查項天天都可能到期,卻永遠不會逾期;
一筆有截止時間的交辦可能早就逾期,卻因為排定點還沒到而判成還沒到。
合成一個值就把兩種情況混成同一格。

due.sh 多第十二欄 overdue,值是減號(沒有截止時間)、no、逾期的秒數,
或 bad。bad 算成欄位不合法回 2——一個解不開的截止時間跟沒有截止時間
在畫面上長得一模一樣,而前者是登錄的時候就填錯了。done 那幾筆不算逾期;
被暫停的那幾筆照算,因為暫停中止的是到期判定,不是日曆,而一筆被暫停
又逾期的交辦正好是最容易被忘掉的那一種。

另加唯讀子命令 due.sh overdue 給狀態查詢那一路走。走 scan 會推進快照,
那一輪之後的事件判定會全部落空;走 events 又判不到逾期;讓呼叫端自己
拿 due 欄去比更糟,那是第二套判定,漂移的那一天兩邊都說自己對。

patrol.sh 在逾期或連續失敗筆數大於 0 時各列一筆待人處理,每一輪都列。
兩種都不會自己好:截止時間過了不會因為過更久就不逾期,連續失敗的項目
每一輪都會再試一次然後再失敗一次。「提過了」不是「處理過了」,而助理
不替人按暫停,那個狀態留給人設。

監控頁那一節逐筆點名連續失敗的那幾筆——id、失敗幾次、最後一次執行、
標題。原本只印一個總數,而失敗次數這個欄位存在的理由是指出「有一筆壞掉
的項目每輪重試而沒人知道」,不說出是哪一筆,那個理由只完成一半。

目錄頁刻意不加欄位。那一頁一台機器一個區塊、各自那一輪寫,新加一條只有
跑到新版的機器寫得出來,讀的人分不出「這台沒有逾期」與「這台還沒寫這
一條」——理由與當初把「本輪非 ok 事件數」擋在那一頁外面的一樣。

欄位數從 11 變 12,run-due.sh 那道欄位數檢核跟著改成 12。那道檢核擋的
不是第 12 欄本身,是「兩支不是同一版」——版本不同的時候,前 11 欄的
順序也不能假設還是原來那樣。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:53:00 +08:00
jiantw83andClaude Opus 5 205da9b61f fix(assistant): 判定是不是存取庫交給 git 自己回答
原本看 .git 在不在。這台機器的工作目錄底下剛好有一個目錄的 .git 只剩
一個空的 info/,git 一句「not a git repository」——而掃描把它算成一個
存取庫,還印成「沒有 origin」。那個說法讀起來像「這個存取庫還沒接遠端」,
不像「這裡根本不是存取庫」,兩件事的處置完全不同。

改成由 git 回答,並核對它認定的頂層就是那個目錄:只問「是不是在工作樹裡」
不夠,git 會從那裡往上找,.git 壞掉時它會找到上一層的存取庫然後答「是」。
兩邊都解成實體路徑再比,掃描起點帶符號連結那一段時字串才對得上。

多一個 not_a_repo= 判定行與收尾計數,執行那一支照樣原樣轉出。
存取庫數從 19 個變 18 個。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:39:43 +08:00
jiantw83andClaude Opus 5 e53bb23f8e feat(assistant): 存取庫掃描接上 {repo} 代入點
新增 tools/scan-repos.sh:掃出工作目錄底下的存取庫,交出 {repo} 要代的
那份清單,順便對出每一個存取庫的 REPO_{HASH} 盤點頁頁名(雜湊規則與
盤點頁那一支相同,實測值一致)。

掃描起點刻意沒有預設值。給一個預設就等於猜,而猜錯的後果不是掃不到,
是在猜錯的那些目錄底下跑指令——往上一層是家目錄、再往上是整台機器,
而那一輪沒有人看得到它跑到哪裡去了。沒設就回 3 並說要設哪一個變數。
深度預設一層、上限三層;點開頭的目錄、符號連結、路徑帶空白或殼層特殊
字元的三種一律不交出去,但逐個印出來——那是真的存在卻沒被盤點到的
存取庫,只印一個總數會讀成整批都掃過了。

run-due.sh 改成兩個代入點都代:{cli} 取偵測到的 CLI 代號、{repo} 取
掃到的存取庫,兩個都帶的那一筆目標數是乘積。待辦簿那一筆的 repo 欄
有值時只代那一個,值可以是路徑也可以是 {owner}/{repo};對不出來就
印 held=,不退回全部存取庫——那一筆指名了一個目標。代入之後再驗一次
禁止字元,因為代進去的值是這一輪現場算出來的。

schedule.sh 把 JSC_ASSIST_SCAN_ROOT 與 JSC_ASSIST_SCAN_EXCLUDE 快照進
排程條目。少了掃描起點,帶 {repo} 的內建項每一輪都代不出目標,而那一輪
只印一行 held=,看起來像這一批還沒接上、不像一個變數沒進條目。

同時補兩個只有真的執行才發現得了的缺陷:

一、cron 一行的長度上限只在寫入那一刻由 crontab 自己擋,--dry-run 那一路
根本不碰它,所以預演每一次都過。實測踩過一次:整條 PATH 快照進條目那一批
預演全綠、安裝回結束碼 4。改成建好條目就量,兩個模式都印 entry_len= 與
上限,達到上限當場拒絕並指出長度是從哪幾個快照變數來的。

二、run-due.sh 截錯誤訊息用的 cut -c 數的是位元組不是字元,中文字剛好被
截在中間就在報告上留下一個替代字元。改成截完再過一次 iconv -c 丟掉那個
不完整的序列。這一種不會有人來報:亂碼不影響結束碼。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:36:26 +08:00
jiantw83andClaude Opus 5 6cb071a3e7 feat(執行): 拿排程條目記下的 CLI 清單比對,少跑一支就點名
偵測 CLI 那一支純看 PATH,分不出「裝了但執行檔不在 PATH 上」。而條目帶的
PATH 是安裝當下拍的,其中某些 CLI 裝在會過期的目錄底下——這台機器實測就有
一支裝在帶行程編號的多殼層目錄,重開機之後那條路徑就不在了。

少了基準的後果不是報錯,是安靜地少做一支。那一輪照樣把偵測到的每一支都跑
過、照樣全部成功、照樣回報「都跑過了」,因為它不知道該有幾支。實測那一輪報
「對 4 支 CLI 都跑過,全部成功」,而機器上裝了 5 支——報告讀起來完全正常。

所以條目多記一個變數,記下安裝當下偵測到哪幾支,作為往後每一輪的參照基準。
判法照組 PATH 那一套逐支 command -v,不另叫外部腳本:install 那一刻本來就用
同一套判過一次,再叫一支等於同一件事兩套實作,而那一支拿得到的資訊跟這裡
一樣多,多繞一層沒有換到任何東西。

讀的那一邊比對之後印 cli_missing= 並點名少了哪一支,同時明說「全部成功不代表
全部跑過」。只報不修:那條路徑本質上是暫時的,真正的解法是那支 CLI 該裝在
穩定位置,或者重跑一次排程安裝重新拍快照——兩件都不是這一支該替人決定的。

舊條目沒有那個變數時安靜通過,不誤報:那一種情況分不出「沒少」與「沒基準」,
而拿「沒基準」當缺失會讓每一台還沒重裝的機器每輪亮一次紅。

實測三種情況:基準與實際一致時不出聲;基準多一支不存在的會點名那一支;完全
沒設基準時印減號、不警告。另外拿條目實際帶的那條 PATH 在清空環境裡重現真機
情境,確實抓到少了那一支。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:40:00 +08:00
jiantw83andClaude Opus 5 ccb28e2bb3 fix(執行): 到期清單的路徑一律由呼叫端餵進來,並拒跑過舊的清單
判到期與執行是兩支腳本,中間靠一個檔案交棒。判到期那一支是被巡檢用 --out
叫的,輸出寫進那一輪自己的暫存目錄;而執行那一支留了一個預設路徑,指向助理
狀態目錄底下那一份。兩邊不是同一個位置。

於是執行那一支從上線到現在,每一輪讀的都是「上一次有人手動跑判定留下的那一
份」。實測那份清單在機器上放了 67 小時,每一輪都被拿去動手,其中一輪還跑了
一筆已經被移除的待辦——那一筆的識別碼在重新種入時換過了。

而它看起來完全正常。那份舊清單產生的當下是對的,內容剛好沒變,所以每一輪的
報告都合理。錯了 67 小時才顯形。

兩處改動。判到期那一步本來只把給人看的那個檔案轉出去,機器可讀的那一份被
吞掉了,現在一起印成 due_rows_file=。執行那一支拿掉預設值:少帶那個選項就
回用法錯誤,吵一次總比安靜地對舊資料動手好。

另外加一道時效保護。判到期與執行之間隔著呼叫端,那一段有可能斷掉——判定失敗
了而那一輪照樣往下走,或呼叫端餵進上一輪的路徑。門檻取心跳的過期門檻:那是
這台機器認定「一輪跑完的紀錄還算新鮮」的長度,一份比它還舊的到期清單本來就
不可能是這一輪產生的。讀不到門檻就退回 300 秒,跟心跳那一支的預設一致。

實測:不帶選項回 6,清單不存在回 2,兩小時前的清單回 2 且訊息帶出實際秒數與
門檻,新的空清單回 0;拿真機那份 67 小時前的舊清單餵進去,確實被擋下。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 08:59:43 +08:00
jiantw83andClaude Opus 5 bccdbbbd24 feat(執行): 到期的內建檢查項真的跑一遍,成敗回寫待辦簿
待辦簿蓋好之後一直沒有人執行它。十六筆內建項、十五筆到期,而 last_run 與
fail_count 從建立到現在一次都沒動過——巡檢每輪把它們印出來,如此而已。

這一支只跑動作是一行指令、而且帶著 spec_key 的那幾筆。技能名那一種不跑:
叫用一整支技能會寫頁、開 PR、改檔案,而這一支跑在沒有人看的那一輪裡;唯讀
盤點指令失敗最多回一個非零碼,一支技能中途失敗可能留下寫到一半的頁。兩種
代價不同,就該分兩次判、分兩次接。使用者親手登錄的那幾筆也不跑,那幾筆沒有
經過種入那一支的檢核,動作欄想寫什麼都行。

代入點只展開得了 CLI 那一個。存取庫掃描還沒做出來,所以帶那個代入點的一律
印一行跳過,而且**執行紀錄與失敗次數一個字都不動**:代不出目標不是那一筆做
錯了什麼,記成失敗會讓一個沒有人修得動的計數一路往上爬,而那個計數存在的
理由正是指出「有一筆壞掉的項目每輪重試而沒人知道」。把「還沒接上」記成
「壞掉」,等於用假的壞掉把真的壞掉蓋掉。

執行前再驗一次指令形狀,不信任待辦檔的內容——那是純文字,種入之後可能被改。
擋掉金錢符號、反引號、波浪號、分號、管線與連接符號,路徑一定要落在這一輪的
根目錄底下,代完之後不得留大括號。

權限閘門看不到這一支裡面跑了什麼,檔頭把理由寫明了:閘門是可信入口的白名單、
不是沙箱,清單上每一支腳本本來就做得了它該做的事。防線改由「只跑經過審查的
清單種出來的項目」加「執行前再驗一次形狀」擋。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:58:16 +08:00