Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cd3af2764 | |||
| 2fb18e9f48 | |||
| aec7f3a980 | |||
| 34e7c45eb4 | |||
| 500b95d80d | |||
| 7b96f238b6 | |||
| 627f924342 | |||
| 5c3dea6e6c | |||
| c5e82c2617 | |||
| 065bb8f729 |
@@ -2,12 +2,17 @@
|
|||||||
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
||||||
"name": "inkstone",
|
"name": "inkstone",
|
||||||
"description": "InkStoneCo 自用的 Claude Code 環境",
|
"description": "InkStoneCo 自用的 Claude Code 環境",
|
||||||
"owner": { "name": "Leo" },
|
"owner": {
|
||||||
|
"name": "Leo"
|
||||||
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "isep",
|
"name": "isep",
|
||||||
"description": "InkStone Environment Plugin —— 機械閘/command/skill/腳本的唯一真相源",
|
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:42 支機械閘(52 條註冊)、7 支 slash command、2 支 skill、25 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
|
||||||
"author": { "name": "Leo" },
|
"author": {
|
||||||
|
"name": "Leo",
|
||||||
|
"url": "https://uncle6.me"
|
||||||
|
},
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"source": "./"
|
"source": "./"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "isep",
|
"name": "isep",
|
||||||
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:42 支機械閘、7 支 slash command、2 支 skill、24 支腳本。本機與雲端裝同一份。",
|
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:42 支機械閘(52 條註冊)、7 支 slash command、2 支 skill、25 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
|
||||||
"version": "0.0.0",
|
"version": "0.2.0",
|
||||||
"keywords": ["inkstone", "guardrails", "hooks", "gitea", "arcrun"]
|
"keywords": [
|
||||||
|
"inkstone",
|
||||||
|
"guardrails",
|
||||||
|
"hooks",
|
||||||
|
"gitea",
|
||||||
|
"arcrun"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Leo",
|
||||||
|
"url": "https://uncle6.me"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# 雲端 session 怎麼載到 ISEP —— inkstone/ISEP#5
|
||||||
|
|
||||||
|
對照 `docs/governance/sdd-gitea-governance.md` §11.3「載入契約」三條硬規則
|
||||||
|
(L11.3.1/L11.3.2/L11.3.3)與其驗收標準(L11.3.4:能貼出雲端實際載到的清單,
|
||||||
|
逐條對上 ISEP 的註冊條數)。本檔記錄機制、實測結果、還缺什麼。
|
||||||
|
|
||||||
|
## 舊模型死在哪(不要重蹈)
|
||||||
|
|
||||||
|
`InkStoneCo/.claude/cloud-shell/`(`generate-shell-payload.py` + 薄殼)的模型是:
|
||||||
|
真身 hook/command/skill → 產生器跑出「薄殼該長的樣子」→ 人推一份**複製本**進
|
||||||
|
GitHub 私 repo `youlinhsieh/inkstoneco`。這條鏈上兩個環節都「要有人記得」:
|
||||||
|
|
||||||
|
- 忘了重跑產生器 → 產物落後真身
|
||||||
|
- 產物沒推 → 薄殼落後產物
|
||||||
|
|
||||||
|
`inkstone/InkStoneCo#57` 的實測:薄殼比真身少 7 支閘,其中兩支才立一天。
|
||||||
|
`#14` 更早:雲端 33 支閘一支都沒生效。**兩次同一個病**:任何「複製一份」的設計,
|
||||||
|
新鮮度只能靠人記得,而人會忘。
|
||||||
|
|
||||||
|
## 新機制:讓 Claude Code 自己的 plugin marketplace 去裝真身
|
||||||
|
|
||||||
|
不做複製,改用 Claude Code 原生支援、且經官方文件證實可行的路徑:
|
||||||
|
|
||||||
|
1. **ISEP 本身已經是一個合法 plugin**(`.claude-plugin/plugin.json` +
|
||||||
|
`.claude-plugin/marketplace.json`,另一張票的產物),hook 一律用
|
||||||
|
`${CLAUDE_PLUGIN_ROOT}`,不寫死路徑。
|
||||||
|
2. Cloud environment 的 **Setup script**(code-on-web 原生功能,
|
||||||
|
在 Claude Code 啟動**之前**跑,跑在同一台會被拍成快照的 VM 上)裡跑:
|
||||||
|
```
|
||||||
|
claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git --scope user
|
||||||
|
claude plugin install isep@inkstone --scope user
|
||||||
|
```
|
||||||
|
這兩行**不是複製**——跟本機 `claude plugin install` 是同一條路徑,裝的內容
|
||||||
|
100% 來自 ISEP 這個 repo 的 HEAD,沒有第二份、沒有產生器、沒有「子集」。
|
||||||
|
3. Setup script 跑完,Anthropic 把整個檔案系統(含 `~/.claude/plugins/`)拍成快照,
|
||||||
|
之後每個新 session 直接沿用快照,**在 Claude Code 啟動當下**(不是「clone 完才補」)
|
||||||
|
plugin 就已經在磁碟上——`command`/`skill` 啟動時的目錄掃描掃得到,不再是
|
||||||
|
`#14` 那個「hook 可以晚到、skill/command 不行」的破口(見 L11.3.2)。
|
||||||
|
|
||||||
|
私有 repo 的認證:不把 token 寫進任何檔案,改用官方文件建議的 CI/CD 寫法——
|
||||||
|
用 `GITEA_TOKEN_CLAUDE_CODE`(既有機器帳號 token,`InkStoneCo#14` 已建立的同一把,
|
||||||
|
沒有新造)在 Setup script 裡做一次 git URL 重寫:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git config --global url."https://x-access-token:${GITEA_TOKEN_CLAUDE_CODE}@git.uncle6.me/".insteadOf \
|
||||||
|
"https://git.uncle6.me/"
|
||||||
|
```
|
||||||
|
|
||||||
|
`marketplace add` 用乾淨網址(不帶 token),認證完全交給上面那條重寫,
|
||||||
|
所以 `known_marketplaces.json` 裡存的來源網址也不帶 token
|
||||||
|
(本機實測驗過,見下面「已驗」第 2 輪)。
|
||||||
|
|
||||||
|
完整腳本:`docs/cloud-setup-script.sh`(貼進 code-on-web 的 Setup script 欄位用)。
|
||||||
|
|
||||||
|
## 已驗(本機,隔離環境,不影響本機正在跑的任何 session)
|
||||||
|
|
||||||
|
🔴 **怎麼保證沒有干擾**:全程把 `$HOME` 指到 scratchpad 底下的隔離目錄
|
||||||
|
(`isep-test-home`/`isep-test-home2`),從未寫到真正的 `~/.claude/`,
|
||||||
|
也沒有動到 `InkStoneCo/.claude/` 那份舊設定。兩者互不相干,
|
||||||
|
本機目前跑著的其他 session/agent 全程沒受影響。
|
||||||
|
|
||||||
|
**第 1 輪**(token 直接嵌在 marketplace URL 裡,沿用 ISEP 這個 git checkout
|
||||||
|
本來就有的、已解析好的 origin 憑證——不是我另外造的憑證,是既有機制解析出來的那份):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ claude plugin marketplace add "https://claude-code:<token>@git.uncle6.me/inkstone/ISEP.git" --scope user
|
||||||
|
Adding marketplace…Refreshing marketplace cache (timeout: 120s)…
|
||||||
|
Cloning repository (timeout: 120s): https://***:***@git.uncle6.me/inkstone/ISEP.git
|
||||||
|
Clone complete, validating marketplace…
|
||||||
|
✔ Successfully added marketplace: inkstone (declared in user settings)
|
||||||
|
|
||||||
|
$ claude plugin install isep@inkstone --scope user
|
||||||
|
Installing plugin "isep@inkstone"...✔ Successfully installed plugin: isep@inkstone (scope: user)
|
||||||
|
|
||||||
|
$ claude plugin list
|
||||||
|
Installed plugins:
|
||||||
|
❯ isep@inkstone
|
||||||
|
Version: 0.0.0
|
||||||
|
Scope: user
|
||||||
|
Status: ✔ enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
**第 2 輪**(重跑一次,改用實際要交付的「乾淨 URL + git config url.insteadOf 重寫」
|
||||||
|
寫法,驗證 §建議腳本 那段真的可行,而不是理論上可行):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git config --global url."https://x-access-token:<token>@git.uncle6.me/".insteadOf "https://git.uncle6.me/"
|
||||||
|
$ claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git --scope user
|
||||||
|
✔ Successfully added marketplace: inkstone (declared in user settings)
|
||||||
|
$ claude plugin install isep@inkstone --scope user
|
||||||
|
✔ Successfully installed plugin: isep@inkstone (scope: user)
|
||||||
|
|
||||||
|
$ cat ~/.claude/plugins/known_marketplaces.json
|
||||||
|
{
|
||||||
|
"inkstone": {
|
||||||
|
"source": { "source": "git", "url": "https://git.uncle6.me/inkstone/ISEP.git" },
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
⇒ 存在磁碟上的 marketplace 來源紀錄**不帶 token**——符合 L11.3.3「憑證只准取名字」。
|
||||||
|
|
||||||
|
**逐條對照**(`claude plugin details isep@inkstone` 的輸出 + 直接數快取目錄裡的檔案,
|
||||||
|
對照 ISEP 這次測試當下的 Gitea `main` HEAD):
|
||||||
|
|
||||||
|
| | 裝到本機隔離環境的 | ISEP `main` 當下的來源 | 對上了嗎 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Skills | 9(`cp-write`/`deep-recall`/`issue-handle`/`sdd-check`/`ship-check`/`wiki-capture`/`wiki-init`/`wiki-recall`/`wiki-update`) | `commands/` 7 支 + `skills/` 2 支 = 9 | ✅ 逐支比對名稱一致 |
|
||||||
|
| Commands 目錄 | 7 個 `.md` | 7 個 `.md` | ✅ `diff` 兩邊檔名清單完全一致 |
|
||||||
|
| Skills 目錄 | `deep-recall`/`ship-check` 2 個 | 同 | ✅ |
|
||||||
|
| Hook 腳本(`hooks/*.sh` 實體檔) | 42 支 | 42 支 | ✅ `ls` 兩邊都是 42 |
|
||||||
|
| `hooks.json` 裡註冊的 hook 腳本路徑(去重) | 39 支唯一路徑 | 39 支 | ✅ `diff` 兩邊 grep 結果完全一致 |
|
||||||
|
|
||||||
|
(42 支實體檔 vs 39 支被 `hooks.json` 引用:差的 3 支是 `hooks.json` 自己
|
||||||
|
+ `pre-write-guard.sh`/`pre-write-guard.template.sh` 這類非直接掛註冊的輔助檔,
|
||||||
|
兩邊都一樣,不是漏裝。)
|
||||||
|
|
||||||
|
**意外的額外證據**:測試途中 ISEP 的 `main` 因為別的票(`#6`/`#9` 等)合併而往前推進
|
||||||
|
(多出 `hooks/release-tag-guard.sh`、`docs/governance/`…),**下一次 `claude plugin
|
||||||
|
marketplace update` / 重裝立刻拿到新內容**——證明這條路徑讀的是 Gitea 當下的 HEAD,
|
||||||
|
不是任何時間點的快照複製本。
|
||||||
|
|
||||||
|
## 沒驗到的(明講,不含糊)
|
||||||
|
|
||||||
|
- ❌ **沒有在真正的 code-on-web 雲端 session 裡跑過。** 本機能做到的最接近測試是
|
||||||
|
「隔離 `$HOME` + 真的私有 repo + 真的 `claude plugin` CLI」,但 Cloud environment
|
||||||
|
的 Setup script 欄位、Environment variables 欄位是 claude.ai 帳號層級的設定,
|
||||||
|
我沒有去改——那是 leo 的 dashboard,不是這台機器上的檔案,我也判斷這件事
|
||||||
|
不屬於「可以自己裁」的範圍(不是 GitHub push,但同樣是帳號層級設定,
|
||||||
|
比照 D20 的精神交給 leo 動手)。
|
||||||
|
- ❌ **沒有驗到「hook 真的攔下第一個工具呼叫」這一步的完整 runtime 行為**——
|
||||||
|
只驗到「plugin 在磁碟上正確就位、`claude plugin list` 回報 enabled」。
|
||||||
|
完整跑一個已登入的 `claude -p` session需要這台機器的 Claude Code 登入憑證
|
||||||
|
(存在 macOS Keychain,不是可複製的檔案),我判斷把它匯出到隔離測試環境
|
||||||
|
超出這張票該做的事,沒有做。
|
||||||
|
Q5(plugin 是否在 SessionStart 前同步就位、保證第一個工具呼叫就有效)
|
||||||
|
這格的證據來自官方文件(`plugin-marketplaces.md` §Pre-populate plugins for
|
||||||
|
containers:「At startup, Claude Code registers marketplaces found in the
|
||||||
|
seed's `known_marketplaces.json`... This works in both interactive mode and
|
||||||
|
non-interactive mode with the `-p` flag.」),**不是我自己重現的 runtime 實測**。
|
||||||
|
- ❌ **Environment caching 的 ~7 天新鮮度窗口沒有解**——setup script 只在
|
||||||
|
「這個 environment 第一次開 session」跑一次,之後沿用快照,直到快照過期
|
||||||
|
(約 7 天)或 leo 改了 setup script/allowed network hosts 才重跑。
|
||||||
|
這代表 ISEP 若在窗口期內更新,雲端會暫時停在舊版本,直到快照重建。
|
||||||
|
這不是本票要解的「載不載得到」問題,而是另一種新鮮度問題,**留給 leo 決定
|
||||||
|
要不要另開票**(例如:leo 定期手動點一下「rebuild environment」,或接受
|
||||||
|
7 天週期)。
|
||||||
|
|
||||||
|
## 需要 leo 做的(帳號層級設定,非 GitHub push,但同樣是我不該自己動的地方)
|
||||||
|
|
||||||
|
去 code-on-web 的 **Cloud environments** 設定(`claude.ai` 帳號設定,不是
|
||||||
|
GitHub、不是 Gitea):
|
||||||
|
|
||||||
|
1. 選 InkStoneCo 這條線在用的 environment(或建一個新的),
|
||||||
|
**Environment variables** 欄位加一行:`GITEA_TOKEN_CLAUDE_CODE=<既有那把值>`
|
||||||
|
(名字沿用 `InkStoneCo#14` 已建立的那把,不要新造;值只有 leo 知道要填什麼,
|
||||||
|
我這邊沒有也不該有)。
|
||||||
|
2. **Setup script** 欄位貼 `docs/cloud-setup-script.sh` 的內容。
|
||||||
|
3. 開一個新 session(或用 dashboard 的「rebuild environment」逼快照重建),
|
||||||
|
驗 `claude plugin list` 顯示 `isep@inkstone enabled`,且照 §逐條對照 那張表
|
||||||
|
再核一次數字。
|
||||||
|
|
||||||
|
## 這張票沒動、也不會動的東西
|
||||||
|
|
||||||
|
- 沒有動 `youlinhsieh/inkstoneco`(GitHub 薄殼 repo)——這個新機制**完全不需要
|
||||||
|
改它**:安裝目標是 `--scope user`(VM 家目錄),跟 session 從哪個 cwd 啟動無關。
|
||||||
|
舊模型需要在薄殼裡放 `.claude/settings.json` 指標,新模型不需要。
|
||||||
|
- 沒有 push 到 GitHub、沒有 push 到本 repo的 `main`。全部改動只在
|
||||||
|
`leaf/5-cloud` 這條分支。
|
||||||
|
- 沒有把任何 token 值寫進這個 repo 的任何檔案(`docs/cloud-setup-script.sh`
|
||||||
|
只引用環境變數名字 `GITEA_TOKEN_CLAUDE_CODE`)。
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 貼進 code-on-web「Cloud environments → 你的環境 → Setup script」欄位的內容。
|
||||||
|
# 不是 ISEP 的一部分(不會被 Claude Code 當 hook/command/skill 掃描),
|
||||||
|
# 純粹是給 leo 複製貼上的參考檔,見 docs/cloud-session-bootstrap.md。
|
||||||
|
#
|
||||||
|
# 前提(要先在同一個 Cloud environment 的 Environment variables 欄位加好):
|
||||||
|
# GITEA_TOKEN_CLAUDE_CODE ← 既有機器帳號 token,名字沿用 InkStoneCo#14 已建立的那把,
|
||||||
|
# 不要新造一把。值本身不寫在這支腳本或任何檔案裡。
|
||||||
|
#
|
||||||
|
# 這支腳本做兩件事:
|
||||||
|
# 1. 設定 git URL 重寫,讓任何對 git.uncle6.me 的 clone 都能用 GITEA_TOKEN_CLAUDE_CODE 認證
|
||||||
|
# (官方文件對「CI/CD 裝私有 marketplace」建議的寫法,見 references 段)。
|
||||||
|
# 2. 直接把 ISEP 裝成 user-scope plugin ——不是「複製一份」,是跟本機一樣走
|
||||||
|
# `claude plugin marketplace add` + `claude plugin install`,裝的東西
|
||||||
|
# 100% 來自 inkstone/ISEP 這個 repo 本身,沒有第二份內容。
|
||||||
|
#
|
||||||
|
# 何時跑:只在「這個 Cloud environment 第一次開 session」時跑一次,
|
||||||
|
# 跑完 Anthropic 會把整個檔案系統拍成快照,之後的 session 直接沿用快照
|
||||||
|
# (不重跑,除非改了這支腳本本身、改了 allowed network hosts、或快照滿 7 天過期)。
|
||||||
|
# ⇒ 這是唯一會讓「ISEP 改了但雲端還是舊的」重新出現的地方,
|
||||||
|
# 緩解法見 docs/cloud-session-bootstrap.md「已知限制」段。
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ -z "${GITEA_TOKEN_CLAUDE_CODE:-}" ]; then
|
||||||
|
echo "❌ 找不到 GITEA_TOKEN_CLAUDE_CODE —— 去 Cloud environment 的 Environment variables 加這個名字" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 官方文件建議的私有 marketplace 認證寫法:只重寫這個 host 的 URL,不動其他 git 操作。
|
||||||
|
git config --global url."https://x-access-token:${GITEA_TOKEN_CLAUDE_CODE}@git.uncle6.me/".insteadOf \
|
||||||
|
"https://git.uncle6.me/"
|
||||||
|
|
||||||
|
# 用乾淨網址(不帶 token)加 marketplace,實際認證交給上面那條 URL 重寫。
|
||||||
|
claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git --scope user
|
||||||
|
claude plugin install isep@inkstone --scope user
|
||||||
|
|
||||||
|
echo "✅ ISEP 已裝成 user-scope plugin,之後每個 session 啟動時直接生效。"
|
||||||
@@ -121,6 +121,17 @@ s/triage ──驗傷通過──▶ s/backlog ──排進 milestone──▶ s
|
|||||||
🔴 自動打 tag 會製造假交付——tag 永遠只在「驗過了」之後發生。
|
🔴 自動打 tag 會製造假交付——tag 永遠只在「驗過了」之後發生。
|
||||||
- **M4.4** Milestone 關閉 = release tag,一對一。「已交付」唯一合法形式是 **tag 存在且裝得起來**;打 tag 前置 open issues = 0(§8 E12)。
|
- **M4.4** Milestone 關閉 = release tag,一對一。「已交付」唯一合法形式是 **tag 存在且裝得起來**;打 tag 前置 open issues = 0(§8 E12)。
|
||||||
- **M4.5** Description 只寫版本目標一句 + tracking 連結。討論回 tracking issue。
|
- **M4.5** Description 只寫版本目標一句 + tracking 連結。討論回 tracking issue。
|
||||||
|
- **M4.7(降 scope 必須留痕)** 🔴 **把票移出里程碑,必須同時寫進該里程碑的 description。**
|
||||||
|
格式:原本幾張、移走哪幾張、為什麼、移去哪裡。
|
||||||
|
- **禁的不是降 scope**——卡人閘時降 scope 就是 M4.3 要的。禁的是**降得無聲無息**。
|
||||||
|
- 為什麼寫在 description 不是寫在票裡:**leo 看的是百分比那個畫面**,
|
||||||
|
他不會為了確認 100% 是不是真的而去逐張點票。**痕跡要留在他會經過的地方。**
|
||||||
|
- 來由(2026-08-20 實犯):`v0.2.0` 本來 6 張,`#5` 卡人閘被移出 ⇒ 分母 6 變 5 ⇒ 顯示 100%。
|
||||||
|
leo 當場問「為什麼還有很多 issues 開放中」。
|
||||||
|
🔴 這與 §3.4(審核完沒關票 ⇒ 數字偏低)是**同一個病的兩面**:
|
||||||
|
畫面上的數字不等於實際狀態,而 leo 只看得到畫面。
|
||||||
|
- 機械閘:`inkstone/ISEP#24`。
|
||||||
|
|
||||||
- **M4.6** 🔴 **release note 寫在 Gitea Releases 裡,不寫在 README。**(leo 2026-08-20:「release 不是寫在 readme,要放在 release 裡」)
|
- **M4.6** 🔴 **release note 寫在 Gitea Releases 裡,不寫在 README。**(leo 2026-08-20:「release 不是寫在 readme,要放在 release 裡」)
|
||||||
README 不得自行宣稱版本號——那會產生第二份會漂移的版本真相。
|
README 不得自行宣稱版本號——那會產生第二份會漂移的版本真相。
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@
|
|||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/release-tag-guard.sh"
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/release-tag-guard.sh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/ticket-api-bypass-guard.sh"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ticket-api-bypass-guard.sh — 開票的「側門」也要經過同一道搜尋閘
|
||||||
|
#
|
||||||
|
# 來由(leo 2026-08-20 當場問「如何防止」):
|
||||||
|
# `scripts/ticket new` 早就強制「開票前先搜」(/tmp/.ticket-where-ok 戳記,30 分鐘失效)。
|
||||||
|
# 但總管當天開了 12 張與舊票重疊的新票——因為他**沒用那支工具,直接打 Gitea API**。
|
||||||
|
# ⇒ 規範有、閘也有,但閘長在「工具」上,而那個動作有兩條路,只封了一條。
|
||||||
|
# leo:「你在讓事情複雜化」/「每張票開以前都要搜尋現有票,你為什麼會開了不搜?」
|
||||||
|
#
|
||||||
|
# 這支封的是**動作**:任何 Bash 指令只要在對 Gitea 的 issues 端點做寫入,
|
||||||
|
# 就要有一個新鮮的搜尋戳記。它不強迫你用 scripts/ticket,只強迫你搜過。
|
||||||
|
# (同 InkStoneCo#36:「守 prod 的閘,包一層腳本就繞過去了——它看的是指令長相」。
|
||||||
|
# 本支同樣只看得到指令文字,這是 PreToolUse 這層的天花板;
|
||||||
|
# 所以判準取「端點 + 寫入動詞」兩個都命中才擋,讓純讀取一律放行。)
|
||||||
|
#
|
||||||
|
# 放行(刻意,這些都不是「開票」):
|
||||||
|
# - 只讀不寫(GET):撈清單、看票、對帳
|
||||||
|
# - 對既有票的留言/改標籤/關票(/issues/<N>/... 這種帶票號的子路徑)
|
||||||
|
# - scripts/ticket 自己(它有自己的閘,重複擋只會互相打架)
|
||||||
|
# - 指令裡出現 ticket-api-ok(逃生口,會留在指令歷史上)
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
CMD=$(printf '%s' "$INPUT" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
try: print(json.load(sys.stdin).get('tool_input',{}).get('command',''))
|
||||||
|
except Exception: print('')
|
||||||
|
" 2>/dev/null)
|
||||||
|
[ -n "$CMD" ] || exit 0
|
||||||
|
|
||||||
|
# 逃生口(留痕)
|
||||||
|
case "$CMD" in *ticket-api-ok*) exit 0 ;; esac
|
||||||
|
# scripts/ticket 有自己的閘
|
||||||
|
case "$CMD" in *scripts/ticket*|*"ticket where"*|*"ticket new"*|*"ticket say"*) exit 0 ;; esac
|
||||||
|
|
||||||
|
# ① 有沒有打到 Gitea 的 issues 端點(不帶票號的那個=建立新票的路徑)
|
||||||
|
printf '%s' "$CMD" | grep -qE 'repos/[^ "'"'"']*/issues([?"'"'"'`,)\\[:space:]]|$)' || exit 0
|
||||||
|
|
||||||
|
# ② 指令裡有沒有 POST 這個詞(純 GET 一律放行)
|
||||||
|
# 刻意只認一個裸字:跳脫引號、heredoc、python、curl、各種包裝的寫法無限多,
|
||||||
|
# 逐個補 pattern 追不完(leo 2026-08-17:「自然語言的變體是無限的,blacklist 永遠追不完」)。
|
||||||
|
# ①已經確定這是「開票那條端點」,純讀取的指令不會出現 POST ⇒ 一個字就夠,而且沒有跳脫的破口。
|
||||||
|
printf '%s' "$CMD" | grep -qw 'POST' || exit 0
|
||||||
|
|
||||||
|
# ③ 要有新鮮的搜尋戳記(與 scripts/ticket 共用同一個,30 分鐘)
|
||||||
|
STAMP=/tmp/.ticket-where-ok
|
||||||
|
NOW=$(date +%s)
|
||||||
|
FRESH=no
|
||||||
|
if [ -f "$STAMP" ]; then
|
||||||
|
AT=$(python3 -c "import json;print(int(json.load(open('$STAMP'))['at']))" 2>/dev/null || echo 0)
|
||||||
|
[ $((NOW - AT)) -le 1800 ] && FRESH=yes
|
||||||
|
fi
|
||||||
|
[ "$FRESH" = "yes" ] && exit 0
|
||||||
|
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
🚫 你正在用 Gitea API 直接開新票,而這一輪沒有搜尋紀錄
|
||||||
|
|
||||||
|
leo 2026-08-16:「**寫開票前先去搜尋要開在哪裡,不然你永遠會亂開新票**」
|
||||||
|
leo 2026-08-20:「**每張票開以前都要搜尋現有票,你為什麼會開了不搜?這個規範不是早就有 hook 了?**」
|
||||||
|
|
||||||
|
規範有,閘也有——但那道閘長在 `scripts/ticket` 這支工具裡,
|
||||||
|
而你走的是 API 這條側門。**本閘就是把那道門也封上。**
|
||||||
|
|
||||||
|
實錯(2026-08-20 同日):總管用 API 開了 12 張票,事後盤點**每一張都跟舊票重疊**,
|
||||||
|
全部只能關掉指回舊票。leo:「**你在讓事情複雜化**」。
|
||||||
|
|
||||||
|
── 怎麼過(擇一)───────────────────────────────
|
||||||
|
1. 先搜(預設,戳記 30 分鐘有效,之後 API 也放行):
|
||||||
|
scripts/ticket where <關鍵字...>
|
||||||
|
🔴 搜到了就**貼進那張票**,不要開新的:
|
||||||
|
scripts/ticket say <owner/repo#N> -F <內文檔>
|
||||||
|
|
||||||
|
2. 真的是新的一條線 → 直接用那支工具開,它會幫你把該檢查的檢查完:
|
||||||
|
scripts/ticket new <repo> -F <內文檔> --title <標題>
|
||||||
|
|
||||||
|
3. 這次確實不是在開新票(例如批次改標籤/關票/留言)
|
||||||
|
→ 指令裡加 `ticket-api-ok` 說明理由,留痕放行。
|
||||||
|
|
||||||
|
放行的情況(本閘不管):純讀取(GET)、對既有票 /issues/<N>/ 的留言與標籤、scripts/ticket 自己。
|
||||||
|
MSG
|
||||||
|
exit 2
|
||||||
@@ -25,3 +25,13 @@
|
|||||||
原因: 兩種路徑指的是完全不同的東西(「plugin 安裝到哪」vs「正在操作哪個專案」),
|
原因: 兩種路徑指的是完全不同的東西(「plugin 安裝到哪」vs「正在操作哪個專案」),
|
||||||
混用就是這條坑的直接原因。
|
混用就是這條坑的直接原因。
|
||||||
日期: 2026-08-20(README「路徑規約」段記錄,ISEP 0.1.0 把 51 條 hook 路徑全部改過一輪)
|
日期: 2026-08-20(README「路徑規約」段記錄,ISEP 0.1.0 把 51 條 hook 路徑全部改過一輪)
|
||||||
|
|
||||||
|
## ⚠️ MISTAKE: 「裝好了」不等於「它在跑」
|
||||||
|
|
||||||
|
2026-08-20 實查:ISEP repo 建好、README 寫著 0.1.0、41 支閘都在裡面——
|
||||||
|
但 `claude plugin list` 裡**根本沒有 ISEP**。本機仍然在跑 `InkStoneCo/.claude/`,
|
||||||
|
Gitea 上**一個 release tag 都沒有**。
|
||||||
|
|
||||||
|
⇒ 「東西做出來了」與「有人在用它」是兩件事,而只有後者算交付。
|
||||||
|
⇒ 判準:**去執行環境查它有沒有被載入**(`claude plugin list` / `claude plugin details`),
|
||||||
|
不要從 repo 裡有什麼檔案去推論。
|
||||||
|
|||||||
@@ -28,3 +28,18 @@ ISEP(InkStone Environment Plugin):leo 的 Claude Code 環境唯一真相
|
|||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)——README 明寫「尚未驗證」 | 🟡 | 待 `#1`/`#5` 相關票驗證 |
|
| Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)——README 明寫「尚未驗證」 | 🟡 | 待 `#1`/`#5` 相關票驗證 |
|
||||||
| `system-dev/wiki/PANORAMA.md`(跨 repo wiki 全景圖)尚未產生——`scripts/wiki-panorama.sh --write` 要先建 `.panorama-repos.txt` roster,屬另一支票的地盤,本次未動 | ⚪ | 待補 |
|
| `system-dev/wiki/PANORAMA.md`(跨 repo wiki 全景圖)尚未產生——`scripts/wiki-panorama.sh --write` 要先建 `.panorama-repos.txt` roster,屬另一支票的地盤,本次未動 | ⚪ | 待補 |
|
||||||
|
|
||||||
|
## 🔴 目前是「兩份閘都在跑」的中間狀態(2026-08-20)
|
||||||
|
|
||||||
|
本機已經裝上 ISEP plugin(`claude plugin list` 看得到 `isep@inkstone`,enabled),
|
||||||
|
但 `InkStoneCo/.claude/settings.json` 的 41 支閘**還沒拆**。⇒ 同一條規則會擋兩次。
|
||||||
|
|
||||||
|
**這是刻意的,不是忘了**:plugin 的 hook 是 session 啟動時載入,
|
||||||
|
裝它的那個 session 驗不到它真的會觸發。沒驗到就拆,最壞情況是下一個 session 一支閘都沒有。
|
||||||
|
|
||||||
|
**下一個 session 第一件事**:照 `inkstone/ISEP#19` 的驗法確認 plugin 的閘真的會擋,
|
||||||
|
確認了才拆舊的。**在那之前,看到閘訊息出現兩次是正常的。**
|
||||||
|
|
||||||
|
已比對過的等價性(2026-08-20 逐支):
|
||||||
|
- hook 檔案:41 支檔名完全相同,ISEP 多一支 `release-tag-guard.sh`
|
||||||
|
- 註冊條數:PreToolUse 29→30、SessionStart 2、Stop 10、SubagentStop 7、PostToolUse 3,其餘完全相同
|
||||||
|
|||||||
Reference in New Issue
Block a user