Compare commits
142
Commits
08455fb053
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49ae1085d | ||
|
|
e969b88f69 | ||
|
|
a202b76bb2 | ||
|
|
ec6fc5f50b | ||
|
|
3f2279c3a3 | ||
|
|
1195f856e2 | ||
|
|
be8ff3f4d2 | ||
|
|
971cc58d14 | ||
|
|
0d3bd72de3 | ||
|
|
e17589ad04 | ||
|
|
3dccddf055 | ||
|
|
815f34377b | ||
|
|
d2ffbdae17 | ||
|
|
37022854b8 | ||
|
|
62d2b8b436 | ||
|
|
0bb0fa402e | ||
|
|
b66ee5bade | ||
|
|
93ec62db9c | ||
|
|
7d784bf729 | ||
|
|
e0ebd17492 | ||
|
|
aa9baf491a | ||
|
|
b6810f4c49 | ||
|
|
ce22a83a1b | ||
|
|
ee8a70d7b7 | ||
|
|
5ee39ee2e3 | ||
|
|
220f23234a | ||
|
|
3ff864397a | ||
|
|
e683a70e95 | ||
|
|
c1995944d9 | ||
|
|
93757b3036 | ||
|
|
b75060bb3b | ||
|
|
e47f4873c7 | ||
|
|
69f9ca29a6 | ||
|
|
e43f303ff6 | ||
|
|
9512293dc9 | ||
|
|
adc5521302 | ||
|
|
70d0426366 | ||
|
|
2cbd4b5ba5 | ||
|
|
7b3b4006fa | ||
|
|
8a4ca14b7c | ||
|
|
4281694126 | ||
|
|
32a5104c36 | ||
|
|
fca908edfa | ||
|
|
d376930ee5 | ||
|
|
b9b6bb57d0 | ||
|
|
6d1a25eeab | ||
|
|
c6b6c48b46 | ||
|
|
784ef07518 | ||
|
|
4600379307 | ||
|
|
711a434cac | ||
|
|
cde79e3d3f | ||
|
|
46caabe4d8 | ||
|
|
4a06d770b6 | ||
|
|
e250fcbfce | ||
|
|
627f8e6b90 | ||
|
|
32ed9ab004 | ||
|
|
4dd249f184 | ||
|
|
9bf32a21d2 | ||
|
|
cfa44dea7f | ||
|
|
3c0214a927 | ||
|
|
3b1c466202 | ||
|
|
35010bf8f1 | ||
|
|
c24f6deaed | ||
|
|
afb29b6bc0 | ||
|
|
f3a62e6176 | ||
|
|
a757f9e4b0 | ||
|
|
a30fccc627 | ||
|
|
40c2d2ae46 | ||
|
|
f76915bc87 | ||
|
|
241261087b | ||
|
|
fa62d1fce2 | ||
|
|
e4c8cfc046 | ||
|
|
b36fb12416 | ||
|
|
a190463b50 | ||
|
|
7d41343560 | ||
|
|
97c76bc035 | ||
|
|
70efcd55f2 | ||
|
|
e10865ced7 | ||
|
|
12e7d61114 | ||
|
|
48dc9ea74a | ||
|
|
bf599e81a5 | ||
|
|
c323c65b56 | ||
|
|
2225b91766 | ||
|
|
54628d8b2c | ||
|
|
db492e6ba7 | ||
|
|
c9468fda59 | ||
|
|
932aacf8f0 | ||
|
|
28c2b69800 | ||
|
|
4c5e7034ff | ||
|
|
131cda2978 | ||
|
|
d14ca6556c | ||
|
|
0ebaf8991b | ||
|
|
3c7c3d16b0 | ||
|
|
f302adea1b | ||
|
|
12430131f4 | ||
|
|
a9375e8563 | ||
|
|
0b9e153cda | ||
|
|
c904f4bb9b | ||
|
|
1f365efa02 | ||
|
|
10bae7b6f4 | ||
|
|
f70c010cc5 | ||
|
|
e477e0d1a4 | ||
|
|
177cc9b2bc | ||
|
|
2149e7a3c2 | ||
|
|
9978562092 | ||
|
|
b0c93441a1 | ||
|
|
16125e77e8 | ||
|
|
945c310551 | ||
|
|
bb48a77dd4 | ||
|
|
6d5c4e3f02 | ||
|
|
65426f0c35 | ||
|
|
c713359fb4 | ||
|
|
9a38f15262 | ||
|
|
3748d2e61a | ||
|
|
32f2872dd1 | ||
|
|
294a2c1e1c | ||
|
|
ac8023861d | ||
|
|
f0018fd2a0 | ||
|
|
485f506a28 | ||
|
|
a3fcd62a4c | ||
|
|
3a3114b8b3 | ||
|
|
78f4086a48 | ||
|
|
57a493e612 | ||
|
|
fec3e1d0cb | ||
|
|
e71f165e9a | ||
|
|
716fd2c26e | ||
|
|
c7b7e684a5 | ||
|
|
0fae7e1723 | ||
|
|
d6f9c4956b | ||
|
|
32f53edd78 | ||
|
|
7ebcec0a8e | ||
|
|
d54d6ed8e2 | ||
|
|
43d157f0ce | ||
|
|
d91c293666 | ||
|
|
497dc55b61 | ||
|
|
32a94cb6df | ||
|
|
c2239dd97d | ||
|
|
c25307b9b9 | ||
|
|
604656caa1 | ||
|
|
8521682583 | ||
|
|
cad5be26b6 | ||
|
|
6ba3a4d4cf |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "generic",
|
||||
"name": "shared",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "jsc",
|
||||
"name": "jsc-shared",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://gitea.jsc.idv.tw/plugins/generic.git"
|
||||
"url": "https://gitea.jsc.idv.tw/plugins/shared.git"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "generic",
|
||||
"description": "JSC 跨 AI 助理共用 skills 的 Claude Code marketplace。",
|
||||
"name": "shared",
|
||||
"description": "JSC 跨 AI 助理共用規範 skills 的 Claude Code marketplace。",
|
||||
"owner": {
|
||||
"name": "JSC"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "jsc",
|
||||
"name": "jsc-shared",
|
||||
"source": "./",
|
||||
"description": "JSC 共用 skills(跨 AI 助理)"
|
||||
"description": "JSC 共用規範 skills(跨 AI 助理),另含整組 plugin 的安裝管理 plugins-install / plugins-uninstall"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jsc",
|
||||
"version": "0.0.2",
|
||||
"description": "JSC 跨 AI 助理共用 plugin 模板(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc: 前綴呼叫。",
|
||||
"name": "jsc-shared",
|
||||
"version": "0.0.8",
|
||||
"description": "JSC 跨 AI 助理共用規範 plugin(Claude Code / Codex / GitHub Copilot CLI / Antigravity / OpenCode),並提供整組 plugin 的安裝/更新/移除管理(plugins-install 一次安裝或更新 jsc-code/jsc-doc/jsc-persona/jsc-shared,plugins-uninstall 一次移除四個 JSC plugin)。安裝與更新一律以 Gitea 遠端 repo 的 README 與檔案為準,不依賴既有本機存取庫;所有 skills 以 SKILL.md 為共通標準;於 Claude Code 以 /jsc-shared: 前綴呼叫。",
|
||||
"skills": "./skills",
|
||||
"author": {
|
||||
"name": "JSC"
|
||||
},
|
||||
"homepage": "https://gitea.jsc.idv.tw/plugins/generic",
|
||||
"repository": "https://gitea.jsc.idv.tw/plugins/generic.git",
|
||||
"keywords": ["template", "skills", "cross-tool", "jsc"]
|
||||
"homepage": "https://gitea.jsc.idv.tw/plugins/shared",
|
||||
"repository": "https://gitea.jsc.idv.tw/plugins/shared.git",
|
||||
"keywords": ["spec", "skills", "cross-tool", "jsc"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jsc",
|
||||
"version": "0.0.2",
|
||||
"description": "JSC 跨 AI 助理共用 plugin 模板。所有 skills 以 SKILL.md 為共通標準。",
|
||||
"skills": "./skills"
|
||||
"name": "jsc-shared",
|
||||
"version": "0.0.8",
|
||||
"description": "JSC 跨 AI 助理共用規範 skills plugin,`skills/` 為唯一真實來源,並提供整組 plugin 的安裝/更新/移除管理(plugins-install 一次安裝或更新 jsc-code/jsc-doc/jsc-persona/jsc-shared,plugins-uninstall 一次移除四個 JSC plugin)。安裝與更新一律以 Gitea 遠端 repo 的 README 與檔案為準,不依賴既有本機存取庫;所有 skills 以 SKILL.md 為共通標準。",
|
||||
"skills": "./skills/"
|
||||
}
|
||||
|
||||
@@ -8,3 +8,7 @@ Thumbs.db
|
||||
# 暫存
|
||||
*.tmp
|
||||
*.log
|
||||
|
||||
# Python(scripts/ 內腳本被 import 時產生)
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# jsc — 共用 Skills(跨 AI 助理)
|
||||
# jsc-shared — 共用 Skills(跨 AI 助理)
|
||||
|
||||
本 repo 是一組以 **Agent Skills(`SKILL.md`)** 標準撰寫的共用 skills,可同時被 Claude Code、Codex、Antigravity、OpenCode 使用。
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
- 所有可用的 skills 位於本 repo 的 `skills/<name>/SKILL.md`。
|
||||
- 在處理任務前,先比對使用者需求與各 skill `SKILL.md` frontmatter 的 `description`,若相符請載入並依其步驟執行。
|
||||
- **呼叫慣例**:在 Claude Code 與 Antigravity 中,這些 skill 以 `/jsc:<name>` 呼叫;Codex 以 `$<name>`、OpenCode 由模型依描述自動觸發 — 兩者沒有 `/jsc:` 前綴,不需強制加。
|
||||
- **呼叫慣例**:在 Claude Code 與 Antigravity 中,這些 skill 以 `/jsc-shared:<name>` 呼叫;Codex 以 `$<name>`、OpenCode 由模型依描述自動觸發 — 兩者沒有 `/jsc-shared:` 前綴,不需強制加。
|
||||
- 完整清單與每個 skill 的用途,請見 `README.md` 的「Skills 目錄」。
|
||||
- 本 repo 只保留純 `skills/` 內容;載入前請看各 skill `description` 標示的支援範圍與 `README.md` 的「Skills 目錄」。沒有 plugin 匯入指令、但可使用 skill 的助理,統一先 clone 技能組到工具專屬資料夾,再依 `README.md` 的指定位置匯入。
|
||||
|
||||
## 慣例
|
||||
|
||||
|
||||
@@ -1,149 +1,173 @@
|
||||
# jsc — 跨 AI 助理 Plugin 模板
|
||||
# jsc-shared — 跨 AI 助理共用規範 Plugin
|
||||
|
||||
一個可同時被 **Claude Code、Codex、Antigravity、OpenCode** 安裝的 plugin 模板。
|
||||
一個可同時被 **Claude Code、Codex、Antigravity、OpenCode、GitHub Copilot** 使用的共用規範 plugin。
|
||||
核心是以 [Agent Skills(`SKILL.md`)](https://agentskills.io) 標準撰寫的共用 skills(唯一真實來源放在 `skills/`),
|
||||
搭配各助理各自的 plugin manifest,讓**同一個 repo** 可用各家**原生 plugin CLI** 安裝。
|
||||
在 Claude Code 與 Antigravity 中,skill 以 **`/jsc:` 前綴**呼叫(例如 `/jsc:spec-output`)。
|
||||
在 Claude Code 與 Antigravity 中,skill 以 **`/jsc-shared:` 前綴**呼叫(例如 `/jsc-shared:spec-output`)。
|
||||
|
||||
---
|
||||
|
||||
## 前綴與呼叫方式
|
||||
|
||||
| 助理 | 安裝方式 | 呼叫 | `/jsc:` 前綴 |
|
||||
| 助理 | 安裝方式 | 呼叫 | `/jsc-shared:` 前綴 |
|
||||
| --- | --- | --- | --- |
|
||||
| Claude Code | `claude plugin`(marketplace) | `/jsc:<name>` 或自動觸發 | ✅ |
|
||||
| Claude Code | `claude plugin`(marketplace) | `/jsc-shared:<name>` 或自動觸發 | ✅ |
|
||||
| Codex | `codex plugin`(marketplace) | `$<name>` 或 `/skills` 選單 | ❌(用 `$name`) |
|
||||
| Antigravity | `agy plugin install` | `/jsc:<name>` 或自動觸發 | ✅ |
|
||||
| OpenCode | skills 目錄(複製/clone) | 描述需求自動觸發 | ❌(依名稱) |
|
||||
| Antigravity | `agy plugin install` | `/jsc-shared:<name>` 或自動觸發 | ✅ |
|
||||
| OpenCode | skills 目錄(先 clone 到工具專屬資料夾,再依 README 匯入) | 描述需求自動觸發 | ❌(依名稱) |
|
||||
| GitHub Copilot CLI | `copilot plugin`(marketplace) | 自然語言或 plugin skills | ❌(無 `/jsc-shared:` 前綴) |
|
||||
|
||||
> Codex 不支援自訂前綴(skill 以 `$name` 呼叫);OpenCode 由模型依描述自動呼叫。兩者皆**不強制**前綴。
|
||||
> Codex 不支援自訂前綴(skill 以 `$name` 呼叫);OpenCode 由模型依描述自動呼叫;Copilot CLI 透過原生 plugin 安裝後以自然語言或 plugin skills 使用。三者皆**不強制**前綴。
|
||||
|
||||
---
|
||||
|
||||
## 目錄結構
|
||||
|
||||
同一個 repo 同時帶四種 manifest,彼此以路徑隔離、互不干擾;四家都讀同一份 `skills/`。
|
||||
同一個 repo 同時帶四種 manifest,彼此以路徑隔離、互不干擾;各助理都讀同一份 `skills/`。
|
||||
|
||||
```
|
||||
generic/
|
||||
shared/
|
||||
├── .claude-plugin/
|
||||
│ ├── plugin.json # Claude 外掛定義(name: "jsc")
|
||||
│ └── marketplace.json # Claude marketplace(name: "generic",source 指向本 repo)
|
||||
│ ├── plugin.json # Claude 外掛定義(name: "jsc-shared")
|
||||
│ └── marketplace.json # Claude marketplace(name: "shared",source 指向本 repo)
|
||||
├── .codex-plugin/
|
||||
│ └── plugin.json # Codex 外掛定義(name: "jsc",skills: "./skills")
|
||||
│ └── plugin.json # Codex 外掛定義(name: "jsc-shared",skills: "./skills")
|
||||
├── .agents/plugins/
|
||||
│ └── marketplace.json # Codex marketplace(name: "generic",url source 指向本 repo)
|
||||
├── plugin.json # Antigravity 外掛定義(name: "jsc",skills: "./skills/")
|
||||
├── hooks/
|
||||
│ └── hooks.json # Claude Code hooks(Stop → worklog;其他助理不吃此檔)
|
||||
├── scripts/
|
||||
│ └── worklog/ # worklog 自動記錄的可執行元件(skill 與 hook 共用)
|
||||
│ ├── worklog.sh # 主流程:抽本輪 → 濃縮 → 遮蔽 → 追加到 wiki
|
||||
│ ├── wiki_api.py # Gitea wiki 讀寫、token 解析、append 重試、週頁命名
|
||||
│ └── transcript.py # transcript 本輪抽取與機密遮蔽
|
||||
│ └── marketplace.json # Codex marketplace(name: "shared",url source 指向本 repo)
|
||||
├── plugin.json # Antigravity 外掛定義(name: "jsc-shared",skills: "./skills/")
|
||||
├── skills/ # ★ 唯一真實來源:所有 skills
|
||||
│ ├── spec-*/SKILL.md # 共用規範 skills(一規範一目錄)
|
||||
│ └── worklog/SKILL.md # 工作證明自動記錄的操作與維護
|
||||
│ ├── plugins-install/ # 一次安裝/更新 jsc-code、jsc-doc、jsc-persona
|
||||
│ └── plugins-uninstall/ # 一次移除 jsc-code、jsc-doc、jsc-persona、jsc-shared
|
||||
├── AGENTS.md # 跨助理共用指引
|
||||
└── README.md
|
||||
```
|
||||
|
||||
> generic 的定位是「**共用規範 + 全域自動化**」:`skills/spec-*` 是純規範文件(四家助理通用),`hooks/` 與 `scripts/` 是可執行元件。**`hooks/hooks.json` 只有 Claude Code 會讀**;Codex/Antigravity/OpenCode 匯入時只有 skill 可用,自動記錄不會啟動。
|
||||
> shared 的定位是「**共用規範**」:`skills/spec-*` 是 code/doc plugins 共用的流程與安全規範。工作紀錄自動化 `worklog` 已移到 `doc` plugin。
|
||||
> 例外只有 `plugins-install`/`plugins-uninstall` 兩類:它們是**整組 plugin 的安裝管理**,一次處理所有 JSC plugin,不必逐個 repo 翻 README。
|
||||
|
||||
---
|
||||
|
||||
## 安裝 / 更新 / 移除(各家原生 plugin CLI)
|
||||
## 安裝 / 更新 / 移除(各助理)
|
||||
|
||||
> 指令中的 repo 網址換成你的:`https://gitea.jsc.idv.tw/plugins/generic.git`
|
||||
> 指令中的 repo 網址換成你的:`https://gitea.jsc.idv.tw/plugins/shared.git`
|
||||
>
|
||||
> **Claude / Codex 從 git URL 安裝(會 clone 遠端),請先把本 repo `push` 到 gitea。**
|
||||
> **Antigravity 的 `agy plugin install <url>` 目前只支援 github.com**;gitea 請改用「clone + 本地路徑」(見 Antigravity 節)。
|
||||
> 本機/離線:Claude 可用本地路徑加 marketplace;Antigravity 用本地路徑安裝。
|
||||
> **Antigravity 的 `agy plugin install <url>` 目前只支援 github.com**;gitea 請依本節改用「從遠端重新抓取到暫存目錄,再用本地路徑安裝」。
|
||||
> 本 repo 的安裝/更新/移除流程一律以 Gitea 遠端檔案與下方 README 章節為準,不依賴既有本機存取庫。
|
||||
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/generic.git
|
||||
claude plugin install jsc@generic
|
||||
claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/shared.git
|
||||
claude plugin install jsc-shared@shared
|
||||
|
||||
# 更新
|
||||
claude plugin marketplace update generic
|
||||
claude plugin update jsc@generic
|
||||
claude plugin marketplace update shared
|
||||
claude plugin update jsc-shared@shared
|
||||
|
||||
# 移除
|
||||
claude plugin uninstall jsc@generic
|
||||
claude plugin marketplace remove generic
|
||||
claude plugin uninstall jsc-shared@shared
|
||||
claude plugin marketplace remove shared
|
||||
```
|
||||
|
||||
- 工作階段內 slash 版(等價):把 `claude plugin` 換成 `/plugin`。
|
||||
- 本機開發(免 push):`claude plugin marketplace add C:\Users\h3285\source\repos.plugins\generic`(本地路徑)後再 install。
|
||||
- **呼叫**:`/jsc:<name>`(例 `/jsc:spec-output`)。
|
||||
- 本機開發(免 push)仍可用本地路徑,但正式安裝請以 Gitea 遠端 README 為準。
|
||||
- **呼叫**:`/jsc-shared:<name>`(例 `/jsc-shared:spec-output`)。
|
||||
|
||||
### Codex
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/generic.git
|
||||
codex plugin add jsc@generic
|
||||
codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/shared.git
|
||||
codex plugin add jsc-shared@shared
|
||||
|
||||
# 更新(重新抓取 marketplace 的 git 快照)
|
||||
codex plugin marketplace upgrade generic
|
||||
codex plugin marketplace upgrade shared
|
||||
|
||||
# 移除
|
||||
codex plugin remove jsc@generic
|
||||
codex plugin marketplace remove generic
|
||||
codex plugin remove jsc-shared@shared
|
||||
codex plugin marketplace remove shared
|
||||
```
|
||||
|
||||
- 安裝 token `jsc@generic` = plugin 名(`.codex-plugin/plugin.json` 的 `name`)@ marketplace 名(`.agents/plugins/marketplace.json` 的 `name`)。
|
||||
- 安裝 token `jsc-shared@shared` = plugin 名(`.codex-plugin/plugin.json` 的 `name`)@ marketplace 名(`.agents/plugins/marketplace.json` 的 `name`)。
|
||||
- 本 repo 的 Codex marketplace 以 `url` 來源指向自己,故 Codex **一律從 gitea 安裝**(需先 push);安裝後重啟 Codex。
|
||||
- **呼叫**:`$<name>`(例 `$spec-output`),或用 `/skills` 選單。
|
||||
|
||||
### Antigravity(`agy`)
|
||||
|
||||
> `agy plugin install <url>` 目前**只支援 github.com**;gitea 等自架 git 不支援 URL 安裝,請先 `git clone` 再用**本地路徑**安裝。
|
||||
> `agy plugin install <url>` 目前**只支援 github.com**;gitea 等自架 git 不支援 URL 安裝,請先從遠端抓到暫存目錄,再用**本地路徑**安裝。
|
||||
|
||||
```bash
|
||||
# 安裝:clone 後用本地路徑
|
||||
git clone https://gitea.jsc.idv.tw/plugins/generic.git ~/plugins/generic
|
||||
agy plugin install ~/plugins/generic
|
||||
# 安裝:先從遠端抓到暫存目錄,再用本地路徑
|
||||
git clone --depth 1 https://gitea.jsc.idv.tw/plugins/shared.git /tmp/jsc-shared
|
||||
agy plugin install /tmp/jsc-shared
|
||||
|
||||
# 更新(agy 無 update 子指令 → git pull 後重裝)
|
||||
git -C ~/plugins/generic pull
|
||||
agy plugin uninstall jsc
|
||||
agy plugin install ~/plugins/generic
|
||||
# 更新(重新抓取遠端後重裝)
|
||||
git clone --depth 1 https://gitea.jsc.idv.tw/plugins/shared.git /tmp/jsc-shared
|
||||
agy plugin uninstall jsc-shared
|
||||
agy plugin install /tmp/jsc-shared
|
||||
|
||||
# 移除
|
||||
agy plugin uninstall jsc
|
||||
agy plugin uninstall jsc-shared
|
||||
```
|
||||
|
||||
- 若把 skills 放到 GitHub,則可直接 `agy plugin install https://github.com/<owner>/<repo>`。
|
||||
- 其他:`agy plugin list`、`agy plugin enable jsc` / `disable jsc`、`agy plugin validate <path>`。安裝後重啟工作階段。
|
||||
- **呼叫**:`/jsc:<name>`(例 `/jsc:spec-output`)或依描述自動觸發。
|
||||
- 不讀取既有本機 repo;若需要對照 README,只用遠端 checkout 的暫存工作區。
|
||||
- 其他:`agy plugin list`、`agy plugin enable jsc-shared` / `disable jsc-shared`、`agy plugin validate <path>`。安裝後重啟工作階段。
|
||||
- **呼叫**:`/jsc-shared:<name>`(例 `/jsc-shared:spec-output`)或依描述自動觸發。
|
||||
|
||||
### OpenCode
|
||||
### 無 plugin 指令但可使用 skill 的助理
|
||||
|
||||
OpenCode 的「plugin」是 TypeScript/npm 套件,不適用於 skill 包;skills 改用**目錄安裝**。
|
||||
OpenCode 會讀 `~/.config/opencode/skills/`(也會讀 `~/.claude/skills/`、`~/.agents/skills/`)。
|
||||
以 OpenCode 為例,這類工具不走原生 plugin install / uninstall,而是:
|
||||
|
||||
1. 先把整組 skill clone 到工具的專屬資料夾。
|
||||
2. 再依技能組自己的 `README.md`,把技能匯入到 README 指定的位置。
|
||||
3. 移除時則反向刪除 README 指定的那些 skill 目錄。
|
||||
|
||||
OpenCode 會讀 `~/.config/opencode/skills/`(也會讀 `~/.claude/skills/`、`~/.agents/skills/`),因此可把技能匯入這些位置。
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
git clone https://gitea.jsc.idv.tw/plugins/generic.git ~/plugins/generic
|
||||
git clone --depth 1 https://gitea.jsc.idv.tw/plugins/shared.git /tmp/jsc-shared
|
||||
mkdir -p ~/.config/opencode/skills
|
||||
cp -r ~/plugins/generic/skills/* ~/.config/opencode/skills/
|
||||
cp -r /tmp/jsc-shared/skills/* ~/.config/opencode/skills/
|
||||
|
||||
# 更新
|
||||
git -C ~/plugins/generic pull
|
||||
cp -r ~/plugins/generic/skills/* ~/.config/opencode/skills/
|
||||
git clone --depth 1 https://gitea.jsc.idv.tw/plugins/shared.git /tmp/jsc-shared
|
||||
cp -r /tmp/jsc-shared/skills/* ~/.config/opencode/skills/
|
||||
|
||||
# 移除
|
||||
rm -rf ~/.config/opencode/skills/spec-*
|
||||
# 移除(逐一移除本 plugin 帶入的 skill 目錄;勿只清 spec-*,否則其他 skill 會殘留)
|
||||
for s in /tmp/jsc-shared/skills/*/; do rm -rf "$HOME/.config/opencode/skills/$(basename "$s")"; done
|
||||
```
|
||||
|
||||
> **Windows PowerShell**:`cp -r A B` → `Copy-Item A B -Recurse -Force`、`rm -rf X` → `Remove-Item X -Recurse -Force`、`~` → `$HOME`。
|
||||
|
||||
- **呼叫**:直接描述需求,模型會依 skill 描述自動透過 skill 工具呼叫。
|
||||
|
||||
### GitHub Copilot CLI
|
||||
|
||||
Copilot CLI 支援與 Claude Code 類似的原生 plugin / marketplace 指令,可直接從 marketplace 安裝、更新與移除本 plugin。
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
copilot plugin marketplace add https://gitea.jsc.idv.tw/plugins/shared.git
|
||||
copilot plugin install jsc-shared@shared
|
||||
|
||||
# 更新
|
||||
copilot plugin marketplace update shared
|
||||
copilot plugin update jsc-shared@shared
|
||||
|
||||
# 移除
|
||||
copilot plugin uninstall jsc-shared@shared
|
||||
copilot plugin marketplace remove shared
|
||||
```
|
||||
|
||||
- 安裝 token `jsc-shared@shared` = plugin 名(plugin manifest 的 `name`)@ marketplace 名。
|
||||
- `copilot plugin marketplace add` 支援 GitHub `owner/repo`、git URL 與本地路徑;Gitea repo 可用上方 HTTPS URL。
|
||||
- **呼叫**:在 Copilot CLI 中用自然語言描述需求,例如 `copilot -i "請使用 spec-output 說明輸出規範"`。
|
||||
|
||||
---
|
||||
|
||||
## 用 CLI 直接執行 skill(headless / 一次性)
|
||||
@@ -152,15 +176,16 @@ rm -rf ~/.config/opencode/skills/spec-*
|
||||
|
||||
| 助理 | headless 指令 | 執行 `spec-output` skill |
|
||||
| --- | --- | --- |
|
||||
| Claude Code | `claude -p "<prompt>"` | `claude -p "/jsc:spec-output"` |
|
||||
| Claude Code | `claude -p "<prompt>"` | `claude -p "/jsc-shared:spec-output"` |
|
||||
| Codex | `codex exec "<prompt>"` | `codex exec '$spec-output'` |
|
||||
| Antigravity | `agy -p "<prompt>"` | `agy -p "/jsc:spec-output"` |
|
||||
| Antigravity | `agy -p "<prompt>"` | `agy -p "/jsc-shared:spec-output"` |
|
||||
| OpenCode | `opencode run "<message>"` | `opencode run "說明 JSC 共用輸出規範的內容"` |
|
||||
| GitHub Copilot CLI | `copilot -p "<message>"` | `copilot -p "說明 JSC 共用輸出規範的內容"` |
|
||||
|
||||
- Claude / Antigravity 支援 `/jsc:` 前綴,直接 `-p "/jsc:<name>"` 即可。
|
||||
- Claude / Antigravity 支援 `/jsc-shared:` 前綴,直接 `-p "/jsc-shared:<name>"` 即可。
|
||||
- Codex 以 `$<name>` 觸發;在 shell 請用**單引號**避免 `$` 被展開:`codex exec '$spec-output'`。
|
||||
- OpenCode 沒有前綴,用自然語言描述需求,模型會自動透過 skill 工具呼叫。
|
||||
- 帶引數就接在後面,例如 `claude -p "/jsc:spec-output 參數"`、`codex exec '$spec-output 參數'`。
|
||||
- OpenCode 與 Copilot 沒有前綴,用自然語言描述需求;Copilot CLI 會讀取已安裝 plugin 提供的 skills。
|
||||
- 帶引數就接在後面,例如 `claude -p "/jsc-shared:spec-output 參數"`、`codex exec '$spec-output 參數'`。
|
||||
|
||||
---
|
||||
|
||||
@@ -173,7 +198,7 @@ rm -rf ~/.config/opencode/skills/spec-*
|
||||
|
||||
### 共用規範(spec-*)
|
||||
|
||||
以下 skills 是 **code/doc plugins 各 skill 引用的共用規範**:其他 skill 內文以 `/jsc:spec-<name>` 引用時載入;也可單獨呼叫查看規範內容。
|
||||
以下 skills 是 **code/doc plugins 各 skill 引用的共用規範**:其他 skill 內文以 `/jsc-shared:spec-<name>` 引用時載入;也可單獨呼叫查看規範內容。
|
||||
|
||||
| Skill | 類型 | 內容 |
|
||||
| --- | --- | --- |
|
||||
@@ -185,30 +210,52 @@ rm -rf ~/.config/opencode/skills/spec-*
|
||||
| `spec-action-params` | Action 參數 | action 參數來源優先序(context/環境變數 → inputs)、secrets/vars 一律視為不可用 |
|
||||
| `spec-dockerfile` | Dockerfile | 六步流程(參數→安裝→複製→執行→縮小→入口)、多階段建置、固定版號、對外契約不動、自我檢查 |
|
||||
| `spec-project-board` | Gitea 看板 | 進度欄位語意對應、建議欄位規則、GET 探測(404/501 不支援)、不往回移、不得新建欄位 |
|
||||
| `spec-doc-funcs-handoff` | 文件化串接 | code 類 skill 完成後完整執行 /jsc:doc-funcs 的標準流程與統一時間戳 |
|
||||
| `spec-plugin-version` | 版號規則 | 三 manifest 同步 bump、對照 master 確保單調遞增、新 plugin 首發 0.0.1、chore(plugin 版本) commit |
|
||||
| `spec-doc-funcs-handoff` | 文件化串接 | code 類 skill 完成後完整執行 /jsc-doc:funcs 的標準流程與統一時間戳 |
|
||||
| `spec-plugin-version` | 版號規則 | 三 manifest 同步 bump、以 master 為基準計算同一 PR 的最終版本、新 plugin 首發 0.0.1、patch 到 9 後進位 minor、chore(plugin 版本) commit |
|
||||
|
||||
### 全域自動化
|
||||
### 整組 plugin 安裝管理
|
||||
|
||||
| Skill | 類型 | 內容 |
|
||||
一次操作所有 JSC plugin,不必逐個 repo 翻 README 的安裝章節。四家原生 plugin CLI(`claude`/`codex`/`copilot`/`agy`)都支援,OpenCode 走複製/刪除 skills 目錄。
|
||||
|
||||
| Skill | 用途 | 使用方法 |
|
||||
| --- | --- | --- |
|
||||
| `worklog` | 工作證明記錄 | 每輪工作濃縮成條目追加到 Gitea wiki 當週頁(`Worklog-yyyy-MM-W<週>`);`--init`/`--tune`/`--diagnose`/`--append`/`--show` 五個模式;自動記錄由 Claude Code `Stop` hook 完成 |
|
||||
| `plugins-install` | 一次**安裝或更新** `jsc-code`、`jsc-doc`、`jsc-persona`、`jsc-shared`:先盤點每個 plugin 已安裝或未安裝,未安裝就安裝、已安裝就更新到最新,最後以表格回報動作、位置、結果與版本。`agy` 走 clone+本地路徑安裝;OpenCode 這類**沒有 plugin 匯入指令、但可使用 skill** 的助理先把技能組 clone 到工具專屬資料夾,再依技能組 `README.md` 匯入到指定位置,**已安裝就在該路徑就地更新、未安裝才放進工具的全域資料夾** | `/jsc-shared:plugins-install`;可帶 `--assistant claude\|codex\|copilot\|agy\|opencode`、`--plugins code,doc,persona,shared`、`--host <gitea 主機>`、`--clone-dir <目錄>`、`--yes` |
|
||||
| `plugins-uninstall` | 一次**移除** `jsc-code`、`jsc-doc`、`jsc-persona`、`jsc-shared`:動手前先列出將被移除的項目與不會被碰的資料請使用者確認,移除順序固定把 `jsc-shared` 放最後(本 skill 就住在裡面)。人格倉庫與記憶目錄一律不刪;OpenCode 這類**沒有 plugin 匯入指令、但可使用 skill** 的助理則依技能組 `README.md` 反向刪除匯入位置 | `/jsc-shared:plugins-uninstall`;可帶 `--assistant …`、`--plugins code,doc,persona,shared`、`--keep-marketplace`、`--yes` |
|
||||
|
||||
> `plugins-install` 與 `plugins-uninstall` 都會處理 `jsc-shared`;移除時一定放最後一步。
|
||||
|
||||
<!-- JSC-SKILLS:END -->
|
||||
|
||||
---
|
||||
|
||||
## 元件對各助理的適用範圍
|
||||
|
||||
`skills/` 各助理都能用。
|
||||
|
||||
| 元件 | Claude Code | Codex | Antigravity | OpenCode | GitHub Copilot |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `skills/spec-*`(純規範) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| `skills/plugins-install`、`plugins-uninstall` | ✅ | ✅ | ✅ | ⚠️ 只能操作 OpenCode 自己 | ✅ |
|
||||
| cron 睡眠整理(系統排程) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
> **OpenCode 以複製 `skills/` 目錄安裝**。
|
||||
|
||||
---
|
||||
|
||||
## 新增一個 skill
|
||||
|
||||
1. 複製既有 skill 作範本:`cp -r skills/spec-output skills/<your-skill-name>`
|
||||
2. 編輯 `skills/<your-skill-name>/SKILL.md` 的 frontmatter:
|
||||
- `name`:小寫、數字、連字號(`-`),最長 64 字元。**這就是 Claude Code / Antigravity 的 `/jsc:<name>`**。
|
||||
- `name`:小寫、數字、連字號(`-`),最長 64 字元。**這就是 Claude Code / Antigravity 的 `/jsc-shared:<name>`**。
|
||||
- `description`:第三人稱,寫清楚「何時用、何時不用」與觸發關鍵字 — 這是各助理自動載入的唯一依據。
|
||||
3. 在內文寫下 skill 的具體步驟。
|
||||
4. 手動把這個 skill 補進上方「Skills 目錄」區塊。
|
||||
5. **bump 版本並 push**:四家都以 git 內容/版本判斷更新,請把 `.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`、`plugin.json` 三個 manifest 的 `version` 一起 bump(規則見 `skills/spec-plugin-version/`),commit 後 push 到 gitea。
|
||||
5. **bump 版本並 push**:各助理都以 git 內容/版本判斷更新,請把 `.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`、`plugin.json` 三個 manifest 的 `version` 一起 bump(規則見 `skills/spec-plugin-version/`),commit 後 push 到 gitea。
|
||||
6. 讓各助理更新:
|
||||
- Claude:`claude plugin update jsc@generic`
|
||||
- Codex:`codex plugin marketplace upgrade generic`
|
||||
- Antigravity:`git -C ~/jsc-plugin pull && agy plugin uninstall jsc && agy plugin install ~/jsc-plugin`
|
||||
- OpenCode:`git pull` 後重新複製 `skills/`
|
||||
- Claude:`claude plugin update jsc-shared@shared`
|
||||
- Codex:`codex plugin marketplace upgrade shared`
|
||||
- Antigravity:重新從 Gitea 遠端抓取到暫存目錄後再 `agy plugin uninstall jsc-shared && agy plugin install <temp-dir>/shared`
|
||||
- OpenCode:重新從 Gitea 遠端抓取到暫存目錄後再複製 `skills/`
|
||||
- Copilot:`copilot plugin marketplace update shared && copilot plugin update jsc-shared@shared`
|
||||
|
||||
> 本 repo 只放純 `SKILL.md` 內容,不含可執行腳本或 hook。
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/worklog/worklog.sh",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jsc",
|
||||
"version": "0.0.2",
|
||||
"description": "JSC 跨 AI 助理共用 plugin 模板。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc: 前綴呼叫。",
|
||||
"name": "jsc-shared",
|
||||
"version": "0.0.8",
|
||||
"description": "JSC 跨 AI 助理共用規範 skills plugin,`skills/` 為唯一真實來源,並提供整組 plugin 的安裝/更新/移除管理(plugins-install 一次安裝或更新 jsc-code/jsc-doc/jsc-persona/jsc-shared,plugins-uninstall 一次移除四個 JSC plugin)。安裝與更新一律以 Gitea 遠端 repo 的 README 與檔案為準,不依賴既有本機存取庫;所有 skills 以 SKILL.md 為共通標準。",
|
||||
"skills": "./skills/"
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# ==============================================================================
|
||||
# 用途:worklog 的 transcript 處理工具。負責 (1) 從 Claude Code transcript
|
||||
# JSONL 抽出「本輪」對話片段(最後一筆使用者訊息之後的全部內容),
|
||||
# (2) 對文字做機密遮蔽(token/密碼/PII),作為寫入 wiki 前的第二道防線。
|
||||
# 更新時間:2026/07/27 11:14:16
|
||||
# 相依:Python 3 標準庫。全程僅走 stdin/stdout,不寫任何檔案。
|
||||
# ==============================================================================
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
# 單則工具結果/參數的擷取上限,避免整份 transcript 塞進摘要輸入
|
||||
TOOL_RESULT_LIMIT = 200
|
||||
TOOL_INPUT_LIMIT = 160
|
||||
TOTAL_LIMIT = 24000
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 機密遮蔽規則:命中一律換成 ***
|
||||
# ------------------------------------------------------------------------------
|
||||
REDACT_PATTERNS = [
|
||||
(r"[A-Za-z0-9_\-]*:[A-Za-z0-9_\-]{16,}@", "***@"), # URL 內嵌憑證 user:token@
|
||||
(r"\b[0-9a-f]{40}\b", "***"), # Gitea 40 字元 token
|
||||
(r"\bgh[pousr]_[A-Za-z0-9_]{16,}\b", "***"), # GitHub token
|
||||
(r"\bsk-[A-Za-z0-9\-_]{16,}\b", "***"), # API key
|
||||
(r"(?i)\b(token|password|passwd|pwd|secret|api[_-]?key)\b\s*[:=]\s*\S+", r"\1=***"),
|
||||
(r"(?i)Authorization:\s*(token|bearer)\s+\S+", r"Authorization: \1 ***"),
|
||||
(r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}", "***"), # Email
|
||||
(r"\b09\d{2}[-\s]?\d{3}[-\s]?\d{3}\b", "***"), # 台灣手機
|
||||
(r"\b[A-Z][12]\d{8}\b", "***"), # 身分證字號
|
||||
]
|
||||
|
||||
|
||||
def redact(text):
|
||||
"""對文字套用全部機密遮蔽規則,回傳遮蔽後的結果。"""
|
||||
for pattern, replacement in REDACT_PATTERNS:
|
||||
text = re.sub(pattern, replacement, text)
|
||||
return text
|
||||
|
||||
|
||||
def _is_real_user_message(entry):
|
||||
"""判斷 transcript 條目是否為真正的使用者輸入(排除 tool_result 回填的 user 條目)。"""
|
||||
if entry.get("type") != "user":
|
||||
return False
|
||||
content = entry.get("message", {}).get("content")
|
||||
if isinstance(content, str):
|
||||
return bool(content.strip())
|
||||
if isinstance(content, list):
|
||||
return any(b.get("type") == "text" for b in content if isinstance(b, dict))
|
||||
return False
|
||||
|
||||
|
||||
def _blocks(entry):
|
||||
"""取出條目的 content blocks,統一為 list 形式。"""
|
||||
content = entry.get("message", {}).get("content")
|
||||
if isinstance(content, str):
|
||||
return [{"type": "text", "text": content}]
|
||||
return content if isinstance(content, list) else []
|
||||
|
||||
|
||||
def _render(entry):
|
||||
"""將單一 transcript 條目轉為摘要輸入用的純文字行(工具結果僅取前段)。"""
|
||||
role = entry.get("type")
|
||||
lines = []
|
||||
for block in _blocks(entry):
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
kind = block.get("type")
|
||||
if kind == "text":
|
||||
text = (block.get("text") or "").strip()
|
||||
if text:
|
||||
lines.append(f"[{role}] {text}")
|
||||
elif kind == "tool_use":
|
||||
name = block.get("name", "?")
|
||||
raw = json.dumps(block.get("input", {}), ensure_ascii=False)
|
||||
lines.append(f"[tool:{name}] {raw[:TOOL_INPUT_LIMIT]}")
|
||||
elif kind == "tool_result":
|
||||
raw = block.get("content")
|
||||
if isinstance(raw, list):
|
||||
raw = " ".join(
|
||||
b.get("text", "") for b in raw if isinstance(b, dict) and b.get("type") == "text"
|
||||
)
|
||||
raw = str(raw or "").strip().replace("\n", " ")
|
||||
if raw:
|
||||
lines.append(f"[result] {raw[:TOOL_RESULT_LIMIT]}")
|
||||
return lines
|
||||
|
||||
|
||||
def extract_turn(path):
|
||||
"""
|
||||
從 transcript JSONL 抽出本輪內容:最後一筆真正使用者訊息(含該筆)之後的全部條目。
|
||||
|
||||
不需任何狀態檔即可界定「本輪」,符合工作內容不落地的要求。
|
||||
回傳純文字字串;讀取失敗或無內容時回空字串。
|
||||
"""
|
||||
try:
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
entries = []
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entries.append(json.loads(line))
|
||||
except ValueError:
|
||||
continue
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
start = 0
|
||||
for index in range(len(entries) - 1, -1, -1):
|
||||
if _is_real_user_message(entries[index]):
|
||||
start = index
|
||||
break
|
||||
|
||||
lines = []
|
||||
for entry in entries[start:]:
|
||||
lines.extend(_render(entry))
|
||||
|
||||
text = "\n".join(lines).strip()
|
||||
if len(text) > TOTAL_LIMIT:
|
||||
head = text[: TOTAL_LIMIT // 2]
|
||||
tail = text[-TOTAL_LIMIT // 2 :]
|
||||
text = f"{head}\n…(中段省略)…\n{tail}"
|
||||
return text
|
||||
|
||||
|
||||
USAGE = """用法:transcript.py <子命令> [參數]
|
||||
|
||||
extract <transcript 路徑> 抽出本輪內容並遮蔽機密後輸出到 stdout
|
||||
redact 自 stdin 讀取文字,遮蔽機密後輸出到 stdout
|
||||
"""
|
||||
|
||||
|
||||
def main(argv):
|
||||
"""CLI 進入點:解析子命令並執行抽取或遮蔽。"""
|
||||
if not argv or argv[0] in ("-h", "--help"):
|
||||
print(USAGE)
|
||||
return 0
|
||||
if argv[0] == "extract":
|
||||
if len(argv) < 2:
|
||||
return 2
|
||||
text = extract_turn(argv[1])
|
||||
if not text:
|
||||
return 1
|
||||
sys.stdout.write(redact(text))
|
||||
return 0
|
||||
if argv[0] == "redact":
|
||||
sys.stdout.write(redact(sys.stdin.read()))
|
||||
return 0
|
||||
print(USAGE)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -1,434 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# ==============================================================================
|
||||
# 用途:Gitea Wiki 讀寫工具(worklog 專用)。提供 token 解析、頁面讀取、
|
||||
# 建立、append 追加(read-modify-write + 寫後驗證重試),供 worklog.sh
|
||||
# 與 /jsc:worklog skill 共用,避免兩份實作漂移。
|
||||
# 更新時間:2026/07/27 11:14:16
|
||||
# 相依:Python 3 標準庫(urllib、base64、json、re)。不需 requests、不需 jq。
|
||||
# 機密:token 一律從環境變數或本機憑證檔讀取,絕不輸出、絕不寫入任何檔案。
|
||||
# ==============================================================================
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import ssl
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
TAIPEI = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def _ssl_context():
|
||||
"""
|
||||
建立 TLS 連線設定:維持完整憑證鏈驗證,僅關閉 VERIFY_X509_STRICT。
|
||||
|
||||
Python 3.13 起預設啟用 X509 嚴格檢查,內部 CA 憑證若缺少 Subject Key
|
||||
Identifier 會被拒絕(curl 不做此檢查,故 curl 可連而 Python 不行)。
|
||||
此處只放寬擴充欄位的嚴格檢查,主機名稱與憑證鏈驗證仍完整保留。
|
||||
"""
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT
|
||||
return ctx
|
||||
|
||||
|
||||
SSL_CONTEXT = _ssl_context()
|
||||
|
||||
|
||||
def now_str():
|
||||
"""取得台灣時區的 yyyy/MM/dd HH:mm:ss 時間字串。"""
|
||||
return datetime.now(TAIPEI).strftime("%Y/%m/%d %H:%M:%S")
|
||||
|
||||
|
||||
def log(level, message, stage="wiki_api"):
|
||||
"""輸出統一格式訊息([時間][階段][等級]: 訊息,一行一則),一律走 stderr 不污染 stdout。"""
|
||||
print(f"[{now_str()}][{stage}][{level}]: {message}", file=sys.stderr)
|
||||
|
||||
|
||||
def mask(text, secret):
|
||||
"""將字串中的 secret 遮蔽為 ***,避免 token 洩漏到輸出。"""
|
||||
if not secret:
|
||||
return text
|
||||
return text.replace(secret, "***")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# token 解析:GITEA_TOKEN → tea config → git-credentials
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
def _token_from_tea(host):
|
||||
"""從 tea 設定檔取出指定 host 的 token(找不到回 None)。"""
|
||||
for path in ("~/.config/tea/config.yml", "~/.tea/config.yml"):
|
||||
f = os.path.expanduser(path)
|
||||
if not os.path.isfile(f):
|
||||
continue
|
||||
try:
|
||||
raw = open(f, encoding="utf-8").read()
|
||||
except OSError:
|
||||
continue
|
||||
for block in re.split(r"(?m)^\s*-\s+name:", raw):
|
||||
if host not in block:
|
||||
continue
|
||||
m = re.search(r"(?m)^\s*token:\s*[\"']?([A-Za-z0-9_\-]+)", block)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def _token_from_git_credentials(host):
|
||||
"""從 ~/.git-credentials(credential.helper=store)取出指定 host 的密碼作為 token。"""
|
||||
f = os.path.expanduser("~/.git-credentials")
|
||||
if not os.path.isfile(f):
|
||||
return None
|
||||
try:
|
||||
lines = open(f, encoding="utf-8").read().splitlines()
|
||||
except OSError:
|
||||
return None
|
||||
for line in lines:
|
||||
m = re.match(r"https?://([^:]+):([^@]+)@(.+)$", line.strip())
|
||||
if m and m.group(3) == host:
|
||||
return urllib.parse.unquote(m.group(2))
|
||||
return None
|
||||
|
||||
|
||||
def resolve_token(host, repo):
|
||||
"""
|
||||
依固定優先序解析可用 token,並以 GET /repos/<repo> 實際驗證權限。
|
||||
|
||||
優先序:GITEA_TOKEN → tea 設定檔該 host 的 token → ~/.git-credentials。
|
||||
回傳 (token, 來源說明);全部失敗回 (None, 說明)。
|
||||
"""
|
||||
candidates = []
|
||||
env = os.environ.get("GITEA_TOKEN")
|
||||
if env:
|
||||
candidates.append((env, "GITEA_TOKEN"))
|
||||
tea = _token_from_tea(host)
|
||||
if tea and tea != env:
|
||||
candidates.append((tea, "tea 設定檔"))
|
||||
cred = _token_from_git_credentials(host)
|
||||
if cred and cred not in (env, tea):
|
||||
candidates.append((cred, "git-credentials"))
|
||||
|
||||
if not candidates:
|
||||
return None, "找不到任何可用憑證來源"
|
||||
|
||||
for token, source in candidates:
|
||||
code, _ = _request("GET", f"https://{host}/api/v1/repos/{repo}", token, None)
|
||||
if code == 200:
|
||||
return token, source
|
||||
log("DBG", f"{source} 對 {host} 驗證失敗(HTTP {code}),改試下一個來源")
|
||||
return None, f"{len(candidates)} 個憑證來源全部驗證失敗"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# HTTP
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
def _request(method, url, token, payload):
|
||||
"""發出 Gitea API 請求,回傳 (HTTP 狀態碼, 回應內文字串)。網路層錯誤以 0 表示。"""
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8") if payload is not None else None
|
||||
req = urllib.request.Request(url, data=data, method=method)
|
||||
req.add_header("Authorization", f"token {token}")
|
||||
req.add_header("Accept", "application/json")
|
||||
if data:
|
||||
req.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30, context=SSL_CONTEXT) as resp:
|
||||
return resp.status, resp.read().decode("utf-8", "replace")
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode("utf-8", "replace")
|
||||
except Exception as e: # 網路錯誤、逾時
|
||||
return 0, str(e)
|
||||
|
||||
|
||||
def _api_base(host, repo):
|
||||
"""組出 repo 層級的 wiki API base URL。"""
|
||||
return f"https://{host}/api/v1/repos/{repo}/wiki"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# wiki 操作
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
def list_pages(host, repo, token):
|
||||
"""
|
||||
列出 wiki 全部頁面(分頁完整讀取),回傳 (狀態, 頁面清單)。
|
||||
|
||||
狀態為 'ok'/'missing'(wiki 尚未初始化)/'error'。清單元素含 title 與 sub_url。
|
||||
"""
|
||||
pages = []
|
||||
page_no = 1
|
||||
limit = 50
|
||||
while True:
|
||||
url = f"{_api_base(host, repo)}/pages?page={page_no}&limit={limit}"
|
||||
code, body = _request("GET", url, token, None)
|
||||
if code == 404:
|
||||
return "missing", []
|
||||
if code != 200:
|
||||
return "error", []
|
||||
try:
|
||||
batch = json.loads(body)
|
||||
except ValueError:
|
||||
return "error", []
|
||||
if not isinstance(batch, list):
|
||||
return "error", []
|
||||
pages.extend(batch)
|
||||
if len(batch) < limit:
|
||||
return "ok", pages
|
||||
page_no += 1
|
||||
|
||||
|
||||
def resolve_sub_url(host, repo, token, title):
|
||||
"""
|
||||
以 title 查出 Gitea 實際的 sub_url。
|
||||
|
||||
Gitea wiki 會對 title 做轉義(`-` 代表空格,實際 dash 另有轉義形式,例如
|
||||
title `Worklog-2026-07-W4` 的 sub_url 為 `Worklog-2026-07-W4.-`),因此讀寫
|
||||
一律以查表得到的 sub_url 為準,不自行猜測轉義規則。
|
||||
找不到回 None。
|
||||
"""
|
||||
status, pages = list_pages(host, repo, token)
|
||||
if status != "ok":
|
||||
return None
|
||||
for item in pages:
|
||||
if item.get("title") == title:
|
||||
return item.get("sub_url") or title
|
||||
return None
|
||||
|
||||
|
||||
def get_page(host, repo, token, page):
|
||||
"""
|
||||
讀取 wiki 頁面內容(page 可傳 title 或 sub_url,內部會自動解析)。
|
||||
|
||||
回傳 (狀態, 內容字串);狀態為 'ok'(存在)、'missing'(404,頁面或 wiki 尚未建立)、
|
||||
'error'(其他失敗,內容為遮蔽後的錯誤訊息)。
|
||||
"""
|
||||
sub_url = resolve_sub_url(host, repo, token, page) or page
|
||||
url = f"{_api_base(host, repo)}/page/{urllib.parse.quote(sub_url)}"
|
||||
code, body = _request("GET", url, token, None)
|
||||
if code == 404:
|
||||
return "missing", ""
|
||||
if code != 200:
|
||||
return "error", mask(f"HTTP {code} {body[:200]}", token)
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except ValueError:
|
||||
return "error", "回應不是合法 JSON"
|
||||
raw = data.get("content_base64") or ""
|
||||
try:
|
||||
return "ok", base64.b64decode(raw).decode("utf-8", "replace")
|
||||
except Exception:
|
||||
return "error", "content_base64 解碼失敗"
|
||||
|
||||
|
||||
def create_page(host, repo, token, page, content, message):
|
||||
"""建立新的 wiki 頁面(wiki 尚未初始化時亦由此初始化)。回傳 (是否成功, 訊息)。"""
|
||||
url = f"{_api_base(host, repo)}/new"
|
||||
payload = {
|
||||
"title": page,
|
||||
"content_base64": base64.b64encode(content.encode("utf-8")).decode("ascii"),
|
||||
"message": message,
|
||||
}
|
||||
code, body = _request("POST", url, token, payload)
|
||||
if code in (201, 200):
|
||||
return True, f"已建立頁面 {page}"
|
||||
return False, mask(f"建立頁面失敗 HTTP {code} {body[:200]}", token)
|
||||
|
||||
|
||||
def delete_page(host, repo, token, page):
|
||||
"""刪除 wiki 頁面(page 可傳 title 或 sub_url)。回傳 (是否成功, 訊息)。"""
|
||||
sub_url = resolve_sub_url(host, repo, token, page) or page
|
||||
url = f"{_api_base(host, repo)}/page/{urllib.parse.quote(sub_url)}"
|
||||
code, body = _request("DELETE", url, token, None)
|
||||
if code in (204, 200):
|
||||
return True, f"已刪除頁面 {page}"
|
||||
return False, mask(f"刪除頁面失敗 HTTP {code} {body[:200]}", token)
|
||||
|
||||
|
||||
def update_page(host, repo, token, page, content, message):
|
||||
"""整頁覆寫既有 wiki 頁面(append 由呼叫端先合併內容)。回傳 (是否成功, 訊息)。"""
|
||||
sub_url = resolve_sub_url(host, repo, token, page) or page
|
||||
url = f"{_api_base(host, repo)}/page/{urllib.parse.quote(sub_url)}"
|
||||
payload = {
|
||||
"title": page,
|
||||
"content_base64": base64.b64encode(content.encode("utf-8")).decode("ascii"),
|
||||
"message": message,
|
||||
}
|
||||
code, body = _request("PATCH", url, token, payload)
|
||||
if code in (200, 201):
|
||||
return True, f"已更新頁面 {page}"
|
||||
return False, mask(f"更新頁面失敗 HTTP {code} {body[:200]}", token)
|
||||
|
||||
|
||||
def append_entry(host, repo, token, page, header, entry, marker, retries=3):
|
||||
"""
|
||||
將條目追加到週頁尾端:讀取現有內容 → 合併 → 寫回 → 寫後讀取驗證。
|
||||
|
||||
marker 為條目內唯一字串(時間戳+session 短碼),用於驗證自己的內容確實落地;
|
||||
多個 session 同時寫入時,驗證失敗會重讀最新內容重試,避免互相覆蓋。
|
||||
回傳 (是否成功, 訊息)。
|
||||
"""
|
||||
for attempt in range(1, retries + 1):
|
||||
status, current = get_page(host, repo, token, page)
|
||||
if status == "error":
|
||||
return False, f"讀取頁面失敗:{current}"
|
||||
|
||||
if status == "missing":
|
||||
content = f"{header}\n\n{entry}\n"
|
||||
ok, msg = create_page(host, repo, token, page, content, f"worklog: 建立 {page}")
|
||||
if not ok:
|
||||
# wiki 已存在但頁面不存在時,建立可能失敗;下一輪改走更新
|
||||
log("WRN", f"第 {attempt} 次建立失敗:{msg}")
|
||||
continue
|
||||
else:
|
||||
if marker in current:
|
||||
return True, "條目已存在,無需重複寫入"
|
||||
body = current.rstrip("\n")
|
||||
if not body:
|
||||
body = header
|
||||
content = f"{body}\n\n{entry}\n"
|
||||
ok, msg = update_page(host, repo, token, page, content, f"worklog: 追加 {marker}")
|
||||
if not ok:
|
||||
log("WRN", f"第 {attempt} 次寫入失敗:{msg}")
|
||||
continue
|
||||
|
||||
verify_status, verify_content = get_page(host, repo, token, page)
|
||||
if verify_status == "ok" and marker in verify_content:
|
||||
return True, f"條目已寫入 {page}(第 {attempt} 次嘗試)"
|
||||
log("WRN", f"第 {attempt} 次寫後驗證未找到條目,準備重試")
|
||||
|
||||
return False, f"重試 {retries} 次仍未成功寫入 {page}"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 週頁命名
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
def week_page_name(when=None):
|
||||
"""依台灣時區產生週頁名稱 Worklog-yyyy-MM-W<該月第幾週>(第幾週=ceil(日/7))。"""
|
||||
when = when or datetime.now(TAIPEI)
|
||||
week = (when.day + 6) // 7
|
||||
return f"Worklog-{when.year:04d}-{when.month:02d}-W{week}"
|
||||
|
||||
|
||||
def week_page_header(page=None, when=None):
|
||||
"""產生週頁首行標題(例:# 2026 年 07 月 第 4 週工作紀錄)。"""
|
||||
when = when or datetime.now(TAIPEI)
|
||||
week = (when.day + 6) // 7
|
||||
return f"# {when.year} 年 {when.month:02d} 月 第 {week} 週工作紀錄"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# CLI
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
USAGE = """用法:wiki_api.py <子命令> [參數]
|
||||
|
||||
probe 檢查 host/repo/token/wiki API 可用性
|
||||
page-name 印出當週頁面名稱
|
||||
pages 列出全部頁面(title 與實際 sub_url)
|
||||
show [頁面] 印出指定頁面內容(預設當週頁)
|
||||
append <marker> [頁面] 自 stdin 讀取條目內容並追加(預設當週頁)
|
||||
init [頁面] 若當週頁不存在則建立(僅含標題)
|
||||
delete <頁面> 刪除指定頁面
|
||||
|
||||
環境變數:WORKLOG_HOST(必要)、WORKLOG_REPO(必要)、GITEA_TOKEN(選用,會自動 fallback)
|
||||
"""
|
||||
|
||||
|
||||
def _env():
|
||||
"""讀取並檢查必要環境變數,回傳 (host, repo);缺少時結束程式。"""
|
||||
host = os.environ.get("WORKLOG_HOST", "").strip()
|
||||
repo = os.environ.get("WORKLOG_REPO", "").strip()
|
||||
if not host or not repo:
|
||||
log("ERR", "缺少 WORKLOG_HOST 或 WORKLOG_REPO")
|
||||
sys.exit(2)
|
||||
return host, repo
|
||||
|
||||
|
||||
def main(argv):
|
||||
"""CLI 進入點:解析子命令並執行對應 wiki 操作。"""
|
||||
if not argv or argv[0] in ("-h", "--help"):
|
||||
print(USAGE)
|
||||
return 0
|
||||
|
||||
cmd = argv[0]
|
||||
|
||||
if cmd == "page-name":
|
||||
print(week_page_name())
|
||||
return 0
|
||||
|
||||
host, repo = _env()
|
||||
token, source = resolve_token(host, repo)
|
||||
if not token:
|
||||
log("ERR", f"無可用 token:{source}")
|
||||
return 2
|
||||
|
||||
if cmd == "probe":
|
||||
log("INF", f"token 來源:{source}")
|
||||
code, body = _request("GET", f"https://{host}/api/v1/version", token, None)
|
||||
log("INF", f"Gitea 版本查詢 HTTP {code} {body[:80]}")
|
||||
status, _ = get_page(host, repo, token, week_page_name())
|
||||
log("INF", f"當週頁 {week_page_name()} 狀態:{status}")
|
||||
return 0
|
||||
|
||||
if cmd == "pages":
|
||||
status, pages = list_pages(host, repo, token)
|
||||
if status != "ok":
|
||||
log("WRN" if status == "missing" else "ERR", f"頁面清單狀態:{status}")
|
||||
return 0 if status == "missing" else 1
|
||||
for item in pages:
|
||||
print(f"{item.get('title')}\t{item.get('sub_url')}")
|
||||
return 0
|
||||
|
||||
if cmd == "delete":
|
||||
if len(argv) < 2:
|
||||
log("ERR", "delete 需要頁面名稱")
|
||||
return 2
|
||||
ok, msg = delete_page(host, repo, token, argv[1])
|
||||
log("INF" if ok else "ERR", msg)
|
||||
return 0 if ok else 1
|
||||
|
||||
if cmd == "show":
|
||||
page = argv[1] if len(argv) > 1 else week_page_name()
|
||||
status, content = get_page(host, repo, token, page)
|
||||
if status == "ok":
|
||||
print(content)
|
||||
return 0
|
||||
log("WRN" if status == "missing" else "ERR", f"頁面 {page} 狀態:{status} {content}")
|
||||
return 0 if status == "missing" else 1
|
||||
|
||||
if cmd == "init":
|
||||
page = argv[1] if len(argv) > 1 else week_page_name()
|
||||
status, _ = get_page(host, repo, token, page)
|
||||
if status == "ok":
|
||||
log("INF", f"頁面 {page} 已存在,不重建")
|
||||
return 0
|
||||
ok, msg = create_page(host, repo, token, page, week_page_header(page) + "\n", f"worklog: 初始化 {page}")
|
||||
log("INF" if ok else "ERR", msg)
|
||||
return 0 if ok else 1
|
||||
|
||||
if cmd == "append":
|
||||
if len(argv) < 2:
|
||||
log("ERR", "append 需要 marker 參數")
|
||||
return 2
|
||||
marker = argv[1]
|
||||
page = argv[2] if len(argv) > 2 else week_page_name()
|
||||
entry = sys.stdin.read().strip()
|
||||
if not entry:
|
||||
log("WRN", "條目內容為空,不寫入")
|
||||
return 0
|
||||
ok, msg = append_entry(host, repo, token, page, week_page_header(page), entry, marker)
|
||||
log("INF" if ok else "ERR", msg)
|
||||
return 0 if ok else 1
|
||||
|
||||
log("ERR", f"未知子命令:{cmd}")
|
||||
print(USAGE)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 用途:工作證明自動記錄(worklog)。由 Claude Code 的 Stop hook 觸發,
|
||||
# 抽出本輪工作內容 → 呼叫模型濃縮成精簡條目 → 機密遮蔽 →
|
||||
# 追加到 Gitea wiki 的當週工作紀錄頁。工作內容全程不落地。
|
||||
# 更新時間:2026/07/27 11:14:16
|
||||
# 相依:python3、claude CLI、curl(wiki 走 Python urllib,不需 curl 亦可)。
|
||||
# 機密:token 僅由環境變數/本機憑證讀取,不 echo、不寫檔;輸出前套用遮蔽規則。
|
||||
# 退出碼:一律 0 —— hook 絕不可阻斷使用者的工作流程。
|
||||
# ==============================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STAGE="worklog"
|
||||
FALLBACK_MODEL="claude-haiku-4-5-20251001"
|
||||
MODEL_CACHE="${HOME}/.claude/worklog/model"
|
||||
CACHE_MAX_AGE_DAYS=30
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 共用函式
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
log() {
|
||||
# 輸出統一格式訊息([時間][階段][等級]: 訊息,一行一則),一律走 stderr
|
||||
local level="$1" message="$2" stamp
|
||||
stamp="$(TZ='Asia/Taipei' date +'%Y/%m/%d %H:%M:%S')"
|
||||
printf '[%s][%s][%s]: %s\n' "$stamp" "$STAGE" "$level" "$message" >&2
|
||||
if [ -n "${WORKLOG_ERRLOG:-}" ] && [ "$level" = "ERR" ]; then
|
||||
printf '[%s][%s][%s]: %s\n' "$stamp" "$STAGE" "$level" "$message" >> "${WORKLOG_ERRLOG}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
die_quiet() {
|
||||
# 記錄原因後以 0 結束:hook 不得阻斷使用者流程
|
||||
log "${2:-DBG}" "$1"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 遞迴防護:摘要用的子 claude 行程會再次觸發 Stop hook,必須在此擋掉
|
||||
# ------------------------------------------------------------------------------
|
||||
[ -n "${WORKLOG_CHILD:-}" ] && exit 0
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 啟用檢查:未設定 WORKLOG_* 的環境完全不動作(他人匯入 plugin 零影響)
|
||||
# ------------------------------------------------------------------------------
|
||||
[ "${WORKLOG_ENABLED:-}" = "1" ] || exit 0
|
||||
[ -n "${WORKLOG_HOST:-}" ] || die_quiet "未設定 WORKLOG_HOST,略過記錄" "WRN"
|
||||
[ -n "${WORKLOG_REPO:-}" ] || die_quiet "未設定 WORKLOG_REPO,略過記錄" "WRN"
|
||||
|
||||
command -v python3 >/dev/null 2>&1 || die_quiet "找不到 python3,略過記錄" "WRN"
|
||||
command -v claude >/dev/null 2>&1 || die_quiet "找不到 claude CLI,略過記錄" "WRN"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 讀取 hook 傳入的 JSON(session_id/transcript_path/cwd/stop_hook_active)
|
||||
# ------------------------------------------------------------------------------
|
||||
HOOK_INPUT="$(cat)"
|
||||
[ -n "$HOOK_INPUT" ] || die_quiet "hook 輸入為空,略過記錄" "WRN"
|
||||
|
||||
read -r SESSION_ID TRANSCRIPT_PATH STOP_ACTIVE HOOK_CWD <<EOF_HOOK
|
||||
$(printf '%s' "$HOOK_INPUT" | python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
d = json.load(sys.stdin)
|
||||
except ValueError:
|
||||
d = {}
|
||||
print(
|
||||
d.get("session_id", "-") or "-",
|
||||
d.get("transcript_path", "-") or "-",
|
||||
"1" if d.get("stop_hook_active") else "0",
|
||||
d.get("cwd", "") or "-",
|
||||
)
|
||||
')
|
||||
EOF_HOOK
|
||||
|
||||
[ "$STOP_ACTIVE" = "1" ] && die_quiet "stop_hook_active 為 true,避免迴圈不重複記錄"
|
||||
[ -f "$TRANSCRIPT_PATH" ] || die_quiet "找不到 transcript:${TRANSCRIPT_PATH}" "WRN"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 記錄範圍:WORKLOG_SCOPE 以冒號分隔的路徑前綴,未設定則全部 session 都記
|
||||
# ------------------------------------------------------------------------------
|
||||
if [ -n "${WORKLOG_SCOPE:-}" ]; then
|
||||
in_scope=0
|
||||
IFS=':' read -r -a scopes <<< "${WORKLOG_SCOPE}"
|
||||
for scope in "${scopes[@]}"; do
|
||||
case "$HOOK_CWD" in "${scope%/}"*) in_scope=1 ;; esac
|
||||
done
|
||||
[ "$in_scope" = "1" ] || die_quiet "cwd 不在 WORKLOG_SCOPE 範圍內:${HOOK_CWD}"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 專案判定:git remote 的 <owner>/<repo> 優先,其次目錄名
|
||||
# ------------------------------------------------------------------------------
|
||||
PROJECT="$(basename "$HOOK_CWD")"
|
||||
if git -C "$HOOK_CWD" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
origin="$(git -C "$HOOK_CWD" remote get-url origin 2>/dev/null)"
|
||||
if [ -n "$origin" ]; then
|
||||
cleaned="${origin%.git}"
|
||||
cleaned="${cleaned##*://}"
|
||||
cleaned="${cleaned#*@}"
|
||||
owner_repo="$(printf '%s' "$cleaned" | awk -F/ 'NF>=2 {print $(NF-1)"/"$NF}')"
|
||||
[ -n "$owner_repo" ] && PROJECT="$owner_repo"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 抽出本輪內容(最後一筆使用者訊息之後),並先做一次機密遮蔽
|
||||
# ------------------------------------------------------------------------------
|
||||
TURN="$(python3 "${SCRIPT_DIR}/transcript.py" extract "$TRANSCRIPT_PATH" 2>/dev/null)"
|
||||
[ -n "$TURN" ] || die_quiet "本輪無可記錄內容"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 模型決定:WORKLOG_MODEL → 快取檔(/jsc:worklog --tune 產生)→ 保底
|
||||
# ------------------------------------------------------------------------------
|
||||
MODEL=""
|
||||
MODEL_NOTE=""
|
||||
if [ -n "${WORKLOG_MODEL:-}" ]; then
|
||||
MODEL="${WORKLOG_MODEL}"
|
||||
elif [ -f "$MODEL_CACHE" ]; then
|
||||
if [ -n "$(find "$MODEL_CACHE" -mtime "+${CACHE_MAX_AGE_DAYS}" 2>/dev/null)" ]; then
|
||||
MODEL="$FALLBACK_MODEL"
|
||||
MODEL_NOTE=" (model: fallback)"
|
||||
log "WRN" "模型快取已超過 ${CACHE_MAX_AGE_DAYS} 天,改用保底模型,建議重跑 /jsc:worklog --tune"
|
||||
else
|
||||
MODEL="$(grep -m1 -E '^model=' "$MODEL_CACHE" 2>/dev/null | cut -d= -f2- | tr -d '[:space:]')"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$MODEL" ]; then
|
||||
MODEL="$FALLBACK_MODEL"
|
||||
MODEL_NOTE=" (model: fallback)"
|
||||
log "WRN" "無模型快取,改用保底模型,建議執行 /jsc:worklog --tune"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 濃縮:交給模型產出精簡條目(子行程帶 WORKLOG_CHILD=1 阻斷遞迴)
|
||||
# ------------------------------------------------------------------------------
|
||||
PROMPT="$(cat <<'EOF_PROMPT'
|
||||
你是工作紀錄濃縮器。輸入是一段 AI 助理與使用者的對話片段(含工具呼叫)。
|
||||
請濃縮成工作紀錄條目,規則:
|
||||
|
||||
1. 只輸出 1 到 3 個 markdown bullet(以「- 」開頭),不要標題、不要前言、不要結語。
|
||||
2. 使用繁體中文(台灣用語),每個 bullet 一行、不超過 60 字,聚焦「做了什麼、動到什麼、結果如何」。
|
||||
3. 保留關鍵事實:檔案/專案/指令/數量/分支/PR/議題編號;不要抄程式碼、不要貼指令全文。
|
||||
4. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
|
||||
5. 若這段對話沒有實質工作產出(純閒聊、純提問、僅讀取資訊而未產生結論),只輸出一行:SKIP
|
||||
EOF_PROMPT
|
||||
)"
|
||||
|
||||
SUMMARY="$(printf '%s' "$TURN" | WORKLOG_CHILD=1 timeout 45 claude -p "$PROMPT" --model "$MODEL" 2>/dev/null)"
|
||||
if [ -z "$SUMMARY" ]; then
|
||||
log "WRN" "摘要產出為空(模型 ${MODEL}),略過本輪"
|
||||
exit 0
|
||||
fi
|
||||
printf '%s' "$SUMMARY" | grep -qiE '^\s*SKIP\s*$' && die_quiet "模型判定本輪無實質工作產出"
|
||||
|
||||
# 第二道防線:對模型輸出再做一次機密遮蔽
|
||||
SUMMARY="$(printf '%s' "$SUMMARY" | python3 "${SCRIPT_DIR}/transcript.py" redact 2>/dev/null)"
|
||||
# 只保留 bullet 行,避免模型帶出多餘敘述
|
||||
SUMMARY="$(printf '%s\n' "$SUMMARY" | grep -E '^\s*[-*]\s+' | sed -E 's/^\s*[*]/-/' | head -5)"
|
||||
[ -n "$SUMMARY" ] || die_quiet "摘要不含合法條目,略過本輪" "WRN"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 組條目並追加到當週 wiki 頁
|
||||
# ------------------------------------------------------------------------------
|
||||
STAMP="$(TZ='Asia/Taipei' date +'%Y/%m/%d %H:%M:%S')"
|
||||
MARKER="worklog:$(TZ='Asia/Taipei' date +'%Y%m%d%H%M%S')-${SESSION_ID:0:8}"
|
||||
|
||||
ENTRY="$(printf '## %s — %s%s <!-- %s -->\n%s\n' "$STAMP" "$PROJECT" "$MODEL_NOTE" "$MARKER" "$SUMMARY")"
|
||||
|
||||
export WORKLOG_HOST WORKLOG_REPO
|
||||
if printf '%s' "$ENTRY" | python3 "${SCRIPT_DIR}/wiki_api.py" append "$MARKER" 2>&1 | grep -q '\[ERR\]'; then
|
||||
log "ERR" "寫入 wiki 失敗(專案 ${PROJECT})"
|
||||
else
|
||||
log "INF" "已記錄工作條目(專案 ${PROJECT},模型 ${MODEL})"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,264 @@
|
||||
---
|
||||
name: plugins-install
|
||||
description: 一次把 JSC 的四個 plugin(jsc-code/jsc-doc/jsc-persona/jsc-shared)安裝或更新到一個或多個 AI 助理,可同時處理 Claude Code、Codex、GitHub Copilot CLI、Antigravity 四家原生 plugin CLI 與沒有 plugin 匯入指令、但可使用 skill 的助理;沒指定時偵測本機裝了哪些 CLI 並讓使用者多選,每個 plugin 先判斷已安裝或未安裝,未安裝就安裝、已安裝就更新到最新。非指令助理先把技能組 clone 到工具專屬資料夾,再依技能組 README.md 將技能匯入到指定位置:已安裝就在 README 指到的路徑就地更新,未安裝才放進工具的預設資料夾,最後以「助理 × plugin」的表格回報動作、位置、結果與版本。當使用者說要安裝所有 jsc plugin、一次更新全部 skill 套件、把 code/doc/persona/shared 都裝起來、要同時更新好幾個 CLI、換新機器要把 plugin 都補齊、技能匯入到錯的地方、要更新專案自己那份 skills、或問怎麼一次更新所有 plugin 時觸發。不適用於:移除 plugin(用 /jsc-shared:plugins-uninstall)、只處理單一 plugin(直接照該 plugin README 的安裝章節)、安裝非 JSC 的第三方 plugin。
|
||||
argument-hint: "[--assistant <助理清單,逗號分隔,或 all>] [--plugins code,doc,persona,shared] [--host <gitea 主機>] [--clone-dir <目錄>] [--yes]"
|
||||
---
|
||||
|
||||
# plugins-install — 一次安裝/更新所有 JSC plugin
|
||||
|
||||
四階段 skill:先**決定助理與目標清單**,再**逐一判斷已安裝或未安裝**,接著**安裝或更新**,最後**回報結果並提醒重啟工作階段**。
|
||||
|
||||
| 階段 | 動作 |
|
||||
| --- | --- |
|
||||
| A. 前置設定 | 決定要操作哪些助理(`--assistant` 可帶多個或 all/偵測本機有哪些 CLI/多個就讓使用者多選)→ 決定 gitea 主機 → 決定 plugin 清單(預設 code、doc、persona、shared) |
|
||||
| B. 現況盤點 | 對每個 plugin 查詢 marketplace 與 plugin 是否已存在,決定「安裝」或「更新」 |
|
||||
| C. 安裝/更新 | 依助理的原生指令逐一執行;Antigravity 走 clone+本地路徑,預設把本機 clone 收在 `~/.gemini/plugins`(避免共用開發中工作區),沒有 plugin 匯入指令但可使用 skill 的助理則先 clone 技能組到工具專屬資料夾,再依 README.md 匯入到指定位置 |
|
||||
| D. 回報 | 以表格列出每個 plugin 的動作、結果與版本,並提醒重啟工作階段 |
|
||||
|
||||
---
|
||||
|
||||
## 共用規範(shared plugin,必要前置)
|
||||
|
||||
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守:
|
||||
|
||||
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼、表格呈現。
|
||||
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
|
||||
- `/jsc-shared:spec-git-safety`:Antigravity/其他會動到本機 clone 的工具路徑有未提交變更時不得強制更新。
|
||||
|
||||
本 skill 特有補充:
|
||||
|
||||
- **不移除任何東西**。更新時就算需要「先移除再安裝」(Antigravity 沒有 update 子指令),也只針對該 plugin 自己,且移除後必須立刻重裝成功。
|
||||
- **必要決策**(會中斷詢問):無法判斷目前是哪個助理、指定的助理 CLI 不存在、本機 clone 有未提交變更、安裝失敗且原因需要使用者裁示。
|
||||
- **可處理 `jsc-shared` 自己**:但更新目前正在執行本 skill 的助理時,將 `shared` 放在該助理的最後處理,並在回報中提醒重啟工作階段。
|
||||
|
||||
---
|
||||
|
||||
## 參數
|
||||
|
||||
- `--assistant <清單>`:要操作的助理,**可以多個**,以逗號分隔(`claude,codex,copilot,agy,opencode`),或用 `all` 代表本機找得到的全部。**省略時**依階段 A1 判斷(只有一個就直接用,多個就讓使用者多選)。
|
||||
- `--plugins code,doc,persona,shared`:要處理的 plugin(以逗號分隔,用 repo 短名)。**省略時預設四個全做**。
|
||||
- `--host <gitea 主機>`:gitea 主機,省略時預設 `gitea.jsc.idv.tw`。
|
||||
- `--clone-dir <目錄>`:Antigravity/其他會用到本機 clone 的工具的根目錄。`agy` 預設用 `~/.gemini/plugins`,`opencode` 預設用 `~/plugins`;若兩者同時選中而且要共用同一個 clone root,必須由使用者明確指定 `--clone-dir`。
|
||||
- `--yes`:全自動,不做確認式詢問(必要決策仍會中斷)。
|
||||
|
||||
---
|
||||
|
||||
## plugin 對照表(安裝識別的唯一依據)
|
||||
|
||||
| repo 短名 | plugin 名 | marketplace 名 | 安裝 token | repo 網址 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `code` | `jsc-code` | `code` | `jsc-code@code` | `https://<host>/plugins/code.git` |
|
||||
| `doc` | `jsc-doc` | `doc` | `jsc-doc@doc` | `https://<host>/plugins/doc.git` |
|
||||
| `persona` | `jsc-persona` | `persona` | `jsc-persona@persona` | `https://<host>/plugins/persona.git` |
|
||||
| `shared` | `jsc-shared` | `shared` | `jsc-shared@shared` | `https://<host>/plugins/shared.git` |
|
||||
|
||||
> 指令一律照這張表帶,不要用 repo 短名去猜;若 CLI 回報 marketplace 宣告名稱與表格不一致,先記錄差異,再用 CLI 實際接受的名稱完成同一個 plugin。
|
||||
|
||||
各 plugin 帶入的 skill 目錄(OpenCode 路徑會用到):
|
||||
|
||||
| repo 短名 | skill 目錄 |
|
||||
| --- | --- |
|
||||
| `code` | `action-composite`、`action-docker`、`action-node`、`image`、`issues`、`nuget`、`review-resolve`、`sync`、`target` |
|
||||
| `doc` | `docker`、`funcs`、`issues-analyze`、`issues-analyze-to-file`、`issues-sync`、`worklog` |
|
||||
| `persona` | `persona-anime`、`persona-chat`、`persona-create`、`persona-icon`、`persona-invite`、`persona-memory`、`persona-relation`、`persona-sleep`、`persona-status`、`persona-sync`、`persona-therapist`、`persona-transfer` |
|
||||
| `shared` | `plugins-install`、`plugins-uninstall`、`spec-action-params`、`spec-doc-funcs-handoff`、`spec-dockerfile`、`spec-execution`、`spec-git-safety`、`spec-gitea`、`spec-output`、`spec-plugin-version`、`spec-project-board`、`spec-time-log` |
|
||||
|
||||
---
|
||||
|
||||
## 階段 A:前置設定
|
||||
|
||||
### A1. 決定助理(可以一次多個)
|
||||
|
||||
`--assistant` 收的是**清單**,不是單一值:`--assistant claude,codex,copilot`、或 `--assistant all`。
|
||||
最終得到的是一組助理,後面每個階段都對**這組的每一個**各跑一遍。
|
||||
|
||||
依序判斷,**第一個成立的就採用**:
|
||||
|
||||
1. 有帶 `--assistant` → 照它。`all` 代表「本機找得到的全部」(等同下面第 3 點的偵測結果)。
|
||||
2. 沒帶 → 逐一檢查哪些 CLI 存在(`command -v claude codex copilot agy opencode`):
|
||||
- 找到 **1 個** → 直接用它。
|
||||
- 找到 **多個** → 列出來讓使用者**多選**(預設全選)。帶 `--yes` 時不問,直接全做。
|
||||
3. 一個都沒有 → 回報「找不到任何支援的助理 CLI」並停止。
|
||||
|
||||
> 目前正在執行本 skill 的那個助理,如果也在清單裡,**放到最後處理**;該助理內若包含 `shared`,再把 `shared` 放在該助理的最後一個 plugin 處理——更新它自己會需要重啟工作階段。
|
||||
|
||||
### A2. 決定 gitea 主機與 clone 根目錄
|
||||
|
||||
- 主機:`--host` → `$GITEA_HOST` → 預設 `gitea.jsc.idv.tw`。
|
||||
- clone 根目錄(只有 `agy`/`opencode` 用得到):`--clone-dir` → `agy` 預設 `~/.gemini/plugins`、`opencode` 預設 `~/plugins`。若兩者同時選中且未指定 `--clone-dir`,先判斷是否要拆成兩個根目錄;不要默認共用同一份開發工作區。目錄不存在就建立。
|
||||
|
||||
### A3. 決定 plugin 清單
|
||||
|
||||
`--plugins` 指定則照它,否則 `code,doc,persona,shared` 四個都做。清單中出現對照表以外的名稱 → 回報並略過該項,其餘照做。
|
||||
|
||||
---
|
||||
|
||||
## 階段 B:現況盤點
|
||||
|
||||
對清單中每個 plugin,先查現況再決定動作(**先查再做,不要盲目重裝**):
|
||||
|
||||
| 助理 | 查詢指令 | 判定 |
|
||||
| --- | --- | --- |
|
||||
| Claude Code | `claude plugin marketplace list`、`claude plugin list` | 兩者都有 → 更新;缺 marketplace → 先 add;缺 plugin → install |
|
||||
| Codex | `codex plugin marketplace list`、`codex plugin list` | 同上 |
|
||||
| GitHub Copilot CLI | `copilot plugin marketplace list`、`copilot plugin list` | 同上 |
|
||||
| Antigravity | `agy plugin list`,並看 `~/.gemini/plugins/<repo>` 是否存在(除非使用者明確指定 `--clone-dir`) | 目錄在且已安裝 → 更新;否則安裝 |
|
||||
| 無 plugin 匯入指令但可使用 skill 的助理 | 先依工具設定或 README.md 找出**所有**候選匯入位置,再看哪個底下已有該 plugin 的 skill 目錄 | 有 → **更新該位置**(重新複製;注意不是覆蓋,見階段 C);都沒有 → 安裝到工具預設資料夾 |
|
||||
|
||||
盤點的迴圈是**助理 × plugin**:階段 A1 選定的每個助理,都要對每個 plugin 各判定一次,結果分開記。
|
||||
|
||||
指令不存在或子指令不被支援(舊版 CLI)時,**不要中斷整批**:記下那一格為「跳過(CLI 不支援)」,繼續下一個,最後在階段 D 一起回報。同一個助理連續失敗(例如 CLI 存在但每個子指令都不支援)就整個助理標記為跳過,換下一個助理,不要卡住整批。
|
||||
|
||||
---
|
||||
|
||||
## 階段 C:安裝/更新
|
||||
|
||||
以下 `<url>`、`<plugin>`、`<marketplace>`、`<token>` 一律取自對照表。
|
||||
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
# 安裝(marketplace 尚未加入)
|
||||
claude plugin marketplace add <url>
|
||||
claude plugin install <token>
|
||||
|
||||
# 更新(已安裝)
|
||||
claude plugin marketplace update <marketplace>
|
||||
claude plugin update <token>
|
||||
```
|
||||
|
||||
### Codex
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
codex plugin marketplace add <url>
|
||||
codex plugin add <token>
|
||||
|
||||
# 更新(重新抓取 marketplace 的 git 快照)
|
||||
codex plugin marketplace upgrade <marketplace>
|
||||
```
|
||||
|
||||
### GitHub Copilot CLI
|
||||
|
||||
```bash
|
||||
# 安裝
|
||||
copilot plugin marketplace add <url>
|
||||
copilot plugin install <token>
|
||||
|
||||
# 更新
|
||||
copilot plugin marketplace update <marketplace>
|
||||
copilot plugin update <token>
|
||||
```
|
||||
|
||||
### Antigravity(`agy`)
|
||||
|
||||
> `agy plugin install <url>` 目前只支援 github.com;gitea 一律走「clone + 本地路徑」。`agy` 沒有 update 子指令,更新=`git pull` 後重裝。**預設 clone root 在 `~/.gemini/plugins`,不要偷用目前工作目錄或 `~/plugins` 的開發工作區。**
|
||||
|
||||
```bash
|
||||
# 取得或更新本機 clone(只看 clone root 內的 repo;新 clone 用 master,已存在就把 master 拉到最新)
|
||||
if [ -d "<clone-dir>/<repo>/.git" ]; then
|
||||
git -C <clone-dir>/<repo> switch master
|
||||
git -C <clone-dir>/<repo> pull --ff-only origin master
|
||||
else
|
||||
git clone --branch master --single-branch <url> <clone-dir>/<repo>
|
||||
fi
|
||||
|
||||
# 安裝
|
||||
agy plugin install <clone-dir>/<repo>
|
||||
|
||||
# 更新(agy 沒有 update 子指令,只能重裝)
|
||||
agy plugin uninstall <plugin>
|
||||
agy plugin install <clone-dir>/<repo>
|
||||
```
|
||||
|
||||
- **不可無條件 `git clone`**:clone 目錄已經存在(很常見——開發者自己就 clone 在那裡)時,`git clone` 會以 `fatal: destination path already exists` 中止。階段 B 的判定只看「有沒有裝進 agy」,所以「目錄在、但 agy 沒裝」這個狀態會落進安裝分支,必須靠上面的 `if` 擋掉。
|
||||
- `git -C <clone-dir>/<repo> switch master` 或 `git pull --ff-only origin master` 失敗(本機有未提交變更、分支分岔,或本機 repo 無法切到 master)→ **停在該 plugin**,回報現況讓使用者裁示,不得 `reset --hard`/`clean`,其餘 plugin 照常繼續。
|
||||
- **先確認 clone 在哪個分支**:`git -C <clone-dir>/<repo> branch --show-current`。這裡檢查的是 `clone root` 裡的 repo,不是目前工作目錄的任何專案。`agy` 的本機 clone 應以 `master` 為準;若不是,先切回 `master`,再把 `master` 拉到最新。
|
||||
|
||||
### 無 plugin 匯入指令但可使用 skill 的助理
|
||||
|
||||
> 這類助理沒有可用的 plugin 匯入指令,但可以使用 skill,因此改用**目錄安裝**。先把技能組 clone 到工具專屬資料夾,再依技能組 `README.md` 的匯入說明,把技能放到指定位置。
|
||||
> 這類助理**沒有 plugin CLI 可查安裝清單**,所以不能像四家原生 CLI 那樣「問 CLI 裝了沒」,也**不可預設就往全域目錄倒**——先讀設定或 README.md 找出它實際掛在哪,再決定要更新誰。
|
||||
|
||||
#### C-1. 先判定安裝位置(讀設定,不要臆測)
|
||||
|
||||
候選位置由近到遠如下,**只有實際存在於磁碟的才納入候選**:
|
||||
|
||||
| 順位 | 候選 skills 目錄 | 判定依據 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `<專案根>/.opencode/skills/` | 從目前工作目錄往上找到第一個含 `opencode.json`/`opencode.jsonc`/`.opencode/` 的目錄,即為專案根 |
|
||||
| 2 | `${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills/` | 工具全域資料夾(**未安裝時的預設安裝目標**) |
|
||||
| 3 | `$HOME/.claude/skills/`、`$HOME/.agents/skills/` | OpenCode 也會讀的相容來源 |
|
||||
|
||||
```bash
|
||||
# 全域設定目錄(工具資料夾)
|
||||
OC_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
|
||||
|
||||
# 從目前工作目錄往上找專案根
|
||||
proj=""; d="$PWD"
|
||||
while [ "$d" != "/" ]; do
|
||||
if [ -f "$d/opencode.json" ] || [ -f "$d/opencode.jsonc" ] || [ -d "$d/.opencode" ]; then proj="$d"; break; fi
|
||||
d="$(dirname "$d")"
|
||||
done
|
||||
|
||||
# 列出實際存在的候選 skills 目錄
|
||||
for c in ${proj:+"$proj/.opencode/skills"} "$OC_HOME/skills" "$HOME/.claude/skills" "$HOME/.agents/skills"; do
|
||||
[ -d "$c" ] && echo "$c"
|
||||
done
|
||||
```
|
||||
|
||||
判定「這個 plugin 有沒有裝在某個候選位置」,用**對照表列出的 skill 目錄名**去看:候選底下只要出現該 plugin 的任一個 skill 目錄,就算已安裝在那裡。
|
||||
|
||||
- 設定檔存在但**內容看不懂或解析失敗** → 不猜。記為「需人工確認」,非 `--yes` 時先問使用者要更新哪個位置。
|
||||
- 若設定把 skills 指到上表以外的自訂路徑,**以設定為準**,不要改回預設目錄。
|
||||
|
||||
#### C-2. 已安裝 → 到該位置就地更新
|
||||
|
||||
```bash
|
||||
# 取得或更新本機 clone(同 Antigravity,不要無條件 clone)
|
||||
if [ -d "<clone-dir>/<repo>/.git" ]; then
|
||||
git -C <clone-dir>/<repo> pull --ff-only
|
||||
else
|
||||
git clone <url> <clone-dir>/<repo>
|
||||
fi
|
||||
|
||||
# <target> = C-1 / README.md 判定出「已經有這個 plugin」的那個目錄(可能是某個專案下的 skills 目錄)
|
||||
cp -r <clone-dir>/<repo>/skills/* "<target>/"
|
||||
```
|
||||
|
||||
- **就地更新,不要另外補一份到全域目錄**:設定或 README.md 指到專案路徑就更新專案路徑;多倒一份到其他位置會造成同名 skill 兩份、之後每次更新都要記得更兩邊。
|
||||
- **多個候選位置都已安裝** → 全部更新,並在階段 D **逐列列出各自的位置**,同時提醒使用者這個 plugin 被重複安裝了,建議留一份。
|
||||
- **目標在某個 git 專案內** → 複製後會產生未提交變更。依 `/jsc-shared:spec-git-safety`:只回報「該專案有新增/異動檔案待處理」,**不代為 commit、不動既有變更**。
|
||||
|
||||
#### C-3. 未安裝 → 放進工具的資料夾
|
||||
|
||||
```bash
|
||||
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills"
|
||||
cp -r <clone-dir>/<repo>/skills/* "${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills/"
|
||||
```
|
||||
|
||||
四個候選位置都沒有這個 plugin 時,才裝進工具的全域資料夾;**不要因為當下剛好在某個專案目錄,就自作主張裝進那個專案**。
|
||||
|
||||
> **Windows PowerShell**:`cp -r A B` → `Copy-Item A B -Recurse -Force`、`~` → `$HOME`、`${XDG_CONFIG_HOME:-$HOME/.config}` → `$env:XDG_CONFIG_HOME` 沒設就用 `$HOME\.config`。
|
||||
|
||||
- **`cp -r` 不是覆蓋,是合併**:同名檔案會更新,但**上游已經刪掉的檔案會原地留著**。所以 skill 改名或移除之後,OpenCode 端會同時留著新舊兩份。要乾淨更新就先刪該 plugin 帶入的目錄再複製一次(刪法見 `/jsc-shared:plugins-uninstall` 的 OpenCode 段)。回報時不要講「已覆蓋」,講「已複製,舊檔可能殘留」。
|
||||
|
||||
---
|
||||
|
||||
## 階段 D:回報
|
||||
|
||||
以表格回報,**一個「助理 × plugin」一列**:
|
||||
|
||||
| 助理 | plugin | 動作 | 位置 | 結果 | 版本 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Claude Code | `jsc-code` | 安裝/更新/跳過 | CLI 自管快取 | ✅ 成功/⚠ 需處理/❌ 失敗 | 例 `0.0.2` |
|
||||
| Codex | `jsc-code` | 更新 | CLI 自管快取 | ✅ 成功 | 未知 |
|
||||
| OpenCode | `jsc-code` | 更新 | `~/work/app/.opencode/skills` | ✅ 成功 | `0.0.2` |
|
||||
|
||||
只處理一個助理時可以省掉「助理」欄。**處理多個時一定要有**,否則使用者看不出哪一格出問題。
|
||||
|
||||
- 「位置」欄對**非指令助理必填**(寫出實際複製到的絕對路徑),四家原生 CLI 寫「CLI 自管快取」即可。使用者要知道這次更新的是專案那份還是全域那份。
|
||||
|
||||
- 版本取自該 plugin 的 `plugin.json`。**只有 Antigravity 與 OpenCode 拿得到**(它們有本機 clone 可讀);Claude/Codex/Copilot 把 plugin 放在各自 CLI 自管的快取目錄,除非該 CLI 的 `plugin list` 印得出版本,否則一律寫「未知」,不要去猜。
|
||||
- 有任何一列不是 ✅ → 在表格下方逐項說明原因與建議動作。
|
||||
- 最後固定提醒:**安裝或更新後要重啟工作階段**才會生效;`jsc-persona` 在 Claude Code 還要用 `/hooks` 確認六個 hook 都在。
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
name: plugins-uninstall
|
||||
description: 一次把 JSC 的四個 plugin(jsc-code/jsc-doc/jsc-persona/jsc-shared)從一個或多個 AI 助理移除,可同時處理 Claude Code、Codex、GitHub Copilot CLI、Antigravity 四家原生 plugin CLI 與沒有 plugin 匯入指令、但可使用 skill 的助理;沒指定時偵測本機裝了哪些 CLI 並讓使用者多選;移除順序固定把 jsc-shared 放到最後(本 skill 就住在裡面,移除後即失效),並在動手前列出將被移除的項目與會受影響的本機資料讓使用者確認。當使用者說要移除所有 jsc plugin、把 skill 套件整組解除安裝、清掉 code/doc/persona/shared、重灌前先卸載、要同時從好幾個 CLI 移除、或問怎麼一次移除全部 plugin 時觸發。不適用於:安裝或更新(用 /jsc-shared:plugins-install)、只移除單一 plugin(直接照該 plugin README 的移除章節)、刪除人格資料或 Gitea 上的存取庫。
|
||||
argument-hint: "[--assistant <助理清單,逗號分隔,或 all>] [--plugins code,doc,persona,shared] [--keep-marketplace] [--keep-clone] [--yes]"
|
||||
---
|
||||
|
||||
# plugins-uninstall — 一次移除所有 JSC plugin
|
||||
|
||||
四階段 skill:先**決定助理與目標清單**,再**列出將被移除的項目並確認**,接著**依固定順序移除**,最後**回報結果與殘留物**。
|
||||
|
||||
| 階段 | 動作 |
|
||||
| --- | --- |
|
||||
| A. 前置設定 | 決定要操作哪些助理(`--assistant` 可帶多個或 all/偵測本機有哪些 CLI/多個就讓使用者多選)→ 決定 plugin 清單(預設四個全移) |
|
||||
| B. 盤點與確認 | 列出實際已安裝的項目、會一併移除的 marketplace 與本機 clone、以及**不會**被碰的資料,請使用者確認 |
|
||||
| C. 移除 | 依 `code` → `doc` → `persona` → `shared` 的順序逐一移除 |
|
||||
| D. 回報 | 表格回報每個 plugin 的結果,並列出刻意保留的殘留物 |
|
||||
|
||||
---
|
||||
|
||||
## 共用規範(shared plugin,必要前置)
|
||||
|
||||
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守:
|
||||
|
||||
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼、表格呈現。
|
||||
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
|
||||
- `/jsc-shared:spec-git-safety`:不破壞既有工作——本機 clone 只在使用者明確同意時刪除,且**有未提交變更就一律保留**。
|
||||
|
||||
本 skill 特有補充:
|
||||
|
||||
- **移除是不可逆的動作**:階段 B 的確認是**必要決策**,除非帶 `--yes`,否則一定要問過才動手。
|
||||
- **絕不刪除使用者資料**:`~/.claude/personas/`(人格倉庫)、`~/.roles/`、`~/.memory/`(角色與記憶)一律不動,Gitea 上的存取庫也不動。要清這些請使用者自己來。
|
||||
- **順序不可調換**:`jsc-shared` 一定最後移除。它是本 skill 的所在地,移除後本 skill 隨之失效,後面的步驟會執行不到。
|
||||
- **多個助理時的順序**:外層先跑完一個助理的四個 plugin,再換下一個助理。**目前正在執行本 skill 的那個助理排到最後**——不然它自己的 `jsc-shared` 一沒,剩下的助理就處理不到了。
|
||||
|
||||
---
|
||||
|
||||
## 參數
|
||||
|
||||
- `--assistant <清單>`:要操作的助理,**可以多個**,以逗號分隔(`claude,codex,copilot,agy,opencode`),或用 `all` 代表本機找得到的全部。**省略時**依階段 A1 判斷(只有一個就直接用,多個就讓使用者多選,且不預設全選)。
|
||||
- `--plugins code,doc,persona,shared`:要移除的 plugin(以逗號分隔,用 repo 短名)。**省略時預設四個全移**。
|
||||
- `--keep-marketplace`:只移除 plugin,保留 marketplace 登錄(之後要重裝比較快)。
|
||||
- `--keep-clone`:保留 Antigravity/OpenCode 用的本機 clone 目錄(**預設就是保留**,此旗標僅用於明示)。要刪除本機 clone 必須由使用者在階段 B 明確同意。
|
||||
- `--yes`:跳過階段 B 的確認(其他必要決策仍會中斷)。
|
||||
|
||||
---
|
||||
|
||||
## plugin 對照表(移除識別的唯一依據)
|
||||
|
||||
| repo 短名 | plugin 名 | marketplace 名 | 移除 token |
|
||||
| --- | --- | --- | --- |
|
||||
| `code` | `jsc-code` | `code` | `jsc-code@code` |
|
||||
| `doc` | `jsc-doc` | `doc` | `jsc-doc@doc` |
|
||||
| `persona` | `jsc-persona` | `jsc-plugins` | `jsc-persona@jsc-plugins` |
|
||||
| `shared` | `jsc-shared` | `shared` | `jsc-shared@shared` |
|
||||
|
||||
> **`persona` 的 marketplace 名不是 repo 名**(是 `jsc-plugins`)。移除 marketplace 時特別注意別誤刪別的登錄。
|
||||
|
||||
各 plugin 帶入的 skill 目錄(OpenCode 路徑會用到):
|
||||
|
||||
| repo 短名 | skill 目錄 |
|
||||
| --- | --- |
|
||||
| `code` | `action-composite`、`action-docker`、`action-node`、`image`、`issues`、`nuget`、`review-resolve`、`sync`、`target` |
|
||||
| `doc` | `docker`、`funcs`、`issues-analyze`、`issues-analyze-to-file`、`issues-sync`、`worklog` |
|
||||
| `persona` | `persona-anime`、`persona-chat`、`persona-create`、`persona-icon`、`persona-invite`、`persona-memory`、`persona-relation`、`persona-sleep`、`persona-status`、`persona-sync`、`persona-therapist`、`persona-transfer` |
|
||||
| `shared` | `plugins-install`、`plugins-uninstall`、`spec-action-params`、`spec-doc-funcs-handoff`、`spec-dockerfile`、`spec-execution`、`spec-gitea`、`spec-git-safety`、`spec-output`、`spec-plugin-version`、`spec-project-board`、`spec-time-log` |
|
||||
|
||||
> 上表是**寫下來當天的快照**,plugin 之後新增 skill 它不會自己更新。所以 OpenCode 的刪除**優先從本機 clone 的 `skills/` 推導清單**(見階段 C 的 OpenCode 段),clone 不在時才退回這張表,並在回報裡註明「清單可能不完整」。
|
||||
> 兩種做法都**不可用萬用字元一次掃掉整個 `skills/`**——那裡可能還有別處裝進去的 skill。
|
||||
|
||||
---
|
||||
|
||||
## 階段 A:前置設定
|
||||
|
||||
### A1. 決定助理(可以一次多個)
|
||||
|
||||
`--assistant` 收的是**清單**,不是單一值:`--assistant claude,codex,copilot`、或 `--assistant all`。
|
||||
最終得到的是一組助理,階段 B 到 D 都對**這組的每一個**各跑一遍。
|
||||
|
||||
依序判斷,**第一個成立的就採用**:
|
||||
|
||||
1. 有帶 `--assistant` → 照它。`all` 代表「本機找得到的全部」(等同下面第 2 點的偵測結果)。
|
||||
2. 沒帶 → 逐一檢查哪些 CLI 存在(`command -v claude codex copilot agy opencode`):
|
||||
- 找到 **1 個** → 直接用它。
|
||||
- 找到 **多個** → 列出來讓使用者**多選**(移除是不可逆的,**預設不全選**,要他自己勾)。
|
||||
3. 一個都沒有 → 回報「找不到任何支援的助理 CLI」並停止。
|
||||
|
||||
> 目前正在執行本 skill 的那個助理,如果也在清單裡,**放到最後處理**——移掉它自己之後,本 skill 就不存在了。
|
||||
|
||||
### A2. 決定 plugin 清單
|
||||
|
||||
`--plugins` 指定則照它,否則四個全做。無論使用者怎麼排,**實際執行順序一律重排為 `code` → `doc` → `persona` → `shared`**。
|
||||
|
||||
---
|
||||
|
||||
## 階段 B:盤點與確認
|
||||
|
||||
先查出實際狀態(`claude plugin list`/`codex plugin list`/`copilot plugin list`/`agy plugin list`,或 OpenCode 的 skills 目錄),再列出三張清單給使用者看:
|
||||
|
||||
1. **會被移除**:已安裝的 plugin、以及(未帶 `--keep-marketplace` 時)對應的 marketplace 登錄。
|
||||
2. **本機 clone**:`agy`/`opencode` 用的 clone 目錄路徑與是否有未提交變更;**預設保留**,要刪請使用者明確說。
|
||||
3. **不會被碰**:`~/.claude/personas/`(人格:身分、情緒、記憶、關係圖)、`~/.roles/`、`~/.memory/`、Gitea 上的所有存取庫。
|
||||
|
||||
清單裡沒有任何已安裝項目 → 直接回報「沒有可移除的項目」並結束。
|
||||
|
||||
未帶 `--yes` 時,**在這裡停下來等使用者確認**再進入階段 C。
|
||||
|
||||
---
|
||||
|
||||
## 階段 C:移除
|
||||
|
||||
以下 `<plugin>`、`<marketplace>`、`<token>` 一律取自對照表;帶 `--keep-marketplace` 時略過 `marketplace remove` 那行。
|
||||
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
claude plugin uninstall <token>
|
||||
claude plugin marketplace remove <marketplace>
|
||||
```
|
||||
|
||||
- 移除後檢查 `~/.claude/settings.json` 的 `enabledPlugins`,**清掉該 plugin 的殘留鍵**(留著會讓下次安裝的狀態對不上)。
|
||||
|
||||
### Codex
|
||||
|
||||
```bash
|
||||
codex plugin remove <token>
|
||||
codex plugin marketplace remove <marketplace>
|
||||
```
|
||||
|
||||
### GitHub Copilot CLI
|
||||
|
||||
```bash
|
||||
copilot plugin uninstall <token>
|
||||
copilot plugin marketplace remove <marketplace>
|
||||
```
|
||||
|
||||
### Antigravity(`agy`)
|
||||
|
||||
```bash
|
||||
agy plugin uninstall <plugin>
|
||||
```
|
||||
|
||||
- 本機 clone(`~/plugins/<repo>` 之類)**預設保留**;使用者在階段 B 明確同意才刪,且該目錄有未提交變更時一律保留並回報。
|
||||
|
||||
### 無 plugin 匯入指令但可使用 skill 的助理
|
||||
|
||||
逐一刪除該 plugin 帶入的 skill 目錄。**最可靠的做法是從本機 clone 與 README.md 推導清單**,而不是照抄上表——上表是快照,plugin 新增 skill 之後就會漏:
|
||||
|
||||
```bash
|
||||
# clone 還在:從來源目錄與 README.md 推導要刪哪些(唯一不會漏的做法)
|
||||
for s in <clone-dir>/<repo>/skills/*/; do
|
||||
rm -rf "$HOME/.config/opencode/skills/$(basename "$s")"
|
||||
done
|
||||
```
|
||||
|
||||
clone 已經不在時才退回上表,且**一個一行分開刪**:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.config/opencode/skills/docker
|
||||
rm -rf ~/.config/opencode/skills/funcs
|
||||
# …照上表逐行
|
||||
```
|
||||
|
||||
> **不要用 `{a,b,c}` 這種 brace expansion**。它有三種會靜默失效的情況:逗號後有空格(`{a, b}`)不展開、只有一個元素(`{a}`)不展開、以及在 `dash`/`sh` 底下完全不支援。三種都是「什麼都沒刪,但 `-f` 讓結束碼還是 0」,回報會變成假的 ✅。
|
||||
|
||||
> **Windows PowerShell**:`rm -rf X` → `Remove-Item X -Recurse -Force`、`~` → `$HOME`。
|
||||
|
||||
### `jsc-shared` 的收尾
|
||||
|
||||
移除 `jsc-shared` 是**最後一步**。動手前先在輸出裡寫清楚:
|
||||
|
||||
- 本 skill 與所有 `spec-*` 共用規範會一起消失;`jsc-code`/`jsc-doc` 的 skill 內文都會引用 `spec-*`,若它們還留著,之後執行會載入不到共用規範。
|
||||
- 要重新安裝,得直接照 shared 的 README(那時已經沒有 `/jsc-shared:plugins-install` 可用了):
|
||||
`claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/shared.git` → `claude plugin install jsc-shared@shared`。
|
||||
|
||||
---
|
||||
|
||||
## 階段 D:回報
|
||||
|
||||
以表格回報,**一個「助理 × plugin」一列**:
|
||||
|
||||
| 助理 | plugin | 動作 | 結果 | 備註 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Claude Code | `jsc-code` | 移除/跳過(未安裝) | ✅ 成功/⚠ 需處理/❌ 失敗 | 例:marketplace 已保留 |
|
||||
| Codex | `jsc-code` | 跳過(未安裝) | ✅ | — |
|
||||
|
||||
只處理一個助理時可以省掉「助理」欄。**處理多個時一定要有**,否則使用者看不出哪一格出問題。
|
||||
|
||||
表格下方固定列出:
|
||||
|
||||
- **刻意保留的殘留物**:本機 clone 路徑、marketplace 登錄(若帶了 `--keep-marketplace`)、人格倉庫與記憶目錄。
|
||||
- **重啟提醒**:移除後要重啟工作階段,指令與 skill 才會真正消失。
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spec-action-params
|
||||
description: JSC plugins 共用「Gitea/GitHub action 參數來源優先序」:開發 action 需要新參數時,先取 gitea/github context(composite)或 runner 注入的 GITHUB_*/GITEA_* 執行期環境變數(docker),取不到才經使用者同意新增 inputs;secrets/vars 在 action 內一律視為不可用,需要時宣告為 input 由呼叫端 workflow 傳入。當其他 skill 內文引用 spec-action-params 或 /jsc:spec-action-params、或開發 composite/docker action 需要決定參數來源時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「Gitea/GitHub action 參數來源優先序」:開發 action 需要新參數時,先取 gitea/github context(composite)或 runner 注入的 GITHUB_*/GITEA_* 執行期環境變數(docker),取不到才經使用者同意新增 inputs;secrets/vars 在 action 內一律視為不可用,需要時宣告為 input 由呼叫端 workflow 傳入。當其他 skill 內文引用 spec-action-params 或 /jsc-shared:spec-action-params、或開發 composite/docker action 需要決定參數來源時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-action-params — 共用 action 參數來源優先序
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
name: spec-doc-funcs-handoff
|
||||
description: JSC plugins 共用「串接 doc-funcs 文件化流程」規範:code 類 skill(action 標準化、Dockerfile 整理)完成主要工作後,對整個目標專案完整執行 /jsc:doc-funcs(前置可用性檢查、完整流程步驟、由使用者裁示實作方式、完成後統一時間戳)。當其他 skill 內文引用 spec-doc-funcs-handoff 或 /jsc:spec-doc-funcs-handoff、或某 skill 的最後階段要完整執行 doc-funcs 補文件並重建 README 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「串接 funcs 文件化流程」規範:code 類 skill(action 標準化、Dockerfile 整理)完成主要工作後,對整個目標專案完整執行 /jsc-doc:funcs(前置可用性檢查、完整流程步驟、由使用者裁示實作方式、完成後統一時間戳)。當其他 skill 內文引用 spec-doc-funcs-handoff 或 /jsc-shared:spec-doc-funcs-handoff、或某 skill 的最後階段要完整執行 funcs 補文件並重建 README 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-doc-funcs-handoff — 共用「串接 doc-funcs」流程
|
||||
# spec-doc-funcs-handoff — 共用「串接 funcs」流程
|
||||
|
||||
code 類 skill 完成主要工作(action 標準化、容器化、Dockerfile 整理等)後,對**整個目標專案**完整執行 `/jsc:doc-funcs` 流程,替程式碼與指令檔補文件並重建 README。
|
||||
code 類 skill 完成主要工作(action 標準化、容器化、Dockerfile 整理等)後,對**整個目標專案**完整執行 `/jsc-doc:funcs` 流程,替程式碼與指令檔補文件並重建 README。
|
||||
|
||||
## 流程
|
||||
|
||||
- **前置檢查**:先確認 doc-funcs skill 可用(`/jsc:doc-funcs`);不可用則回報並**略過本階段**,於總結標註「未文件化」。
|
||||
- 以呼叫端 skill 的目標專案根目錄為目標,執行 `doc-funcs` skill 的完整流程:判斷語言 → 掃描 function 與指令檔 → 建立 `.docs/` 草稿 → 草稿品質檢查 → 詢問使用者如何實作 → 依選擇寫回 → 保守優化 → 重建 README → 錨點檢查 → 清理草稿 → 建置/語法驗證。
|
||||
- doc-funcs 會把 `action.yml`/`Dockerfile`/`entrypoint.sh`/`docker-compose*` 等視為指令檔/CI/部署設定檔處理:補齊「用途+更新日期同一註解區塊」與逐行註解;`steps` 引用的腳本(`*.sh`/`*.ps1` 等)逐行註解;專案內各 function 補文件註解。
|
||||
- doc-funcs 的「如何實作」詢問(全部一起/逐個/其他)由使用者於該流程內裁示,呼叫端 skill **不代為決定**。
|
||||
- 完成後依 doc-funcs 規範重建根目錄 `README.md`(含台灣時區更新時間、專案列表、功能列表、使用範例)。
|
||||
- **統一時間戳**:doc-funcs 全部完成後,以完成當下的 Asia/Taipei 時間(`yyyy/MM/dd HH:mm:ss`)回頭同步呼叫端 skill 產生的各處時間戳(橫幅 step/`entrypoint.sh`/標頭註解區塊/README),**確保各處一致**(格式見 `/jsc:spec-time-log`)。
|
||||
- **前置檢查**:先確認 funcs skill 可用(`/jsc-doc:funcs`);不可用則回報並**略過本階段**,於總結標註「未文件化」。
|
||||
- 以呼叫端 skill 的目標專案根目錄為目標,執行 `funcs` skill 的完整流程:判斷語言 → 掃描 function 與指令檔 → 建立 `.docs/` 草稿 → 草稿品質檢查 → 詢問使用者如何實作 → 依選擇寫回 → 保守優化 → 重建 README → 錨點檢查 → 清理草稿 → 建置/語法驗證。
|
||||
- funcs 會把 `action.yml`/`Dockerfile`/`entrypoint.sh`/`docker-compose*` 等視為指令檔/CI/部署設定檔處理:補齊「用途+更新日期同一註解區塊」與逐行註解;`steps` 引用的腳本(`*.sh`/`*.ps1` 等)逐行註解;專案內各 function 補文件註解。
|
||||
- funcs 的「如何實作」詢問(全部一起/逐個/其他)由使用者於該流程內裁示,呼叫端 skill **不代為決定**。
|
||||
- 完成後依 funcs 規範重建根目錄 `README.md`(含台灣時區更新時間、專案列表、功能列表、使用範例)。
|
||||
- **統一時間戳**:funcs 全部完成後,以完成當下的 Asia/Taipei 時間(`yyyy/MM/dd HH:mm:ss`)回頭同步呼叫端 skill 產生的各處時間戳(橫幅 step/`entrypoint.sh`/標頭註解區塊/README),**確保各處一致**(格式見 `/jsc-shared:spec-time-log`)。
|
||||
|
||||
> 銜接方式:在呼叫端 skill 環境中以 `/jsc:doc-funcs`(或 Skill 工具)啟動 doc-funcs 流程;若該流程需參數,沿用呼叫端 skill 的目標專案根目錄。
|
||||
> 銜接方式:在呼叫端 skill 環境中以 `/jsc-doc:funcs`(或 Skill 工具)啟動 funcs 流程;若該流程需參數,沿用呼叫端 skill 的目標專案根目錄。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spec-dockerfile
|
||||
description: JSC plugins 共用「Dockerfile 六步流程」:參數處理 → 安裝套件 → 複製檔案 → 執行程序 → 縮小映像檔 → 設定入口,以多階段建置縮小最終映像、ARG 集中檔首、相依描述先 COPY 以利 layer 快取、COPY --from 逐項明列、.dockerignore、對外契約不變與自我檢查。當其他 skill 內文引用 spec-dockerfile 或 /jsc:spec-dockerfile、或需要產生/重整 Dockerfile 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「Dockerfile 六步流程」:參數處理 → 安裝套件 → 複製檔案 → 執行程序 → 縮小映像檔 → 設定入口,以多階段建置縮小最終映像、ARG 集中檔首、相依描述先 COPY 以利 layer 快取、COPY --from 逐項明列、.dockerignore、對外契約不變與自我檢查。當其他 skill 內文引用 spec-dockerfile 或 /jsc-shared:spec-dockerfile、或需要產生/重整 Dockerfile 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-dockerfile — 共用 Dockerfile 六步流程
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: spec-execution
|
||||
description: JSC plugins 共用「執行原則」:自動執行原則(簡短計畫後直接執行到完成、只在必要決策中斷)、不臆測/需人工確認、不擴及無關檔案(排除 node_modules/.git/.docs/bin/obj/第三方依賴)。當其他 skill 內文引用 spec-execution 或 /jsc:spec-execution、或執行任何 JSC skill 需要共用執行原則時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「執行原則」:自動執行原則(簡短計畫後直接執行到完成、只在必要決策中斷)、不臆測/需人工確認、不擴及無關檔案(排除 node_modules/.git/.docs/bin/obj/第三方依賴)。當其他 skill 內文引用 spec-execution 或 /jsc-shared:spec-execution、或執行任何 JSC skill 需要共用執行原則時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-execution — 共用執行原則
|
||||
|
||||
所有 JSC skills(code/doc/generic)的執行行為,一律遵守以下原則。
|
||||
所有 JSC skills(code/doc/shared)的執行行為,一律遵守以下原則。
|
||||
|
||||
## 自動執行原則
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spec-git-safety
|
||||
description: JSC plugins 共用「Git 安全操作規範」:不破壞既有工作(未提交變更先提醒、絕不 reset --hard/checkout -f/clean)、git mv 保留歷史、develop → master 後備分支選擇、pull --ff-only、保守解衝突。當其他 skill 內文引用 spec-git-safety 或 /jsc:spec-git-safety、或執行任何會操作 git 工作區/分支的 JSC skill 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「Git 安全操作規範」:不破壞既有工作(未提交變更先提醒、絕不 reset --hard/checkout -f/clean)、git mv 保留歷史、develop → master 後備分支選擇、pull --ff-only、保守解衝突。當其他 skill 內文引用 spec-git-safety 或 /jsc-shared:spec-git-safety、或執行任何會操作 git 工作區/分支的 JSC skill 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-git-safety — 共用 Git 安全操作規範
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spec-gitea
|
||||
description: JSC plugins 共用「Gitea 工具規範」:tea 或 Gitea REST API + GITEA_TOKEN 的工具選擇與可用性檢查、token 機密保護(不 echo、遮蔽、不落地)、不依賴 jq、API 呼叫慣例(分頁完整讀取、UTF-8 JSON body、實際換行)、gitea 主機決定順序。當其他 skill 內文引用 spec-gitea 或 /jsc:spec-gitea、或執行任何需存取 Gitea 的 JSC skill 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「Gitea 工具規範」:tea 或 Gitea REST API + GITEA_TOKEN 的工具選擇與可用性檢查、token 機密保護(不 echo、遮蔽、不落地)、不依賴 jq、API 呼叫慣例(分頁完整讀取、UTF-8 JSON body、實際換行)、gitea 主機決定順序。當其他 skill 內文引用 spec-gitea 或 /jsc-shared:spec-gitea、或執行任何需存取 Gitea 的 JSC skill 時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-gitea — 共用 Gitea 工具規範
|
||||
@@ -49,7 +49,7 @@ description: JSC plugins 共用「Gitea 工具規範」:tea 或 Gitea REST API
|
||||
- API base:`https://<host>/api/v1`(repo 層:`https://<host>/api/v1/repos/<owner>/<repo>`)。
|
||||
- 標頭:`Authorization: token $GITEA_TOKEN`。
|
||||
- **分頁必須完整讀取**:持續累加 `page` 直到回傳筆數 `< limit`(或回空陣列)為止,不可只取第一頁。
|
||||
- 寫入(議題描述/留言/PR body)以 **UTF-8 JSON 檔**帶入(如 `--data @body.json`);換行必須是**實際換行**,不可讓內容顯示字面 `\n`(編碼細節見 `/jsc:spec-output`)。
|
||||
- 寫入(議題描述/留言/PR body)以 **UTF-8 JSON 檔**帶入(如 `--data @body.json`);換行必須是**實際換行**,不可讓內容顯示字面 `\n`(編碼細節見 `/jsc-shared:spec-output`)。
|
||||
- API 失敗(401/403/網路錯誤)→ 回報錯誤(**遮蔽 token**)並停止;401/403 多半是 token 失效或權限不足。
|
||||
- 版本相依端點(project/column/dependency 等)先以 GET 探測(404/501 視為不支援),**不得對未確認存在的端點做寫入**。
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: spec-output
|
||||
description: JSC plugins 共用「輸出規範」:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼、優先以 Markdown 表格與 Mermaid 圖呈現。當其他 skill 內文引用 spec-output 或 /jsc:spec-output、或執行任何 JSC skill 需要語言/編碼/呈現規範時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「輸出規範」:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼、優先以 Markdown 表格與 Mermaid 圖呈現。當其他 skill 內文引用 spec-output 或 /jsc-shared:spec-output、或執行任何 JSC skill 需要語言/編碼/呈現規範時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-output — 共用輸出規範
|
||||
|
||||
所有 JSC skills(code/doc/generic)面向使用者的輸出與寫入檔案,一律遵守以下規範。
|
||||
所有 JSC skills(code/doc/shared)面向使用者的輸出與寫入檔案,一律遵守以下規範。
|
||||
|
||||
## 語言
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: spec-plugin-version
|
||||
description: JSC plugins 共用「plugin 版號規則」:三個 manifest(plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json)同步 bump 且版本一致、bump 前對照發佈分支(master)現行版本確保單調遞增、新 plugin 首發 0.0.1、一般變更 patch +1、commit 訊息用 chore(plugin 版本)。當其他 skill 內文引用 spec-plugin-version 或 /jsc:spec-plugin-version、或要調整任一 JSC plugin(code/doc/generic)的版本號時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「plugin 版號規則」:三個 manifest(plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json)同步 bump 且版本一致、bump 前對照發佈分支(master)現行版本確保單調遞增、同一 PR 只以 master 版本計算一次最終升版、新 plugin 首發 0.0.1、plugin 更名(name 欄位改變)視為新 plugin 並把版號重置為 0.0.1、一般變更 patch +1 且 master patch 到 9 後才進位 minor(0.0.9 → 0.1.0)、commit 訊息用 chore(plugin 版本)。當其他 skill 內文引用 spec-plugin-version 或 /jsc-shared:spec-plugin-version、或要調整任一 JSC plugin(jsc-code/jsc-doc/jsc-shared)的版本號時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-plugin-version — 共用 plugin 版號規則
|
||||
|
||||
調整任一 JSC plugin(code/doc/generic 等)的版本號時,一律遵守以下規則。
|
||||
調整任一 JSC plugin(`jsc-code`/`jsc-doc`/`jsc-shared` 等)的版本號時,一律遵守以下規則。
|
||||
|
||||
## 三個 manifest 同步 bump
|
||||
|
||||
@@ -22,12 +22,18 @@ description: JSC plugins 共用「plugin 版號規則」:三個 manifest(plu
|
||||
```
|
||||
|
||||
- 新版本必須**大於 master 現行版本**。工作分支(如 `develop`)可能落後或經過 revert,直接在其舊版本上 +1 會讓版本倒退(例:master 已 `0.0.4`,develop 還在 `0.0.1`,此時應 bump 至 `0.0.5` 而非 `0.0.2`)——已安裝 `0.0.4` 的助理會因版本倒退而抓不到更新。
|
||||
- 同一 PR/同一批變更只需最終一個版本;多次修改不必逐次 bump。
|
||||
- 同一 PR/同一批變更只需最終一個版本;多次修改不必逐次 bump,也不要依工作分支上的中間版本連續累加。例如 `master` 是 `0.0.7` 時,同一 PR 的最終版本是 `0.0.8`。
|
||||
- **例外:plugin 更名時本節不適用** —— 更名後的 plugin 是獨立的安裝識別,與舊名沒有版本比較關係,見下節「plugin 更名」。
|
||||
|
||||
## 版號選擇
|
||||
|
||||
- **新 plugin 首發**:`0.0.1`(即使是從 template 複製建立,也要把 template 殘留的版本改回 `0.0.1`)。
|
||||
- **一般變更**(skill 新增/修改/移除、manifest 設定調整):patch +1。
|
||||
- **plugin 更名**(manifest 的 `name` 欄位改變,例如 `jsc` → `jsc-code`):**視為新 plugin,三份 manifest 版號一律重置為 `0.0.1`**,不沿用舊名的版本序列。
|
||||
|
||||
理由:各助理以 `<plugin 名>@<marketplace 名>` 作為安裝識別鍵(例如 `jsc-code@code` 與 `jsc@code` 是兩筆獨立條目)。更名發佈後,舊 plugin 會被移除、新 plugin 為首次安裝,兩者之間**不存在版本比較**,因此不會發生版本倒退,「版本單調遞增」不適用。git 歷史雖然延續,但版號描述的是**該 plugin 識別**的演進,而非 repo 的演進。
|
||||
|
||||
更名發佈的配套動作:使用者端必須**先移除舊 plugin、再安裝新 plugin**(不是 update),並重開工作階段;`~/.claude/settings.json` 的 `enabledPlugins` 舊鍵需一併清除。
|
||||
- **一般變更**(skill 新增/修改/移除、manifest 設定調整):以 `master` 現行版本 patch +1;patch 只使用 `0` 到 `9`,只有 `master` 基準版 patch 已是 `9` 時才進位 minor 並把 patch 歸零,例如 `0.0.8 → 0.0.9 → 0.1.0`。
|
||||
- **重大改版**(skill 大規模重構、破壞相容的呼叫方式變更):minor +1、patch 歸零。
|
||||
|
||||
## 何時必須 bump
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spec-project-board
|
||||
description: JSC plugins 共用「Gitea 專案看板進度欄位規範」:欄位語意對應(分析中/待處理/進行中/待測試/已完成,以看板實際欄位名稱為準)、依需求與 TODO 勾稽結果建議欄位、先 GET 探測 project/column API(404/501 視為不支援、不對未確認端點寫入)、不往回移、不支援時改列建議清單請人工拖曳、不得新建欄位。當其他 skill 內文引用 spec-project-board 或 /jsc:spec-project-board、或需要調整 Gitea 議題所在看板欄位時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「Gitea 專案看板進度欄位規範」:欄位語意對應(分析中/待處理/進行中/待測試/已完成,以看板實際欄位名稱為準)、依需求與 TODO 勾稽結果建議欄位、先 GET 探測 project/column API(404/501 視為不支援、不對未確認端點寫入)、不往回移、不支援時改列建議清單請人工拖曳、不得新建欄位。當其他 skill 內文引用 spec-project-board 或 /jsc-shared:spec-project-board、或需要調整 Gitea 議題所在看板欄位時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-project-board — 共用 Gitea 看板進度欄位規範
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: spec-time-log
|
||||
description: JSC plugins 共用「時間戳與輸出訊息格式規範」:更新時間一律台灣時區(Asia/Taipei)固定 yyyy/MM/dd HH:mm:ss、寫成檔內固定字串、流程完成後統一同步各處時間戳;輸出訊息統一為 [yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息(等級 INF/WRN/ERR/TRC/DBG)、一行一則。當其他 skill 內文引用 spec-time-log 或 /jsc:spec-time-log、或需要產生更新時間/統一 log 格式時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
description: JSC plugins 共用「時間戳與輸出訊息格式規範」:更新時間一律台灣時區(Asia/Taipei)固定 yyyy/MM/dd HH:mm:ss、寫成檔內固定字串、流程完成後統一同步各處時間戳;輸出訊息統一為 [yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息(等級 INF/WRN/ERR/TRC/DBG)、一行一則。當其他 skill 內文引用 spec-time-log 或 /jsc-shared:spec-time-log、或需要產生更新時間/統一 log 格式時載入此 skill。單獨被使用者呼叫時,直接說明本規範內容。
|
||||
---
|
||||
|
||||
# spec-time-log — 共用時間戳與訊息格式規範
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
---
|
||||
name: worklog
|
||||
description: 工作證明自動記錄(worklog)的操作與維護 skill。搭配 Claude Code 的 Stop hook,把每輪工作內容濃縮成精簡條目並追加到 Gitea wiki 的當週工作紀錄頁(Worklog-yyyy-MM-W<週>),工作內容全程不落地。提供 --init(初始化週頁與環境變數指引)、--tune(判定並快取最適合的摘要模型)、--diagnose(診斷 hook 為何沒動作)、--append(手動補寫一筆)、--show(讀當週頁回顧)五個模式。當使用者說工作證明、工作紀錄、worklog、週報自動化、把工作內容寫到 wiki、記錄到 Gitea wiki、hook 沒有寫入 wiki、補寫工作紀錄、看本週做了什麼、重新判定摘要模型,或提到 WORKLOG_ENABLED/WORKLOG_HOST/WORKLOG_REPO/WORKLOG_MODEL/WORKLOG_SCOPE 時觸發。不適用於:Gitea 議題操作(用 doc-issues-sync/code-issues)、專案文件化(用 doc-funcs)。
|
||||
---
|
||||
|
||||
# worklog — 工作證明自動記錄
|
||||
|
||||
把「每輪做了什麼」濃縮成一則條目,追加到 Gitea wiki 的當週工作紀錄頁。**自動記錄由 Claude Code 的 `Stop` hook 完成,不需使用者同意、不需人工觸發**;本 skill 負責自動路徑之外的人工操作:初始化、模型判定、診斷、補寫、回顧。
|
||||
|
||||
| 元件 | 觸發者 | 職責 |
|
||||
| --- | --- | --- |
|
||||
| `hooks/hooks.json` 的 `Stop` hook | harness 自動 | 每輪結束抽本輪內容 → 濃縮 → 遮蔽 → 追加到當週頁 |
|
||||
| 本 skill `/jsc:worklog` | 使用者/助理手動 | `--init`/`--tune`/`--diagnose`/`--append`/`--show` |
|
||||
| `scripts/worklog/worklog.sh` | 上述兩者共用 | 主流程(單一實作,避免漂移) |
|
||||
| `scripts/worklog/wiki_api.py` | 上述兩者共用 | token 解析、wiki 讀寫、append 重試、週頁命名 |
|
||||
| `scripts/worklog/transcript.py` | 上述兩者共用 | 抽本輪片段、機密遮蔽 |
|
||||
|
||||
> `Stop` hook **只有 Claude Code 支援**。Codex/Antigravity/OpenCode 匯入本 plugin 時,只有 skill 可用,自動記錄不會啟動。
|
||||
|
||||
---
|
||||
|
||||
## 共用規範(必要前置)
|
||||
|
||||
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到時先詢問使用者是否安裝 generic plugin(`https://gitea.jsc.idv.tw/plugins/generic.git`),不安裝則中斷**:
|
||||
|
||||
- `/jsc:spec-output`:繁體中文(台灣用語)、UTF-8 無 BOM、表格與 Mermaid 優先、**寫入外部系統不得洩漏 PII**。
|
||||
- `/jsc:spec-execution`:自動執行原則(必要決策才中斷)、不臆測。
|
||||
- `/jsc:spec-gitea`:token 機密保護(不 echo、遮蔽、不落地)、API 分頁、host 決定順序。
|
||||
- `/jsc:spec-time-log`:時間戳固定 Asia/Taipei `yyyy/MM/dd HH:mm:ss`;訊息格式 `[時間][階段][等級]: 訊息`、一行一則。
|
||||
|
||||
本 skill 特有補充:
|
||||
|
||||
- **工作內容不落地**:transcript 片段以 pipe 傳遞、wiki 走 API 不 clone,全程不產生暫存檔。唯一允許落地的是**模型快取檔** `~/.claude/worklog/model`(僅含模型 id 與判定時間,不含任何工作內容)。
|
||||
- **絕不阻斷**:hook 路徑任何失敗都以 exit 0 結束,只在 stderr 留訊息。
|
||||
|
||||
---
|
||||
|
||||
## 環境變數
|
||||
|
||||
| 變數 | 必要 | 說明 | 未設定 |
|
||||
| --- | --- | --- | --- |
|
||||
| `WORKLOG_ENABLED` | ✅ | 總開關,設為 `1` 才啟用 | hook 立即結束,完全不動作 |
|
||||
| `WORKLOG_HOST` | ✅ | Gitea 主機,如 `gitea.housefun.com.tw` | 不啟用 |
|
||||
| `WORKLOG_REPO` | ✅ | wiki 所在 repo,如 `H3285/WorkLog` | 不啟用 |
|
||||
| `WORKLOG_MODEL` | | 強制指定摘要模型 | 讀快取檔 → 保底 `claude-haiku-4-5-20251001` |
|
||||
| `WORKLOG_SCOPE` | | 冒號分隔的路徑前綴,僅這些路徑下的 session 才記 | 全部 session 都記 |
|
||||
| `WORKLOG_ERRLOG` | | 錯誤訊息額外寫入的檔案路徑(只記錯誤、不含工作內容) | 只走 stderr |
|
||||
|
||||
**token 不需另設變數**,依固定優先序自動解析並實際驗證:
|
||||
|
||||
```
|
||||
GITEA_TOKEN →(對目標 host 驗證失敗時)→ tea 設定檔中該 host 的 token → ~/.git-credentials
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模式
|
||||
|
||||
### `--init`
|
||||
|
||||
1. 執行 `python3 scripts/worklog/wiki_api.py probe`,回報 token 來源、Gitea 版本、當週頁狀態。
|
||||
2. 當週頁不存在 → 執行 `wiki_api.py init` 建立(wiki 尚未初始化時一併初始化)。
|
||||
3. 以表格印出應寫入 `~/.bashrc` 的 `WORKLOG_*` 變數清單;**不自動改使用者的 shell profile**(需人工確認的狀態變更)。
|
||||
|
||||
### `--tune`
|
||||
|
||||
決定「目前最適合的摘要模型」並快取,`Stop` hook 只讀快取、**絕不自行呼叫 AI 判斷**(否則就變成雞生蛋,還會拖慢使用者的等待路徑)。
|
||||
|
||||
| 步驟 | 動作 |
|
||||
| --- | --- |
|
||||
| 1 | 以 Skill 工具載入 `claude-api` 取當下模型清單與定價,**不憑記憶** |
|
||||
| 2 | 依本任務條件評分:延遲敏感(在使用者等待路徑上)、輸出極短(1~3 行中文)、需嚴守機密過濾指令、每輪都跑一次故成本敏感 |
|
||||
| 3 | Smoke test:`WORKLOG_CHILD=1 claude -p "回 OK" --model <選定 id>` 確認該模型在此帳號可用 |
|
||||
| 4 | 寫入 `~/.claude/worklog/model`(`model=<id>`、`tuned_at=<時間>`、`reason=<一行理由>`),並回報選擇與理由 |
|
||||
|
||||
快取超過 **30 天** 視為過期:hook 改用保底模型,並在條目標記 `(model: fallback)`,`--diagnose` 會提醒重跑 `--tune`。
|
||||
|
||||
### `--diagnose`
|
||||
|
||||
逐項檢查並以表格回報,用於「hook 沒有寫入 wiki」時定位:
|
||||
|
||||
| 檢查項 | 判準 |
|
||||
| --- | --- |
|
||||
| `python3`/`claude` CLI | `command -v` 是否找得到 |
|
||||
| `WORKLOG_*` 變數 | 必要三項是否齊全、`WORKLOG_SCOPE` 是否把當前路徑排除 |
|
||||
| token | `wiki_api.py probe` 的 token 來源與驗證結果 |
|
||||
| wiki API | Gitea 版本、`repos/<repo>` 與當週頁狀態 |
|
||||
| 模型快取 | 是否存在、是否過期、目前會用哪支模型 |
|
||||
| hook 註冊 | `hooks/hooks.json` 是否存在且 plugin 已啟用 |
|
||||
|
||||
### `--append "<內容>"`
|
||||
|
||||
手動補寫一筆(hook 漏記、離線工作、或事後補充)。條目格式與自動路徑一致:
|
||||
|
||||
```
|
||||
## <時間> — <專案> <!-- worklog:<時間戳>-manual -->
|
||||
- <內容>
|
||||
```
|
||||
|
||||
專案取當前工作目錄的 `<owner>/<repo>`;內容仍會過 `transcript.py redact` 遮蔽後才寫入。
|
||||
|
||||
### `--show`
|
||||
|
||||
讀當週頁(`wiki_api.py show`)並以表格摘要本週工作,用於回顧與週報。
|
||||
|
||||
---
|
||||
|
||||
## 條目與頁面格式
|
||||
|
||||
- 週頁名稱:`Worklog-<yyyy>-<MM>-W<該月第幾週>`,第幾週 = `ceil(日/7)`(例:`2026/07/27` → `Worklog-2026-07-W4`)。
|
||||
- 頁首標題:`# <yyyy> 年 <MM> 月 第 <週> 週工作紀錄`。
|
||||
- 每筆條目:`## <時間> — <專案>` + 1~3 個 bullet;標題行尾帶 HTML 註解 marker(`<!-- worklog:… -->`)供寫後驗證與去重,wiki 渲染時不顯示。
|
||||
- 多 session 同時寫入:`append_entry` 採「讀取 → 合併 → 寫回 → 寫後讀取驗證 marker」,未落地則重讀最新內容重試,最多 3 次。
|
||||
|
||||
---
|
||||
|
||||
## 機密與 PII(兩道防線)
|
||||
|
||||
| 防線 | 位置 | 內容 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 濃縮提示詞 | 明令不得輸出 token/密碼/API key/連線字串/Email/電話/姓名/身分證號 |
|
||||
| 2 | `transcript.py redact` | 正則遮蔽:URL 內嵌憑證、40 字元 hex token、`gh?_`/`sk-` token、`token=`/`password=`、`Authorization:`、Email、台灣手機、身分證號 |
|
||||
|
||||
第二道防線不可移除 —— 模型有可能沒遵守指令,而 wiki 一旦寫入就留在 git 歷史裡。
|
||||
|
||||
---
|
||||
|
||||
## 呼叫方式
|
||||
|
||||
| 助理 | 呼叫 |
|
||||
| --- | --- |
|
||||
| Claude Code / Antigravity | `/jsc:worklog --init`、`/jsc:worklog --tune`、`/jsc:worklog --diagnose`、`/jsc:worklog --append "修正 X 的 Y 問題"`、`/jsc:worklog --show` |
|
||||
| Codex | `$worklog --diagnose`,或用 `/skills` 選單 |
|
||||
| OpenCode | 描述需求(如「幫我看這週的工作紀錄」)自動觸發 |
|
||||
Reference in New Issue
Block a user