diff --git a/AGENTS.md b/AGENTS.md index a84dc2c..b45dad0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ 1. 不做任何要問使用者的決策。背景巡檢時靜默套預設值,等於把逐項共識整條做掉。 2. 不參與閘門判定。閘門必須留在 hook:同步、不連網、毫秒級。助理只負責維持心跳。 3. 不寫程式碼存取庫、不 commit、不 push、不開 PR、不合併。 -4. 不覆寫 wiki 頁,一律只附加。助理的寫入是背景行為,覆寫錯了沒人在現場。 +4. 監控頁只照固定三塊寫:最新一輪整塊換掉、摘要表保留近 24 輪、基本資料建頁之後不動;目錄頁只動自己那一列,別台機器的列一個字都不碰。軌跡留在摘要表,一輪一列,看得出是從哪一輪開始壞的;完整內容只留最新一輪,因為頁面要能讀完才有人讀。 5. 不刪除狀態檔、worktree 與 wiki 頁。破壞性操作留給人發動。 6. 不自動執行自己提出的建議。建議與執行是兩件事,自動接下去等於整條流程沒人按過同意就跑完。 diff --git a/README.md b/README.md index 51977cb..047d719 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 ### `assistant` -助理主體,四個操作:`start` 啟動、`status` 查現況、`patrol` 跑一輪巡檢、`stop` 停止。心跳的寫入、判定與清除一律交給 `jsc-hooks` 的 `hooks/heartbeat.sh`,判定只有那一份;系統排程一律交給 `tools/schedule.sh`;一輪巡檢的流程交給 `tools/patrol.sh`。**心跳由巡檢寫,而且只由巡檢寫**:一輪跑完、結果寫上監控頁了,才寫那一次心跳,所以心跳新鮮等於「上一輪巡檢真的做完了」。`start` 先跑一輪巡檢,再裝上巡檢那一筆排程;巡檢週期由心跳的過期門檻算出來,兩個數字綁在一起。`patrol` 讀四項來源(使用統計、版本與重啟閘門、SDLC 階段鎖與工作包鎖、心跳自述),四項各自獨立,一項掛掉其餘三項照跑、照記,結果一律附加到 `MONITOR_{HASH}`、不覆寫。`status` 全程唯讀,讀心跳、排程與待辦簿,印成三塊;助理沒在跑就印「助理未運行」,不當成錯誤。`stop` 先移除排程再清掉心跳,順序不能反。這支不參與閘門判定、不做決策、巡檢那一路全程不問人。 +助理主體,四個操作:`start` 啟動、`status` 查現況、`patrol` 跑一輪巡檢、`stop` 停止。心跳的寫入、判定與清除一律交給 `jsc-hooks` 的 `hooks/heartbeat.sh`,判定只有那一份;系統排程一律交給 `tools/schedule.sh`;一輪巡檢的流程交給 `tools/patrol.sh`。工具一律用 `$JSC_HOME/current/{外掛名}` 那一組不帶版本的路徑叫,不用技能提示給的快取基底目錄——權限只放行 current 那一組。**心跳由巡檢寫,而且只由巡檢寫**:一輪跑完、結果寫上監控頁了,才寫那一次心跳,所以心跳新鮮等於「上一輪巡檢真的做完了」。`start` 先跑一輪巡檢,再裝上巡檢那一筆排程;巡檢週期由心跳的過期門檻算出來,兩個數字綁在一起。`patrol` 讀四項來源(使用統計、版本與重啟閘門、SDLC 階段鎖與工作包鎖、心跳自述),四項各自獨立,一項掛掉其餘三項照跑、照記,結果寫上 `MONITOR_{HASH}`:那頁固定三塊,基本資料不動、最新一輪整塊換掉、摘要表保留近 24 輪,一輪一列。`status` 全程唯讀,讀心跳、排程與待辦簿,印成三塊;助理沒在跑就印「助理未運行」,不當成錯誤。`stop` 先移除排程再清掉心跳,順序不能反。這支不參與閘門判定、不做決策、巡檢那一路全程不問人。 @@ -43,11 +43,11 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 | 檔案 | 用途 | | --- | --- | -| `tools/schedule.sh` | 助理系統排程的安裝、移除與查現況。三個子命令 `install`、`remove`、`status`,只裝 `patrol` 這一筆——心跳由巡檢自己寫,`install heartbeat` 一律回 6,舊版遺留的心跳條目由 `install patrol` 順手清掉。巡檢週期由心跳的過期門檻算出來(`2 × 週期 × 60 < 門檻`,再取能整除一小時的分鐘數):門檻 300 秒是每 2 分鐘一輪,門檻 1800 秒是每 12 分鐘一輪。Linux、WSL 與 macOS 走 crontab,Windows 走 schtasks。條目行尾帶固定標記 `# jsc-assist:assistant {工作}`,只動自己那一筆,別人的排程一行都不碰。裝完會檢查排程服務在不在跑,沒跑就回 1——WSL 預設不啟動 cron。`--dry-run` 只印組出來的條目與寫回後的內容,什麼都不動 | -| `tools/patrol.sh` | 一輪巡檢的收攏與收口。三個子命令:`collect` 取鎖、讀四項來源、組出監控頁要附加的那一節與目錄頁那一列;`finish` 在監控頁寫成之後才寫心跳、換上用量快照、放掉鎖;`abort` 只放掉鎖,不寫心跳。四項來源各自獨立,一項失敗其餘三項照跑,失敗那一項在頁上寫明是「這一項失敗」而不是沒資料。整輪拿一把目錄鎖,上一輪還在跑就回 4 讓開;鎖逾時(門檻取心跳門檻)會被下一輪搶回來,並在頁上記一筆。`version-guard.sh report` 回「查詢失敗」時照原字抄,不補查、不美化 | +| `tools/schedule.sh` | 助理系統排程的安裝、移除與查現況。三個子命令 `install`、`remove`、`status`,只裝 `patrol` 這一筆——心跳由巡檢自己寫,`install heartbeat` 一律回 6,舊版遺留的心跳條目由 `install patrol` 順手清掉。巡檢週期由心跳的過期門檻算出來(`2 × 週期 × 60 < 門檻`,再取能整除一小時的分鐘數):門檻 300 秒是每 2 分鐘一輪,門檻 1800 秒是每 12 分鐘一輪。Linux、WSL 與 macOS 走 crontab,Windows 走 schtasks。條目行尾帶固定標記 `# jsc-assist:assistant {工作}`,只動自己那一筆,別人的排程一行都不碰。條目自己把環境帶齊:CLI 用 `command -v` 解成絕對路徑、安裝當下把 `GITEA_HOST`、`GITEA_TOKEN`、`JSC_HOME`、`JSC_ASSISTANT_HEARTBEAT_TTL` 與已設定的 `JSC_WIKI_REPO` 系列快照進條目、自帶 `JSC_GITEA_CONFIRM=yes`——cron 的 PATH 很短、不讀設定檔、也沒有 tty。印出條目時金鑰一律遮掉,條目本身含金鑰快照,crontab 檔案要保持只有本人讀得到,變數改過要重跑一次 install。裝完會檢查排程服務在不在跑,沒跑就回 1——WSL 預設不啟動 cron;也會檢查 `$JSC_HOME/current` 那組連結在不在、印出這一輪要開的 allow 規則,連結不在只警告、不代建。`--dry-run` 只印組出來的條目與寫回後的內容,什麼都不動 | +| `tools/patrol.sh` | 一輪巡檢的收攏與收口。三個子命令:`collect` 取鎖、讀四項來源、組出監控頁的「最新一輪」與「近 24 輪摘要」兩塊、本輪的摘要列與目錄頁那一列;`finish` 在監控頁寫成之後才寫心跳、換上用量快照、放掉鎖;`abort` 只放掉鎖,不寫心跳。四項來源各自獨立,一項失敗其餘三項照跑,失敗那一項在頁上寫明是「這一項失敗」而不是沒資料。整輪拿一把目錄鎖,上一輪還在跑就回 4 讓開;鎖逾時(門檻取心跳門檻)會被下一輪搶回來,並在頁上記一筆。`version-guard.sh report` 回「查詢失敗」時照原字抄,不補查、不美化 | | `references/behaviors.md` | 本 domain 的技能行為清單:一支技能一節,五列記下觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象,供稽核與驗證比對。格式合約見 `plugins/meta` 的 `references/guidelines.md`「技能行為清單」 | | `templates/monitor-contents.md` | 目錄頁 `MONITOR_CONTENTS` 的範本。一列代表一台機器,雜湊來源是 `{主機名}/{登入帳號}`。寫入語意是**只更新自己那一列**:比對主機與帳號兩欄,別台機器的列原樣保留,禁止整頁覆蓋 | -| `templates/monitor-page.md` | 內容頁 `MONITOR_{HASH}` 的範本。記的是這台機器的巡檢軌跡。寫入語意與目錄頁相反,是**一律附加一節、不覆寫**:一次巡檢一節,節標題帶時間戳,既有的節一個字都不動 | +| `templates/monitor-page.md` | 內容頁 `MONITOR_{HASH}` 的範本。記的是這台機器的巡檢軌跡。頁面固定三塊:本頁基本資料建頁時寫一次就不動、最新一輪每輪整塊換掉、近 24 輪摘要一輪一列且最新的在最上面。軌跡留在摘要表,完整內容只留最新一輪,頁面才讀得完 | ## 助理的狀態檔 @@ -59,7 +59,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 | `tasks/{id}` | 待辦簿,一筆一檔。一筆一檔是為了讓並行寫入不互相覆寫 | | `schedule.log` | 排程條目的輸出。刻意放在存取庫外面:寫進專案會多出未追蹤檔,污染別人的變更盤點 | | `patrol.lock/` | 一輪巡檢的鎖,是目錄——`mkdir` 是原子操作,搶不到就是別人在跑。裡面的 `info` 記 `round`、`pid`、`started` | -| `patrol/` | 本輪巡檢的暫存檔:`section.md` 是要附加的那一節,`newpage.md` 是頁不存在時要建的整頁,`contents.tsv` 是目錄頁那一列 | +| `patrol/` | 本輪巡檢的暫存檔:`latest.md` 是「最新一輪」那一塊,`summary.md` 是摘要那一塊、裡面已經放好本輪這一列,`summary-row.md` 只有那一列,`newpage.md` 是頁不存在時要建的整頁,`contents.tsv` 是目錄頁那一列 | | `usage-prev.tsv` | 上一輪記下來的累計用量。有了它,下一輪的「本輪次數」才算得出來;沒有它的第一輪一律寫「-」,不拿累計冒充本輪 | ## 相關 domain diff --git a/references/behaviors.md b/references/behaviors.md index 510eb64..65d6b93 100644 --- a/references/behaviors.md +++ b/references/behaviors.md @@ -7,7 +7,7 @@ | 項目 | 內容 | | --- | --- | | 觸發時機 | 要啟動助理、要停止助理、要跑一輪巡檢,或要問助理現在還在不在跑、待辦簿剩下哪幾筆時用。四個操作 `start`、`status`、`patrol`、`stop` 都走這一支。排程每一輪叫起來的也是這一支的 `patrol`。執行環境健檢不走這支,走 `jsc-cli:doctor`。技能使用次數不走這支,走 `jsc-log:stats` | -| 關鍵步驟 | 先認出使用者要的是哪一個操作,`patrol` 那一路全程不問人。`start`:先照 `patrol` 的每一步跑完一輪巡檢,第一次心跳由那一輪寫、不另外寫、跑不完就不算啟動、跑 `heartbeat.sh report` 確認 `state=fresh`、跑 `tools/schedule.sh install patrol` 裝巡檢那一筆排程、依結束碼選一段收尾訊息印出——排程接上、排程寫進去了但 cron 沒在跑、排程沒接上三種各一段。心跳那一筆不裝了,`install heartbeat` 一律回 6。`patrol`:跑 `tools/patrol.sh collect` 取鎖並讀四項來源、結束碼 4 就讓開不寫任何東西、結束碼 1 與 3 照樣把那一節寫上監控頁、`hash` 是空的就 `abort`、把 `section_file` 交給 `jsc-gitea:wiki` 附加到 `MONITOR_{HASH}`、頁不存在(唯有結束碼 4)才用 `newpage_file` 建頁、把 `contents_file` 的 `row` 更新到 `MONITOR_CONTENTS` 自己那一列、兩次寫入任一失敗就 `abort` 且不寫心跳、全部寫成才跑 `tools/patrol.sh finish` 寫心跳、最後印出四項結果與待人處理列。`status`:跑 `heartbeat.sh report` 取心跳現況、把 `state` 對映成新鮮、過期、心跳檔損壞、不存在、不自己解析心跳檔也不自己判定、從 `file=` 解出助理目錄後列出 `tasks/` 底下每一個檔案並解析 `state`、`title`、`next_run`、`fail_count`、跑 `tools/schedule.sh status` 取排程現況與週期、印成心跳、排程、待辦三塊、`fail_count` 大於 0 的列標上「已連續失敗 N 次」、心跳與排程兜起來會誤讀的四種組合各補一句話。`stop`:先跑 `heartbeat.sh report` 留下原本的狀態、再跑 `tools/schedule.sh remove all` 移除排程與舊版遺留的心跳條目、最後才跑 `heartbeat.sh clear` 清掉心跳、印出停止訊息並說明心跳清掉之後閘門會擋人、同時說明閘門還沒接線所以現在擋不到人 | -| 外部呼叫 | `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` 三個子命令,七個結束碼各有處置:0 往下走、1 是條目裝了但 cron 沒在跑要照實講不會執行、2 是缺 jsc-hooks 導致門檻讀不到、3 是這台機器沒有排程機制、4 是排程操作失敗要原樣引用 stderr、5 是回讀驗證失敗要叫人自己去看 `crontab -l`、6 是呼叫寫錯,含 `install heartbeat` 與週期塞不進門檻。本 domain 的 `tools/patrol.sh` 的 `collect`、`finish`、`abort` 三個子命令,七個結束碼各有處置: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`,全部只讀,任一項失敗不影響其餘三項。wiki 讀寫一律經 `jsc-gitea:wiki`,技能自己不拼 API 呼叫。crontab 與 schtasks 一律經 `tools/schedule.sh`。另外唯讀 `$JSC_HOME/assistant/tasks/` 底下的檔案。呼叫端沒講清楚要哪一個操作時走 `jsc-ask:ask` 的決策樹問,但 `patrol` 那一路一律不問。不參與閘門判定 | -| 完成條件 | `start` 要那一輪巡檢的 `finish` 回 0 且 `report` 回 `state=fresh`,才算啟動成功;巡檢沒寫成心跳一律回報失敗並停下,不得宣稱啟動;`schedule.sh install patrol` 回 1 要講明條目不會被執行與 `sudo service cron start`,不得宣稱排程會定時執行。`patrol` 要四項各自有 `status`、監控頁附加成功、目錄頁那一列更新成功、`finish` 回 0,才算一輪跑完;`collect` 回 4 是讓開,不算失敗也不寫任何東西;監控頁或目錄頁任一沒寫成就 `abort`,回報「這一輪沒有結果」,心跳一定不寫。`status` 要印出現況表,或印出「助理未運行」並說明原因;心跳不存在、待辦簿目錄不存在、待辦簿零筆、排程沒裝,四種都算正常結束。`stop` 要 `schedule.sh remove all` 先回 0、`clear` 再回 0,並印出帶三段話的停止訊息;`remove` 非 0 就回報排程還在、助理停不掉,不清心跳也不印停止訊息;`clear` 回 5 就回報心跳檔還在、助理沒有確實停掉,不印停止訊息 | -| 可驗證跡象 | `start` 之後 `$JSC_HOME/assistant/heartbeat` 存在,`ts` 是剛才那一輪的時間,`crontab -l` 找得到一筆帶 `# jsc-assist:assistant patrol` 的條目,而且只有一筆,帶 `# jsc-assist:assistant heartbeat` 的舊條目一筆都不剩。`patrol` 跑完之後 wiki 的 `MONITOR_{HASH}` 多一節、節標題帶時間戳、舊的節一字不改,`MONITOR_CONTENTS` 只有自己那一列變動,`$JSC_HOME/assistant/patrol/` 底下有本輪的 `section.md`、`newpage.md`、`contents.tsv`,`$JSC_HOME/assistant/usage-prev.tsv` 換成本輪的累計數,`$JSC_HOME/assistant/patrol.lock` 已經放掉。讓開的那一輪沒有任何寫入跡象。`stop` 之後心跳路徑不存在,`crontab -l` 找不到任何 `# jsc-assist:assistant` 條目。以上都不動別人的排程條目,條目數量前後相同。`status` 無寫入跡象,只有回報內容。四個操作都不動 `tasks/` 底下的檔案,也不動 worktree 與程式碼存取庫。排程的 log 一律在 `$JSC_HOME/assistant/schedule.log`,不落在任何存取庫 | +| 關鍵步驟 | 先認出使用者要的是哪一個操作,`patrol` 那一路全程不問人。`start`:先照 `patrol` 的每一步跑完一輪巡檢,第一次心跳由那一輪寫、不另外寫、跑不完就不算啟動、跑 `heartbeat.sh report` 確認 `state=fresh`、跑 `tools/schedule.sh install patrol` 裝巡檢那一筆排程、把它印的 `allow_rule=` 每一行、環境快照提醒與 `current` 連結缺漏的警告原樣轉給人、依結束碼選一段收尾訊息印出——排程接上、排程寫進去了但 cron 沒在跑、排程沒接上三種各一段。心跳那一筆不裝了,`install heartbeat` 一律回 6。`patrol`:跑 `tools/patrol.sh collect` 取鎖並讀四項來源、結束碼 4 就讓開不寫任何東西、結束碼 1 與 3 照樣把這一輪寫上監控頁、`hash` 是空的就 `abort`、經 `jsc-gitea:wiki` 讀回 `MONITOR_{HASH}` 舊頁、基本資料原樣留著、最新一輪那一塊整塊換成 `latest_file`、`summary_file` 的本輪那一列擺最上面、舊的資料列接在下面並截到 24 列、三塊重組成整頁寫回、頁不存在(唯有結束碼 4)才用 `newpage_file` 建頁、讀不回舊頁就不寫、把 `contents_file` 的 `row` 更新到 `MONITOR_CONTENTS` 自己那一列、兩次寫入任一失敗就 `abort` 且不寫心跳、全部寫成才跑 `tools/patrol.sh finish` 寫心跳、最後印出四項結果與待人處理列。`status`:跑 `heartbeat.sh report` 取心跳現況、把 `state` 對映成新鮮、過期、心跳檔損壞、不存在、不自己解析心跳檔也不自己判定、從 `file=` 解出助理目錄後列出 `tasks/` 底下每一個檔案並解析 `state`、`title`、`next_run`、`fail_count`、跑 `tools/schedule.sh status` 取排程現況與週期、印成心跳、排程、待辦三塊、`fail_count` 大於 0 的列標上「已連續失敗 N 次」、心跳與排程兜起來會誤讀的四種組合各補一句話。`stop`:先跑 `heartbeat.sh report` 留下原本的狀態、再跑 `tools/schedule.sh remove all` 移除排程與舊版遺留的心跳條目、最後才跑 `heartbeat.sh clear` 清掉心跳、印出停止訊息並說明心跳清掉之後閘門會擋人、同時說明閘門還沒接線所以現在擋不到人 | +| 外部呼叫 | 工具一律走 `$JSC_HOME/current/{外掛名}` 那一組不帶版本的路徑:`current/jsc-assist/tools/patrol.sh`、`current/jsc-assist/tools/schedule.sh`、`current/jsc-hooks/hooks/heartbeat.sh`,wiki 那一支是 `current/jsc-gitea/tools/gitea.sh`,`$JSC_HOME` 沒設就退回 `~/.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` 會查 `$JSC_HOME/current/jsc-assist` 與 `$JSC_HOME/current/jsc-gitea` 兩個連結在不在、不在就警告且不代建,會把巡檢的 CLI 用 `command -v` 解成絕對路徑、把 `GITEA_HOST`、`GITEA_TOKEN`、`JSC_HOME`、`JSC_ASSISTANT_HEARTBEAT_TTL` 與所有已設定的 `JSC_WIKI_REPO` 系列快照進條目、條目自帶 `JSC_GITEA_CONFIRM=yes`、並印出這一輪要開的 `allow_rule=` 規則(四支腳本各三種呼叫形式,含 `gitea.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` 上。本 domain 的 `tools/patrol.sh` 的 `collect`、`finish`、`abort` 三個子命令,七個結束碼各有處置: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`,全部只讀,任一項失敗不影響其餘三項。wiki 讀寫一律經 `jsc-gitea:wiki`,技能自己不拼 API 呼叫。crontab 與 schtasks 一律經 `tools/schedule.sh`。另外唯讀 `$JSC_HOME/assistant/tasks/` 底下的檔案。呼叫端沒講清楚要哪一個操作時走 `jsc-ask:ask` 的決策樹問,但 `patrol` 那一路一律不問。不參與閘門判定 | +| 完成條件 | `start` 要那一輪巡檢的 `finish` 回 0 且 `report` 回 `state=fresh`,才算啟動成功;巡檢沒寫成心跳一律回報失敗並停下,不得宣稱啟動;`schedule.sh install patrol` 回 1 要講明條目不會被執行與 `sudo service cron start`,不得宣稱排程會定時執行;回 0 或 1 都要把 `allow_rule=` 各行、「條目含金鑰快照、變數改了要重裝」這句提醒,以及 `current` 連結缺漏的警告轉出去。`patrol` 要四項各自有 `status`、監控頁三塊重組寫成、目錄頁那一列更新成功、`finish` 回 0,才算一輪跑完;`collect` 回 4 是讓開,不算失敗也不寫任何東西;舊頁讀不回來就不寫,回報「這一輪沒有結果」;監控頁或目錄頁任一沒寫成就 `abort`,心跳一定不寫。`status` 要印出現況表,或印出「助理未運行」並說明原因;心跳不存在、待辦簿目錄不存在、待辦簿零筆、排程沒裝,四種都算正常結束。`stop` 要 `schedule.sh remove all` 先回 0、`clear` 再回 0,並印出帶三段話的停止訊息;`remove` 非 0 就回報排程還在、助理停不掉,不清心跳也不印停止訊息;`clear` 回 5 就回報心跳檔還在、助理沒有確實停掉,不印停止訊息 | +| 可驗證跡象 | `start` 之後 `$JSC_HOME/assistant/heartbeat` 存在,`ts` 是剛才那一輪的時間,`crontab -l` 找得到一筆帶 `# jsc-assist:assistant patrol` 的條目,而且只有一筆,帶 `# jsc-assist:assistant heartbeat` 的舊條目一筆都不剩;那一筆條目裡的 CLI 是絕對路徑,前面帶著 `JSC_GITEA_CONFIRM=yes` 與環境變數快照;install 印出的 `allow_rule=` 都是 `$JSC_HOME/current` 那一組確切路徑,沒有萬用字元,也沒有 `Write(...)`。`patrol` 跑完之後 wiki 的 `MONITOR_{HASH}` 只有三塊:基本資料一字未改、最新一輪換成本輪、摘要表最上面一列是本輪且總列數不超過 24,`MONITOR_CONTENTS` 只有自己那一列變動,`$JSC_HOME/assistant/patrol/` 底下有本輪的 `latest.md`、`summary.md`、`summary-row.md`、`newpage.md`、`contents.tsv`,`$JSC_HOME/assistant/usage-prev.tsv` 換成本輪的累計數,`$JSC_HOME/assistant/patrol.lock` 已經放掉。讓開的那一輪沒有任何寫入跡象。`stop` 之後心跳路徑不存在,`crontab -l` 找不到任何 `# jsc-assist:assistant` 條目。以上都不動別人的排程條目,條目數量前後相同。`status` 無寫入跡象,只有回報內容。四個操作都不動 `tasks/` 底下的檔案,也不動 worktree 與程式碼存取庫。排程的 log 一律在 `$JSC_HOME/assistant/schedule.log`,不落在任何存取庫 | diff --git a/skills/assistant/SKILL.md b/skills/assistant/SKILL.md index e58dc92..8683fcc 100644 --- a/skills/assistant/SKILL.md +++ b/skills/assistant/SKILL.md @@ -1,20 +1,37 @@ --- name: assistant -description: Start, inspect, patrol, or stop the background assistant, with jsc-hooks/hooks/heartbeat.sh owning the single freshness verdict, tools/schedule.sh owning the system scheduler, and tools/patrol.sh owning one patrol round. The heartbeat is written by a completed patrol round and by nothing else, so the schedule carries the patrol entry only and its period is derived from the heartbeat TTL; start runs one round and then installs that entry, status turns heartbeat.sh report, schedule.sh status and the task book into one read-only table, stop removes the entry first and then clears the heartbeat. One round reads four independent sources - skill and chain usage, version gaps and the restart gate, SDLC stage and work-package locks, and the heartbeat's own report - and appends the result to wiki MONITOR_{HASH} through jsc-gitea:wiki before tools/patrol.sh finish writes the heartbeat. A round that cannot record its result writes no heartbeat, and a round that starts while the previous one still holds the lock stands down. Use when someone starts, patrols or stops the assistant, or asks whether it is running and what is queued; not for environment health checks (jsc-cli:doctor), not for skill usage counts (jsc-log:stats). +description: 'Start, inspect, patrol or stop the background assistant: jsc-hooks/hooks/heartbeat.sh owns the freshness verdict, tools/schedule.sh the system scheduler, tools/patrol.sh one round. The heartbeat is written by a completed round and by nothing else, so the schedule carries the patrol entry only, its period from the heartbeat TTL; start runs one round then installs that entry - absolute CLI path, environment snapshot, unattended write confirmation, which cron lacks - status prints heartbeat, schedule and task book read-only, stop removes the entry before clearing the heartbeat. One round reads four independent sources - skill and chain usage, version gaps and the restart gate, SDLC stage and work-package locks, and the heartbeat''s own report - then rewrites wiki MONITOR_{HASH} through jsc-gitea:wiki as three fixed blocks: basic data untouched, the latest round replaced whole, a 24-row summary table. A round that cannot record its result writes no heartbeat; one that starts while the previous holds the lock stands down. Use when someone starts, patrols or stops the assistant, or asks whether it runs and what is queued; not for environment health checks (jsc-cli:doctor), not for skill usage counts (jsc-log:stats).' --- # assistant — start, status, patrol, stop The background assistant runs where nobody is watching it. Its heartbeat is the only evidence that it is alive, so this skill is the single entry point for the four operations that touch that evidence: `patrol` writes it, `status` reads it, `stop` clears it, and `start` bootstraps the whole loop. -`jsc-hooks/hooks/heartbeat.sh` owns every heartbeat operation, including the freshness verdict. Never read, parse, write or delete `$JSC_HOME/assistant/heartbeat` directly — one verdict, one source. +`$JSC_HOME/current/jsc-hooks/hooks/heartbeat.sh` owns every heartbeat operation, including the freshness verdict. Never read, parse, write or delete `$JSC_HOME/assistant/heartbeat` directly — one verdict, one source. -`tools/schedule.sh` owns every system-scheduler operation: installing an entry, removing it, and reading which entries exist. Never call `crontab` or `schtasks` from this skill, and never edit a crontab by hand. +`$JSC_HOME/current/jsc-assist/tools/schedule.sh` owns every system-scheduler operation: installing an entry, removing it, and reading which entries exist. Never call `crontab` or `schtasks` from this skill, and never edit a crontab by hand. -`tools/patrol.sh` owns one patrol round: taking the round lock, reading the four sources, composing the monitor-page section, and — after that section is on the page — writing the heartbeat. Never re-read a source this skill already handed to that script, and never compose the section by hand; the script prints the file paths. +`$JSC_HOME/current/jsc-assist/tools/patrol.sh` owns one patrol round: taking the round lock, reading the four sources, composing the monitor page's blocks, and — after the page carries this round — writing the heartbeat. Never re-read a source this skill already handed to that script, and never compose a block by hand; the script prints the file paths. All three flows have fixed inputs and outputs, so all three live in scripts. The task book is the only thing this skill reads for itself, and that is one directory listing. +## Tool paths + +Every tool below is addressed through `$JSC_HOME/current/{plugin}`, and `$JSC_HOME` falls back to `~/.jsc` exactly as everywhere else in this skill: + +| What it does | Path to run | +| --- | --- | +| one patrol round | `$JSC_HOME/current/jsc-assist/tools/patrol.sh` | +| the system scheduler | `$JSC_HOME/current/jsc-assist/tools/schedule.sh` | +| the heartbeat | `$JSC_HOME/current/jsc-hooks/hooks/heartbeat.sh` | +| the wiki, through `jsc-gitea:wiki` | `$JSC_HOME/current/jsc-gitea/tools/gitea.sh` | + +**A `Skill(...)` rule permits invoking that skill and nothing more.** Every Bash call inside it is still checked on its own, so `jsc-gitea:wiki` reaching the wiki depends on `gitea.sh` carrying its own rule — without it the round is refused locally, before any request leaves the machine, and the monitor page never gets written. + +**Never build a tool path out of the base directory the CLI hands you in the skill prompt.** That directory points into the plugin cache and carries a version segment, and the permission gate allows exactly the four paths above and nothing else. A cache path is therefore refused silently: the round stops on a permission prompt nobody can answer, records nothing, writes no heartbeat, and the refusal looks exactly like a broken tool. Read the paths off this table every time — not off the prompt, not off a previous transcript, not off `crontab -l`. + +`current` is a set of version-free links that `jsc-cli:deploy` maintains, so an upgrade moves the cache and leaves these paths alone. When one of them is missing, report the missing link and say `jsc-cli:deploy` has to run; never fall back to a cache path to get the round through, and never create the link here. + ## Pick the operation Run exactly one operation per invocation. Take it from the request: starting, launching or waking the assistant is `start`; asking whether it runs, what it is doing, or what is queued is `status`; running one round, patrolling, or a scheduled wake-up is `patrol`; stopping, halting or shutting it down is `stop`. When the request names none of the four, or names more than one, ask through the `jsc-ask:ask` decision tree with those four as the options, each stating its effect — `start` runs one round and installs the scheduled entry that keeps running rounds, `status` changes nothing, `patrol` runs one round and writes one heartbeat, `stop` removes that entry and deletes the heartbeat. **The one exception: a `patrol` invocation never asks anything at all** (see 界線 1 below). Never guess, and never run a second operation the caller did not ask for. Completion condition: exactly one of `start`, `status`, `patrol`, `stop` is chosen and named in the report. @@ -27,7 +44,7 @@ Run exactly one operation per invocation. Take it from the request: starting, la | `$JSC_HOME/assistant/schedule.log` | nobody here — the scheduled entry appends to it | free text; point the operator at it when a scheduled round misbehaves | | `$JSC_HOME/assistant/tasks/{id}` | this skill, read-only | `key=value` lines, one task per file: `id`, `kind` (`check` / `todo`), `title`, `action`, `trigger`, `recur`, `repo`, `due`, `state` (`pending` / `done` / `paused`), `last_run`, `next_run`, `fail_count`, `origin` (`user` / `assistant`) | | `$JSC_HOME/assistant/patrol.lock/` | `patrol.sh` only | the round lock, a directory. `info` holds `round`, `pid`, `started` | -| `$JSC_HOME/assistant/patrol/` | `patrol.sh` only | one round's scratch files, including `section.md`, `newpage.md` and `contents.tsv` | +| `$JSC_HOME/assistant/patrol/` | `patrol.sh` only | one round's scratch files, including `latest.md`, `summary.md`, `summary-row.md`, `newpage.md` and `contents.tsv` | | `$JSC_HOME/assistant/usage-prev.tsv` | `patrol.sh` only | last recorded round's cumulative usage counts, so the next round can print a real per-round delta | `$JSC_HOME` defaults to `~/.jsc`. `heartbeat.sh report` prints the resolved heartbeat path in its `file=` field, so take the assistant directory from there rather than rebuilding it. @@ -50,7 +67,7 @@ Every call in every operation below is judged by this table. Report the code you ## The scheduler -Nothing in a background assistant runs on its own. The system scheduler is what makes it periodic, and `tools/schedule.sh` is the only thing here that touches it. One job exists, written as exactly one entry carrying the fixed marker `# jsc-assist:assistant patrol`: +Nothing in a background assistant runs on its own. The system scheduler is what makes it periodic, and `$JSC_HOME/current/jsc-assist/tools/schedule.sh` is the only thing here that touches it. One job exists, written as exactly one entry carrying the fixed marker `# jsc-assist:assistant patrol`: | Job | Period | Runs | Installed by `start` | | --- | --- | --- | --- | @@ -69,10 +86,12 @@ Four properties of that script matter enough to state here, because a report tha - **A written entry is not a running entry.** WSL does not start cron by default, and this is the machine's most likely state. Exit 1 from `install` means the entry is on disk and will never fire. Report that as a failure of the start, name `sudo service cron start`, and say it has to be run again after every WSL restart. Never soften exit 1 into "scheduling is set up". - **The log lives at `$JSC_HOME/assistant/schedule.log`**, deliberately outside every repository. Do not offer to move it into a project. - **The entry runs with no human present.** The command is installed with `