Compare commits

...

3 Commits

Author SHA1 Message Date
Leo ebe71e3283 fix: 三支閘印出來的 ticket 一律絕對路徑,不叫到 InkStoneCo 舊複本(inkstone/ISEP#113)
dispatch-format-guard.sh 的出路 `scripts/ticket say`、factory-idle-guard.sh 主閘與
副閘的 `scripts/ticket where` 原本印相對路徑。相對路徑是對貼上去那個人的 cwd 解析的,
而收工方 cwd 常是 InkStoneCo,那裡的 scripts/ticket 是 2026-08-27 之前的舊複本
(grep -c subtask → 0)⇒ 照著貼會叫到舊的、什麼都不會發生(同 ISEP#112 的病)。

兩支都改成從 ${CLAUDE_PLUGIN_ROOT} 解出絕對路徑再印(沿用 baton-handback-guard 的做法;
baton 本輪未動,早已是絕對路徑)。dispatch 用既有的佔位符替換機制加 __TICKET__;
factory 是 POSIX sh、unquoted heredoc,直接展開 $TICKET,並用 baton 的引號慣例包住路徑。

測試各加一條在驗這件事:
- dispatch-format-guard.test.sh ⑰ 改成驗絕對路徑+確認沒有相對的 scripts/ticket(52/52)
- factory-idle-guard.test.sh 加主閘訊息實跑 + 掃全支源碼兩條(33→35)
- baton 既有 24/24 不動

版本待總管定(plugin.json 未動)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGxitYq49FzYC7EFkbhzF5
2026-09-18 17:45:46 +00:00
Leo 8a3802c4b7 release: v0.30.10——雲端環境產物分檔,開機腳本不再混進分隔線
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:16:19 +08:00
Leo 09769a0dd1 雲端環境產物拆成兩個檔:變數檔只有 KEY=VALUE、開機腳本檔逐位元組=cloud-setup-script.sh(+README)
2026-09-18 實撞:兩段同檔、「════」分隔線被一起貼進 Setup script 欄 ⇒ 雲端開機 exit 127,
cloud-worker 每一趟在開機就死(cse_013LPaaD47m4arFzLjGvSZnN)。新增 V3(變數檔每行都是 KEY=VALUE)、
V4(開機腳本檔逐位元組相同);test-make-cloud-env.sh 15/15。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:16:19 +08:00
9 changed files with 88 additions and 22 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "isep",
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:62 支機械閘(88 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、8 位有名字的工人(agents/,見 docs/governance/worker-roster.md)、65 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
"version": "0.30.9",
"version": "0.30.10",
"keywords": [
"inkstone",
"guardrails",
+4 -1
View File
@@ -1186,7 +1186,10 @@ bash hooks/tests/dispatch-format-guard.test.sh
交件方式、不准 push main、org 是 `inkstone` 這些不必有人記得寫,機器每次都補。
它壞了不會有人立刻發現——派工照樣送出去,只是收工方**不知道要貼回原票**
- ⑨ 紅 ⇒ 真跡放行了。那份測資是**真的發生過的那一次派工**(見 `hooks/tests/fixtures/README.md`
- ⑰ 紅 ⇒ 訊息被 shell 展開了(同 A9 ⑩b 那個病:閘照擋,但它教人怎麼解的那兩行變成空白)
- ⑰ 紅 ⇒ 訊息被 shell 展開了(同 A9 ⑩b 那個病:閘照擋,但它教人怎麼解的那兩行變成空白)
**或出路又印回相對路徑的 `scripts/ticket`**`inkstone/ISEP#113`):收工方 cwd 常是
InkStoneCo,那裡的 `scripts/ticket` 是 2026-08-27 之前的舊複本,相對路徑貼過去會叫到它、
什麼都不會發生——同 comment-carries ⑮、`baton-handback-guard`。出路要印**絕對路徑**的 ticket
- **D 群⑲⑳㉑㉒ 紅 ⇒ `inkstone/ISEP#88` 的驗收條件沒過**:回覆一個正在跑的 subagent
時夾帶指令沒被擋(⑲)、或擋了卻沒把那段內容原文印出來(㉑,出路是「貼上票」,
找不到原文就得回頭自己翻)
+9
View File
@@ -11,6 +11,15 @@
其中 **3 支檔案存在但沒被掛上**(2 支是待人填的空範本、1 支是刻意留著沒開的止血帶,見下面「未生效」表)。
下面按「你會在什麼時候撞到它」分組,一支一行。
> 📌 **`inkstone/ISEP#113`2026-09-18,待總管定版)+0 支、+0 條、腳本不變**:沒加閘、沒加腳本。
> `dispatch-format-guard.sh`(出路 `scripts/ticket say`)與 `factory-idle-guard.sh`(主閘+副閘的 `scripts/ticket where`
> 印出來的 ticket 指令改成**絕對路徑**`${CLAUDE_PLUGIN_ROOT}/scripts/ticket`)——收工方 cwd 常是 InkStoneCo
> 那裡的 `scripts/ticket` 是 2026-08-27 之前的舊複本,相對路徑貼過去會叫到它、什麼都不會發生
> (同 `comment-carries-task-guard` ⑮/`baton-handback-guard``inkstone/ISEP#112`)。`baton-handback-guard` 早已是絕對路徑,本輪未動。
> 兩支的測試各加一條在驗這件事:`dispatch-format-guard.test.sh` ⑰(52/52)、`factory-idle-guard.test.sh`33→35)。
> **62 支、88 條、66 支腳本是在這棵樹上當場數出來的**`ls hooks/*.sh | wc -l`62、
> `grep -c '"command":' hooks/hooks.json`88、`ls -p scripts | grep -v / | wc -l`66)。
> 📌 **`inkstone/ISEP#159`2026-09-18,待總管定版)+0 支、+0 條、腳本 65→66**:沒加閘。`scripts/ticket` 多兩個動詞
> `triage`(沒 s/* 的票給狀態+留說明,或當重複關掉並指回去)與 `compose`(把已分診的票組成 InkStoneCo 里程碑:載體+前置,
> 先全部驗過才寫);`pick --pool` 多兩層(還沒到期的 InkStoneCo 里程碑的前置、最後是待分診的票),每一列帶 `next`。
+7 -2
View File
@@ -83,6 +83,10 @@ set -u
INPUT="$(cat)"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
# 訊息裡的出路一律印絕對路徑:收工方的 cwd 常是 InkStoneCo,那裡的 scripts/ticket 是
# 2026-08-27 之前的舊複本,照著相對路徑貼會叫到它(同 comment-carries-task-guard ⑮、
# baton-handback-guardinkstone/ISEP#112#113)。
TICKET="${CLAUDE_PLUGIN_ROOT:-$(cd "$HERE/.." && pwd)}/scripts/ticket"
FACTS="$(printf '%s' "$INPUT" | python3 "$HERE/lib/dispatch_parse.py" dispatch 2>/dev/null)"
[ -n "$FACTS" ] || exit 0 # parser 掛了 → fail-open(回到「沒有這支閘」的狀態)
@@ -151,7 +155,7 @@ __CARRY__
**本閘每次派工都會自動注入給收工方**,你不必寫、也不會漏。
只有這次 ⇒ **寫進那張票**(main 是哪顆、今天撞過什麼、另一條線在動什麼)
`scripts/ticket say <owner/repo#N> -F <檔>`
`"__TICKET__" say <owner/repo#N> -F <檔>`
然後派工單寫 `【工單】<owner/repo#N> → comment <剛拿到的號碼>`
🔴 **「票上還沒有」不是把它寫進 prompt 的理由——它就是「去把它寫上票」的指令。**
@@ -207,13 +211,14 @@ if t:
fi
DFG_MSG="$MSG" DFG_VIOL="$VIOL_TEXT" DFG_SESSION="$SESSION" \
DFG_HEAD="$HEADLINE" DFG_CARRY="$CARRY_TEXT" python3 -c '
DFG_HEAD="$HEADLINE" DFG_CARRY="$CARRY_TEXT" DFG_TICKET="$TICKET" python3 -c '
import os, sys
sys.stderr.write(
os.environ["DFG_MSG"]
.replace("__HEADLINE__", os.environ.get("DFG_HEAD", ""))
.replace("__VIOLATIONS__", os.environ.get("DFG_VIOL", ""))
.replace("__CARRY__", os.environ.get("DFG_CARRY", ""))
.replace("__TICKET__", os.environ.get("DFG_TICKET", "") or "scripts/ticket")
.replace("__SESSION__", os.environ.get("DFG_SESSION", "") or "<session_id>")
+ "\n"
)
+7 -3
View File
@@ -82,6 +82,10 @@
set -eu
PROJ="${CLAUDE_PROJECT_DIR:-$(pwd)}"
# 訊息裡的出路一律印絕對路徑:收工方的 cwd 常是 InkStoneCo,那裡的 scripts/ticket 是
# 2026-08-27 之前的舊複本,照著相對路徑貼會叫到它(同 comment-carries-task-guard ⑮、
# baton-handback-guardinkstone/ISEP#112#113)。$0 是本檔路徑,往上一層就是 plugin 根。
TICKET="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}/scripts/ticket"
PAYLOAD=$(cat 2>/dev/null || echo '{}')
# ── 主閘:宣告了下一步,有沒有交出派工憑證?────────────────────────
@@ -263,8 +267,8 @@ leo 2026-08-16(本閘的規格):
━━ 現在怎麼過這道閘(四條,程式碼裡都真的有對應的分支)━━━━━━━━━
① **現在就按 trigger**:這個回合直接呼叫 AgentTask 派出去。
沒有票 → 先 \`scripts/ticket where <關鍵字>\` 搜該掛哪張,
再 \`ticket say <owner/repo#N> -F <檔>\`,然後帶【工單】派工。
沒有票 → 先 \`"$TICKET" where <關鍵字>\` 搜該掛哪張,
再 \`"$TICKET" say <owner/repo#N> -F <檔>\`,然後帶【工單】派工。
② **它已經在別人手上**:把票號寫進這段收尾文字(例:「已派給 inkstone/ISEP#30」)。
有 \`#數字\` 就放行——那不是「我等下做」,是「已經有人在做」。
③ **它其實不該現在做**:寫一行說明它在等什麼,格式就是這一個標記——
@@ -318,7 +322,7 @@ leo 2026-08-10:「你的任務是**維護 loop**,你要去拿任務派任務
**全工廠停工你要發現,這是警訊**。」
【現在該做的,不是回報,是派工】
1. 撈池子當場撈:\`scripts/ticket where <關鍵字>\`(或 labels=s/todo
1. 撈池子當場撈:\`"$TICKET" where <關鍵字>\`(或 labels=s/todo
2. 照 CP 排序:\`system-dev/docs/3-specs/critical-paths/ship.md\` 是有序的六步
3. 一次派 2–4 件平行跑;同一個 repo 不要塞兩條(會搶工作區)
4. agent 死於環境錯誤(SSL/API)⇒ **那是要重派的訊號,不是完工**
+9 -3
View File
@@ -216,13 +216,19 @@ fi
# 迴歸:訊息裡的反引號一度在別支閘被 shell 當成命令替換執行掉
# 2026-08-26 ask-user-question-guard:閘照擋,但**它教人怎麼解的那兩行變成空白**)
# ⑰ 兩件事一起驗(都會讓「照著貼」失敗):
# ① 反引號沒被 shell 當命令替換執行掉(出路那兩行沒變空白)——原本這條的用意
# ② 出路印的是**絕對路徑**的 ticket,不是相對的 `scripts/ticket`inkstone/ISEP#113):
# 收工方 cwd 常是 InkStoneCo,那裡的 scripts/ticket 是舊複本(`grep -c subtask` → 0
# ⇒ 相對路徑貼過去會叫到舊的、什麼都不會發生(同 comment-carries ⑮、baton 那條)
N=$((N+1))
if printf '%s' "$msg" | grep -q 'scripts/ticket say' \
if printf '%s' "$msg" | grep -qE '/scripts/ticket" say' \
&& ! printf '%s' "$msg" | grep -qE '`scripts/ticket' \
&& printf '%s' "$msg" | grep -q 'touch /tmp/.dispatch-ok-S-TEST' \
&& ! printf '%s' "$msg" | grep -qi 'is a directory\|command not found'; then
printf ' ✅ ⑰ 訊息原文照印反引號沒被當命令執行,出路那兩行沒有變空白\n'; PASS=$((PASS+1))
printf ' ✅ ⑰ 訊息原文照印反引號沒被執行)+出路的 ticket 是絕對路徑,沒有相對的 scripts/ticketISEP#113\n'; PASS=$((PASS+1))
else
printf ' ❌ ⑰ 訊息被 shell 展開了(指令消失,或冒出 shell 錯誤\n'
printf ' ❌ ⑰ 訊息被 shell 展開,或出路又印回相對路徑的 scripts/ticketInkStoneCo 那份是舊複本\n'
printf '%s\n' "$msg" | sed -n '1,20p' | sed 's/^/ /'
FAIL=$((FAIL+1))
fi
+29
View File
@@ -181,6 +181,35 @@ echo "── 出路③ 的反面:沒寫標記就照樣擋(不准靠刪掉那
t 2 "同一句話拿掉 ⏸ 等: 標記 ⇒ 擋" \
'T:那批 workflow 要重打 bundle。我等你確認,這件事我不自己動。'
echo
echo "── 出路裡的 ticket 是絕對路徑,不是相對的 scripts/ticketinkstone/ISEP#113)─"
# 收工方的 cwd 常是 InkStoneCo,那裡的 scripts/ticket 是 2026-08-27 之前的舊複本
# `grep -c subtask` → 0)⇒ 相對路徑貼過去會叫到它、什麼都不會發生(同 comment-carries ⑮、
# baton-handback)。這兩條分別驗「主閘印出來的那塊」與「整支閘的訊息裡再也沒有相對路徑」。
N=$((N+1))
mk "$TMP/tr-path.jsonl" 'T:我看完了。下一步我去把那支閘的誤攔補上。'
pmsg=$(printf '{"transcript_path":"%s"}' "$TMP/tr-path.jsonl" \
| CLAUDE_PROJECT_DIR="$TMP" sh "$HOOK" 2>&1)
if printf '%s' "$pmsg" | grep -qE '/scripts/ticket" where' \
&& ! printf '%s' "$pmsg" | grep -qE '`scripts/ticket'; then
printf ' ✅ 主閘出路的 ticket 印絕對路徑,沒有相對的 scripts/ticket\n'; PASS=$((PASS+1))
else
printf ' ❌ 主閘出路又印回相對的 scripts/ticket ⇒ 在 InkStoneCo cwd 貼上去會叫到舊複本\n'
printf '%s\n' "$pmsg" | grep -n 'ticket' | sed 's/^/ /'
FAIL=$((FAIL+1))
fi
# 副閘(全廠停工那塊)在離線測試裡拿不到 gitea token、走不到,所以直接掃這支閘的原始碼:
# 印出來的每一處 ticket 指令都不准是相對路徑(backtick 直接接 scripts/ticket)。
N=$((N+1))
if grep -n '`scripts/ticket' "$HOOK" >/dev/null 2>&1; then
printf ' ❌ 這支閘的訊息裡還有相對路徑的 scripts/ticket(副閘那塊漏改了)\n'
grep -n '`scripts/ticket' "$HOOK" | sed 's/^/ /'
FAIL=$((FAIL+1))
else
printf ' ✅ 整支閘的訊息裡沒有任何相對路徑的 scripts/ticket(主閘+副閘都改了)\n'; PASS=$((PASS+1))
fi
echo
echo "── 壞輸入不准亂擋(fail-open)────────────────────────────────"
+16 -11
View File
@@ -143,13 +143,12 @@ SETUP="$(cd "$(dirname "$0")/.." && pwd)/docs/cloud-setup-script.sh"
[ -f "$SETUP" ] || { echo "🔴 找不到 $SETUP" >&2; exit 1; }
MISSING=()
# 🔴 兩個檔分開產(2026-09-18 實撞):原本兩段放同一檔、中間用「════」分隔,
# leo 把分隔線一起貼進 Setup script 欄 ⇒ 雲端開機 exit 127「'════…': command not found」,
# 每一趟 cloud-worker 在開機就死。⇒ 變數一個檔(只有 KEY=VALUE)、開機腳本一個檔(逐位元組=cloud-setup-script.sh),
# **兩個檔都整份貼**,沒有任何要人自己裁切的地方。
SETUP_OUT="${OUT%.txt}-setup-script.sh"
{
echo "claude.ai → Cloud environments → 你的環境。下面兩塊各自貼進對應欄位。"
echo "版本:${VER}${VER_DATE})——${VER_WHY}"
echo "${PREV_VER} 多:${ADDED:-(無)}|少:${REMOVED:-(無)}"
echo "產生時間:$(date '+%Y-%m-%d %H:%M')"
echo
echo "════════ ① Environment variables(一行一個,名字與值分開填)════════"
for n in "${NEEDED[@]}"; do
if v=$(lookup "$n"); then
echo "$n=$v"
@@ -159,13 +158,19 @@ MISSING=()
fi
done
for kv in "${FIXED[@]}"; do echo "$kv"; done
echo
echo "════════ ② Setup script(整段貼,一字不改)════════"
cat "$SETUP"
} > "$OUT"
chmod 600 "$OUT"
cp "$SETUP" "$SETUP_OUT"
chmod 600 "$OUT" "$SETUP_OUT"
{
echo "claude.ai → Cloud environments → 你的環境(${VER}${VER_DATE}${VER_WHY}"
echo "${PREV_VER} 多:${ADDED:-(無)}|少:${REMOVED:-(無)}"
echo "① Environment variables 欄:整份貼 $(basename "$OUT")"
echo "② Setup script 欄:整份貼 $(basename "$SETUP_OUT")(一字不改,不要加任何東西)"
echo "貼完刪掉這三個檔。"
} > "${OUT%.txt}-README.txt"
echo "✅ 產生完成:$OUT"
echo " 版本:${VER}(比 ${PREV_VER} 多:${ADDED:-}|少:${REMOVED:-}"
echo " 變數 ${#NEEDED[@]} 個|找不到值的 ${#MISSING[@]}${MISSING[*]:-}"
echo " 🔴 這個檔含金鑰真身:貼完就刪(rm '$OUT'),它刻意不在任何 repo 裡。"
echo " 開機腳本:$SETUP_OUT"
echo " 🔴 含金鑰真身:貼完就刪(rm '${OUT%.txt}'*),它刻意不在任何 repo 裡。"
+6 -1
View File
@@ -61,7 +61,12 @@ printf '%s\n' "$OUT_LINE" | grep -q "找不到值的 0 個" \
&& ok "A4 收尾那行說「找不到值的 0 個」" || bad "A4 收尾計數不對:$OUT_LINE"
# 版本(leo 2026-09-18):產物檔頭帶現行版本號;清單改了沒升版要拒絕
grep -q "^版本:v[0-9]" "$F" && ok "V1 產物檔頭寫著版本號" || bad "V1 產物檔頭沒有版本號"
grep -q "v[0-9]" "${F%.txt}-README.txt" && ok "V1 README 寫著版本號" || bad "V1 README 沒有版本號"
# 2026-09-18 實撞:兩段同檔、分隔線被貼進 Setup script 欄 ⇒ 雲端開機 exit 127。分檔後兩件事要守住:
bad_lines=$(grep -cvE '^[A-Z][A-Z0-9_]*=' "$F")
chk 0 "$bad_lines" "V3 變數檔每一行都是 KEY=VALUE(沒有分隔線、說明文字)"
cmp -s "${F%.txt}-setup-script.sh" "$(cd "$(dirname "$S")/.." && pwd)/docs/cloud-setup-script.sh" \
&& ok "V4 開機腳本檔與 docs/cloud-setup-script.sh 逐位元組相同" || bad "V4 開機腳本檔被加料了"
VT="$(cd "$(dirname "$S")/.." && pwd)/docs/cloud-env-versions.tsv"; VB="$VT.bak-test"
cp "$VT" "$VB"; printf 'v999\t2099-01-01\tNOPE_ONLY\t測試用\n' >> "$VT"
INKSTONE_ROOT="$FAKE" OUT_DIR="$OUTD" bash "$S" >/dev/null 2>&1; rc=$?