feat(role): 精確資訊逐字保留、晨間狀態檢查、排除 skill 注入內容
三件事都源自 2026/07/28-29 實際踩到的問題。 一、整理與濃縮提示詞加「精確資訊逐字保留」規則 原本 role_sleep.sh 只要求「壓縮成 5 行、不超過 400 字」,沒有任何規則保護精確資訊, 導致一則含檔案路徑與網址的記憶被整理成純情感摘要,路徑與網址全部遺失且無法復原。 - role_sleep.sh 新增第 19 條:檔案路徑、網址、指令、環境變數名稱、版本號、識別碼、檔名 一律逐字保留,不得摘要改寫;必要時可超過字數上限;憑證與個資的禁令仍優先 - role_capture.sh 第 8 條同步強化,並釐清與第 7 條「不要整段抄程式碼」的界線 二、新增 --brief 晨間狀態檢查 睡眠時段結束的整點執行使用者自訂檢查腳本,把有變化的結果寫成一則 daily 記憶, 讓角色當天第一次互動就能主動回報(例如 PR 還沒合併、CI 失敗),不必等使用者開口才查。 - 刻意不內建任何檢查邏輯,不假設使用者用 Gitea/GitHub:檢查內容放 ~/.roles/<角色>.checks/*.sh - 目錄不存在時完全不動作,也不安裝排程條目,對沒設定的人零影響 - 只執行有 +x 的 *.sh;無執行權限記警告並略過 - 沒有輸出就不寫記憶(靜默即代表一切正常,不打擾使用者) - 每個腳本受 ROLE_BRIEF_TIMEOUT 限制,輸出受 ROLE_BRIEF_EACH_LIMIT/ROLE_BRIEF_LIMIT 截斷 - 腳本輸出視為外部資料,寫入前一律經 transcript.js redact 遮蔽 - install_cron/remove_cron/show_status 一併支援;附 examples/check-gitea-prs.sh 範例 三、對話交接排除 skill 等注入內容 實測發現載入一個 skill 會插入一筆 isMeta 的 user 訊息(長度可達兩萬字元), 被誤認為使用者發言,既吃光字元預算也讓輪數計算失真(29 輪虛胖,實際 26 輪)。 - transcript.js 新增 isMetaEntry():以 isMeta 與 sourceToolUseID 判斷注入內容 - isRealUserMessage() 與 dialogLines() 皆排除,連帶讓 Stop hook 的本輪抽取更準確 驗證:晨間檢查涵蓋有輸出/無輸出/無執行權限/逾時/含機密五種腳本, 確認遮蔽生效、逾時腳本未納入、無權限腳本未執行、全部無輸出時不寫記憶、 ROLE_BRIEF_ENABLED=0 可停用;對話交接確認 skill 內容已排除且 8 則全為真實使用者發言。 版號 0.0.3 → 0.0.4 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 用途:晨間狀態檢查範例 —— 列出 Gitea 上仍開啟中的 PR,讓角色早上能主動提醒。
|
||||
# 更新時間:2026/07/29 09:05:00
|
||||
# 相依:bash、curl、node(不使用 jq)。
|
||||
#
|
||||
# 安裝:複製到 ~/.roles/<角色 ID>.checks/ 並加上執行權限,然後重跑 --install-cron
|
||||
# cp check-gitea-prs.sh ~/.roles/YUI01.checks/
|
||||
# chmod +x ~/.roles/YUI01.checks/check-gitea-prs.sh
|
||||
#
|
||||
# 設定(環境變數,需寫進 crontab 或 ~/.roles 的環境,cron 沒有互動 shell 的變數):
|
||||
# GITEA_HOST Gitea 站台,例如 https://gitea.example.com
|
||||
# GITEA_TOKEN 存取權杖(本腳本不會輸出它;晨間檢查寫入記憶前仍會再遮蔽一次)
|
||||
# GITEA_REPOS 逗號分隔的 owner/repo 清單,例如 plugins/generic,plugins/code
|
||||
#
|
||||
# 慣例:**沒有需要回報的事情就不要輸出任何內容**。晨間檢查只在有輸出時才寫記憶,
|
||||
# 靜默即代表「一切正常,不必打擾使用者」。
|
||||
# ==============================================================================
|
||||
|
||||
set -u
|
||||
|
||||
HOST="${GITEA_HOST:-}"
|
||||
TOKEN="${GITEA_TOKEN:-}"
|
||||
REPOS="${GITEA_REPOS:-}"
|
||||
|
||||
# 設定不全就安靜結束:晨間檢查不該因為沒設定而每天產生雜訊
|
||||
[ -n "$HOST" ] && [ -n "$TOKEN" ] && [ -n "$REPOS" ] || exit 0
|
||||
command -v curl >/dev/null 2>&1 || exit 0
|
||||
command -v node >/dev/null 2>&1 || exit 0
|
||||
|
||||
lines=""
|
||||
|
||||
IFS=','
|
||||
for repo in $REPOS; do
|
||||
repo="$(printf '%s' "$repo" | tr -d '[:space:]')"
|
||||
[ -n "$repo" ] || continue
|
||||
|
||||
body="$(curl -sS --max-time 15 \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
"${HOST}/api/v1/repos/${repo}/pulls?state=open&limit=20" 2>/dev/null)" || continue
|
||||
[ -n "$body" ] || continue
|
||||
|
||||
summary="$(printf '%s' "$body" | node -e '
|
||||
let raw = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => { raw += chunk; });
|
||||
process.stdin.on("end", () => {
|
||||
let data;
|
||||
try { data = JSON.parse(raw); } catch { return; }
|
||||
if (!Array.isArray(data) || !data.length) return;
|
||||
const repo = process.argv[1];
|
||||
for (const pr of data) {
|
||||
// mergeable 為 false 通常代表有衝突或未過檢查,值得在早上提醒
|
||||
const blocked = pr.mergeable === false ? ",有衝突或未過檢查" : "";
|
||||
console.log(`${repo} PR #${pr.number}:${pr.title}(${pr.head?.ref ?? "?"} → ${pr.base?.ref ?? "?"}${blocked})`);
|
||||
}
|
||||
});
|
||||
' "$repo" 2>/dev/null)"
|
||||
|
||||
[ -n "$summary" ] && lines="${lines}${summary}
|
||||
"
|
||||
done
|
||||
unset IFS
|
||||
|
||||
# 有開啟中的 PR 才輸出;全部合併完畢就靜默
|
||||
if [ -n "$lines" ]; then
|
||||
printf '尚未合併的 PR:\n%s' "$lines"
|
||||
fi
|
||||
Reference in New Issue
Block a user