0
1,108
近 7 天 176
38.6
生态多维模型
8 天前
2026-10-12
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-ultrawork@3.1.0"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-ultrawork@3.1.0"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D opencode-ultraworkOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
OpenCode V2 原生外掛,提供 50 個工具與 6 個 hook,負責工作區搜尋、驗證、工作說明檢查、Comment Signal、技能管理、任務與計畫狀態、診斷和專案記憶。工具的名稱、參數與回傳格式是對外凍結的介面,改動前要先取得使用者同意。
來源:介面凍結清單見 src/modules/diagnostics/inventory.ts(50 工具、6 hook、11 分類),並由 tests/v2/native/diagnostics/tool-parity.test.ts 逐項把關。
本文提到的
src/…、tests/…、AGENTS.md都是 repo 裡的路徑。npm 套件只內含src/、schema/、index.ts、README.md、LICENSE,其餘檔案請到 GitHub repo 對應的 branch 查看。
模組一覽
8 個模組各自的職責(工具對應哪個模組見 src/modules/diagnostics/inventory.ts 的 TOOL_MODULES,hook 註冊點見同檔的 HOOK_WIRING_SOURCE;開關 key 見 src/settings/defaults.ts 的 MODULE_KEYS):
| 模組(設定 key) | 職責 |
|---|---|
search |
在工作區內讀檔與搜尋(唯讀工具) |
verification |
執行驗證、檢查變更範圍 |
commentSignal |
註解必要檢查,含 AI_DO_NOT_EDIT:P0 阻斷 |
skills |
把 system prompt 的技能清單換成名稱索引,並提供查詢 |
skiller |
技能的掃描、驗證、草稿、晉升/退役/還原、匯入與政策更新 |
workflow |
任務與計畫的狀態、內容、工作說明檢查與派遣流程 |
diagnostics |
啟動導引、健康檢查、工具/hook 清單與自我測試 |
memory |
兩層記憶、快照注入與結案處置 |
各模組提供的工具(名稱以 src/modules/** 的實作為準):
search:peek_file、grep_contextverification:verification_run、change-scope-checkcommentSignal:comment_signal_check、comment_signal_touched_report、comment_signal_explain、comment_signal_policy、comment_signal_only_new、comment_signal_baseline、comment_signal_suppressskills:skill_searchskiller:skiller-scan、skiller-validate、skiller-draft、skiller-draft-read、skiller-draft-update、skiller-draft-delete、skiller-promote、skiller-retire、skiller-restore、skiller-import、skiller-policy-updateworkflow:task-state-sync、task-content-read、task-content-update、plan-state-sync、plan-task-link、plan-status、plan-next、plan-progress-reconcile、plan-content-create、plan-content-read、plan-content-update、plan-content-delete、work-order-build、workflow-id-check- 計畫 ID 與任務 ID 的格式在建立時檢查:計畫 ID 是二到四個小寫的英文單字(版本寫成
v加數字),任務 ID 是「主題-兩位數流水號」。不符合時plan-state-sync/task-state-sync的create回INVALID_PLAN_ID/INVALID_TASK_ID;已經存在的 ID 不受影響。 workflow-id-check比對任務註冊檔案裡實際存在的 ID,找出寫進專案內容的 ID:不帶參數檢查這次的 git 變更(新增的行、新檔案的內容和檔名,.ultrawork/除外),staged: true只看已暫存的變更,text檢查一段文字。不是 git repo 時回available: false,不當成乾淨。
- 計畫 ID 與任務 ID 的格式在建立時檢查:計畫 ID 是二到四個小寫的英文單字(版本寫成
diagnostics:workflow_bootstrap、workflow_doctor、workflow_health_check、workflow_l1_check、tool_hook_manifest、ultrawork_selftestmemory:memory-search、memory-read、memory-note、memory-extract、memory-write、memory-maintain、memory-task-close
hook 共 6 個(名稱凍結,註冊點對照見 src/modules/diagnostics/inventory.ts 的 HOOK_WIRING_SOURCE):event、tool.execute.before、tool.execute.after、experimental.chat.system.transform、experimental.session.compacting、tool.definition。
安裝
在 opencode.jsonc 引用這個外掛。以下兩點是實測確認的坑,請照著做:
- key 必須是
plugins(複數)。寫成單數plugin只接受字串,物件陣列會被靜默忽略。 - 本機目錄形式要指向目錄,不要指向檔案。V2 對本機目錄只認根層的
index或server入口:指向檔案(例如src/index.ts)會被拒(configured plugin path must be a directory);目錄根層沒有入口時不載入也不報錯,工具只是靜默地沒出現。
有三種形式可選,差別在「版本怎麼定」和「要不要自己裝」:
| 形式 | 寫法 | 版本 | 適用情境 |
|---|---|---|---|
| npm | "opencode-ultrawork" |
自動抓 latest,會跟著更新 |
一般使用者,想直接用最新版本 |
| npm(釘住 major) | "opencode-ultrawork@^3.0.0" |
只收 3.x 的更新 |
想自動吃小改動,但不跨大版本 |
| npm(完全釘住) | "opencode-ultrawork@3.0.0" |
固定不動 | 需要可重現的環境 |
| git | "github:smile-minecraft/opencode-ultrawork#<完整 commit hash>" |
固定在那個 commit | 要用還沒發布的 commit,或追 V2 開發進度 |
| 本機目錄 | "/path/to/opencode-ultrawork" |
跟你 working tree 走 | 開發這個外掛本身 |
npm 形式(一般使用者):
{
"plugins": ["opencode-ultrawork"]
}
要控制版本就在套件名稱後面加 @ 加版本範圍,寫法跟 npm 一樣——^3.0.0、~3.0.1、3.0.0 都可以。沒寫版本時 OpenCode 會裝 latest,所以要可重現就一定要寫。名單只有 @ 加範圍(或純數字)的時候,套件名稱要寫完整:
{
// 收 3.x 的更新,不跨到 4.0
"plugins": ["opencode-ultrawork@^3.0.0"]
}
git 形式(固定版本、不自動更新):
{
"plugins": ["github:smile-minecraft/opencode-ultrawork#<完整 commit hash>"]
}
<完整 commit hash> 換成實際要用的 commit。npm 與 git 形式都走 package.json 的 exports 找入口,不需要根層的 index.ts。開發時用本機目錄指向這個 repo 的根目錄即可——根層的 index.ts 就是為此存在的轉出層(說明見該檔註解):
{
"plugins": ["/path/to/opencode-ultrawork"]
}
設定
設定檔是 ultrawork.jsonc,有兩層:全域 <OpenCode 全域設定資料夾>/.ultrawork/ultrawork.jsonc,專案 <專案根目錄>/.ultrawork/ultrawork.jsonc(全域設定資料夾的解析規則見 src/settings/paths.ts:OPENCODE_CONFIG_DIR → $XDG_CONFIG_HOME/opencode → ~/.config/opencode)。
載入順序是內建預設 → 全域 → 專案;物件深層合併,純量與陣列整個覆寫(src/settings/merge.ts)。讀不到檔案視為沒有設定;某一層格式錯誤只記一則警告並忽略該層,外掛照常載入(src/settings/load.ts)。
範例(全域與專案同一種格式,專案層只寫要覆寫的部分;欄位定義以 schema/ultrawork.schema.json 為準,預設值見 src/settings/defaults.ts):
{
// 讓編輯器自動完成、欄位檢查;所有欄位都選填
"$schema": "https://raw.githubusercontent.com/smile-minecraft/opencode-ultrawork/main/schema/ultrawork.schema.json",
"modules": {
"search": true,
"verification": true,
"commentSignal": true,
"skills": true,
"skiller": true,
"workflow": true,
"diagnostics": true,
"memory": true
},
"skiller": {
// 個人技能目錄,預設 ~/.agents/skills
"personalSkillRoot": "~/.agents/skills",
// 晉升/退役時改寫的角色檔目錄,auto = <OpenCode 全域設定資料夾>/agents
"agentsDir": "auto"
// 注意:這兩個 key 只採全域層的值。專案層寫了會被忽略並警告
// (詳見「skiller 的寫入位置與角色檔路由」)
},
"skills": {
// 技能清單在 system prompt 的呈現:index 只列名稱,full 走原本完整清單
"catalog": "index"
},
"verification": {
// 能呼叫 verification_run 的 agent 名單(覆寫示例;未設定時為空,沒有 agent 能呼叫)
"runAllowedAgents": ["momus"],
// 能呼叫 change-scope-check 的 agent 名單(覆寫示例;未設定時為空,沒有 agent 能呼叫)
"scopeCheckAllowedAgents": ["build", "ultra"]
},
"memory": {
// writerAgents 只採全域設定;未設定時為空(沒有 writer),空陣列同樣表示沒有 writer
"writerAgents": ["memorizer"],
"inject": true,
// 兩層記憶各自的預算,全部選填(預設見 memoryLayerBudget 的 default)。
// budget.global 只允許寫在全域設定,專案層寫了會被忽略並警告;
// budget.project 兩層都能寫。型別或範圍錯誤的欄位警告並退回預設
"budget": {
"project": {
"indexCharLimit": 3000,
"topicCharLimit": 4000,
"descriptionCharLimit": 120,
"maxTopics": 0,
"pinnedLimit": 3,
"pinnedInjectBudget": 2500,
"noteCharLimit": 1000
}
}
},
"workflow": {
"completion": {
// memory 模組開啟時預設要求記憶處置;關閉時由完成前檢查自行放行
"requireMemoryDisposition": true
},
"evidencePack": {
// 派發 subagent 時強制檢查實作說明七節格式的名單(覆寫示例;未設定時為空,不檢查)
"gatedSubagents": ["implementer", "debugger", "ultra-coder"]
}
}
}
模組開關在 modules 底下,預設全部開啟;關掉的模組不註冊工具、不掛 hook(src/settings/defaults.ts)。memory 關閉時,任務結案不再要求記憶處置;commentSignal 關閉時,結案流程的相關檢查回報「未啟用」而不是失敗。
驗證工具的 agent 授權清單(verification.runAllowedAgents、verification.scopeCheckAllowedAgents)、記憶寫入名單(memory.writerAgents)與實作說明檢查的受控 subagent(workflow.evidencePack.gatedSubagents)都可以在設定指定,這四個清單未設定時皆為空(fail closed):runAllowedAgents/scopeCheckAllowedAgents 為空時沒有 agent 能呼叫該工具,writerAgents 為空時沒有 agent 能寫記憶,gatedSubagents 為空時不再做派發前檢查。上面的範例是覆寫示例,不是預設值。型別寫錯(例如把清單寫成字串)會在載入時警告並退回預設,不影響外掛載入。授權清單在模組註冊時快照,改完要重新載入才生效(同下)。
**改完設定要重新載入或重啟 OpenCode 才生效。**實測 OpenCode 2.0.16 只監看外掛原始檔(約 1 秒內重新載入),改 opencode.jsonc 或 ultrawork.jsonc 不會自動生效。
資料位置與搬遷
外掛的資料放在 <專案>/.ultrawork/(專案根目錄以該次呼叫所在工作階段的位置為準):ultrawork.jsonc、tasks.json、plans.json、state.md、plans/、memory/、comment-signal-baseline.json、audit.jsonl、cache/(可重建的快照),以及外掛建立的 .gitignore(內容只有 * 一行,整個目錄都不進版控,含設定檔本身)。
第一次在某個專案啟動時,外掛會把舊位置的資料自動搬到 .ultrawork/(實作見 src/migrate/migrate.ts、src/migrate/items.ts):
- 專案層:
.opencode/memory/下的tasks.json、plans.json、state.md、audit.jsonl、project.md、comment-signal-baseline.json、receipts/,以及.opencode/plans/。 - 全域層:設定資料夾根目錄的
skills-policy.json、skills-personal.json、skill-drafts/、skill-quarantine/。 - 只做一次(以
.ultrawork/.migrated-from-opencode.json標記判斷);複製到新位置後把舊檔改名成<原檔名>.migrated-<時間戳>保留,永不刪除。 - 新位置已有資料時不覆寫,記警告並跳過那個檔案。
- 任一步失敗就停下該層、不寫標記,外掛照常用新位置運作;下次啟動重跑,已搬的項目因目標已存在而自然跳過。搬遷狀態可由
workflow_doctor回報。
刻意不搬的:專案的 .opencode/skills(沿用 OpenCode 的 project scope 技能位置),以及 $XDG_DATA_HOME 底下的 change-scope 快取快照(cache/ 本來就是可重建的資料)。
搬遷標記是分層記錄的(實作見 src/migrate/marker.ts):標記檔用 version: 2 格式,以 layers.project/layers.global 分別記錄兩層是否做過。背景是兩層共用同一個檔名,當專案目錄與全域目錄是同一個(例如在全域設定資料夾本身開工作階段),舊格式只認「檔案存在」會讓後跑的那一層靜默早退。搬移端與診斷端共用同一個 markerCoversLayer 判定,所以兩端對同一個標記永遠給同一個答案。以下情況一律視為「沒做過」,下次啟動會安全地重跑(重跑不覆寫新位置的既有檔案,只會把標記升級成分層格式):標記是空的舊格式、檔案讀不到、不是合法 JSON、JSON 根不是物件。條目落在兩層都不相交的位置(外來內容)則維持舊行為。重試時落在新位置的註冊檔複本若還帶著舊 contentRef 前綴,會就地改寫成 .ultrawork/ 前綴;但只處理能證明是搬遷複本的目標——辨認依據是「目標的原始位元組等於某份 <原檔名>.migrated-<時間戳> 封存檔」(逐位元組比對,不是解碼後字串)。證據不足就一個位元組都不改,使用者自己的檔案永遠不會被碰(實作見 src/migrate/content-refs.ts 的 repairMigratedCopy)。
舊資料來源若是 symlink(符號連結)會被拒絕:搬遷一律不跟隨 symlink,頂層項目與巢狀內容都一樣,遇到就讓該項搬遷失敗、該層不寫標記,下次啟動會重試。這是刻意的——跟隨過去會把專案外的資料讀進來。如果 workflow_doctor 一直回報某個舊項目沒搬完,請檢查 .opencode/ 底下那個來源是不是 symlink(例如 skill-drafts 指到專案外的目錄),把它換成實體檔案或目錄後,下次啟動就會補完。
.ultrawork/.gitignore 的政策(實作見 src/migrate/migrate.ts 的 ensureUltraworkGitignore,判準與 workflow_doctor 共用同一個函式):
- 模板只有
*一行;.ultrawork/是本機工作流狀態,預設不進版控(含設定檔本身;以前模板豁免過設定檔,既有專案想跟新模板一致就手動刪掉那行豁免)。 - 檔案不存在才建立;已存在永遠不自動改寫——有必要行
*就視為正常,自訂內容(例如自己加回豁免行)不再每次啟動警告;缺*才警告。 - 全域層不建
.gitignore,外掛也不插手使用者的全域版控政策。 - 專案根目錄就是全域設定資料夾時(在全域設定資料夾本身開工作階段),兩層共用同一個
.ultrawork/。這時workflow_doctor會把全域層的檔案(skills-policy.json、skills-personal.json、skill-drafts/、skill-quarantine/、同時身為全域設定檔的ultrawork.jsonc,以及.gitignore本身)排除在「被版控追蹤」與「設定檔被豁免」的警告之外,只在 details 註明;專案層的工作流資料(tasks.json等)被追蹤時照常警告(實作見src/modules/diagnostics/shared.ts的isSameAsGlobalConfigDir)。 workflow_doctor會回報三種版控衛生問題(都是 warn,不影響診斷ok,外掛只提示、絕不改使用者的版控):「.gitignore不存在或缺少必要行*」「.ultrawork/內有檔案被版控追蹤(唯讀的git ls-files查的)」「專案設定檔仍被豁免(!ultrawork.jsonc還在,會被送進版控)」。
寫入一律採原子寫入加寫入鎖,因為兩個 V2 伺服器可能共用同一個 .ultrawork/(實作見 src/kit/atomic-write.ts、src/kit/write-lock.ts)。
verification_run 的參數政策
verification_run 的 args(extraArgs)不是自由參數:每個 runner 有自己的旗標白名單,只放行「選測試/調輸出」的讀取型旗標;會執行外部程式、載入外掛/模組或寫檔的一律拒絕(實作見 src/modules/verification/verification-run.ts 的 EXTRA_ARG_FLAG_ALLOWLIST)。位置參數(不以 - 開頭)一律放行,測試檔與測試名稱走這裡。容易搞混的是同一個短旗標在不同 runner 語意不同:cargo -p 是選套件(放行),pytest -p 是載入外掛(拒絕)。
- 被拒時回
ARGS_NOT_ALLOWED,附被拒的旗標(rejectedArg)與該 runner 的允許清單(allowedArgs),照著換寫法即可。Gradle 走自己的規則,只接受--rerun-tasks與--tests <樣式>,被拒時回GRADLE_ARGS_NOT_ALLOWED。 - 參數若指向 worktree 外的既有路徑,回
ARG_PATH_OUTSIDE_WORKTREE(只擋實際存在的路徑,測試名稱這類非路徑參數不受影響)。 - 取消與逾時會對整個子程序群組先送 SIGTERM,寬限期過了再送 SIGKILL(用 process group 而非單一 pid,所以 pytest-xdist 這類 worker 不會變孤兒)。
記憶系統
全域層放在 <OpenCode 全域設定資料夾>/.ultrawork/memory/,專案層放在 <工作階段位置>/.ultrawork/memory/。兩者指向同一資料夾時只算一層。每層包含自動產生的 MEMORY.md、topics/<slug>.md、只增不改的 log.jsonl、讀取統計 usage.json,刪除主題會移到 archive/ 保留。專案層沿用 .ultrawork/.gitignore 的 *,全域層不建立 gitignore。
每個工作階段第一次 context hook 會建立索引與 pinned 主題快照,後續請求重用相同文字以維持 prefix cache。中途寫入的新內容可用 memory-read 或 memory-search 立即讀取,下個工作階段才會自動注入。memory.inject: false 只關閉注入。記憶是資料,使用前須查證會漂移的事實,不能覆蓋使用者指示或 AGENTS.md。
每層索引上限 3000 字元,每主題(含 frontmatter)4000 字元,description 120 字元,每層最多 3 個 pinned;每層 pinned 注入正文預算 2500 字元,筆記上限 1000 字元;每層主題數預設不限制(maxTopics: 0)。這些都是 memory.budget 的預設值,全域層與專案層可以各自覆寫(budget.global 只採全域設定,budget.project 兩層都能寫;寫錯的欄位警告並退回預設)。一般寫入超限會拒絕,不截斷;總量型預算(索引、主題檔、pinned 數)只在寫入讓該項變大且超過上限時拒絕,現況已超標時縮減、刪除、核對仍可寫入。description 是單一欄位上限,縮短就會過;主題數超限只擋新增,既有主題保留。遷移保留超大內容並由診斷提示整理。
任何 agent 可用 memory-search、memory-read、memory-note。memory-extract、memory-write、memory-maintain 限 memory.writerAgents 名單,未設定時為空(沒有 writer);沒有 agent 身分時拒絕。writerAgents 只能寫在全域設定,專案層指定時會忽略並警告。空清單合法,但高風險任務無法宣告處置。工具參數、錯誤碼與復原流程見 記憶重新設計規格。
memory-write 預設 preview,確認後以 apply 寫入;update、delete、verify 必須帶 memory-read 回傳的 expectedSha256。工具會在該層鎖內核對版本、檢查預算與疑似 secret、寫主題與索引,再附加證據。log 寫入失敗會回復主題與索引。記憶工具首次存取可能先觸發舊資料遷移。
任務進入 ARCHIVING 後,低中風險且無事可記時可呼叫 memory-task-close,以 outcome: "none" 附至少 8 個非空白字元的理由;有值得記錄的內容或高風險任務,交由 memorizer 萃取、寫入並宣告 recorded 或 none。已有帶 taskId 的寫入時不能宣告 none。task-state-sync complete 不再接受記憶參數,會查處置時間、writer、寫入引用、hash 鏈與主題目前 SHA。memory 模組關閉或 workflow.completion.requireMemoryDisposition: false 時略過並警告。舊設定 requireMemoryReceipt 已移除,載入時會警告並忽略舊值。
hash 鏈用來偵測手動修改與非工具寫入,無法阻止有檔案寫入權的人重算整條鏈,威脅模型與 tasks.json 相同。確認目前內容正確後,writer 可用 memory-maintain 的 reseal-log 模式附理由復原。驗鏈改從最新有效 reseal 開始,舊寫入引用仍須存在且屬於同一任務。log 不截斷,超過 5 MB 由 doctor 提醒。
升級時會在既有 .opencode/ 搬遷之後,將 .ultrawork/project.md 按 H2 拆成主題並重建索引;code fence 裡的 H2 不拆。舊檔與 receipts 目錄改名加上 .migrated-<時間戳> 保留。只有 ARCHIVING 任務的有效舊收據會轉成 legacy-receipt 處置。失敗不寫完成標記,下次存取重試,不覆寫既有主題。診斷工具會回報兩層預算、鏈完整性、待整理筆記、遷移狀態與 writer 設定。
Comment Signal 的掃描政策與結案 gate
Comment Signal 只掃「註解語法有對應 lexer 分支」的副檔名,共 32 種(實作見 src/modules/comment-signal/file-scan.ts 的 SCAN_EXTENSIONS):TS/JS 家族(.ts、.tsx、.js、.jsx、.mts、.cts、.mjs、.cjs)、.json、.css、.html、.vue、.svelte、.yaml/.yml、.txt、.sh、JVM(.java、.kt、.kts、.groovy)、.swift、# 註解(.py、.toml)、C 家族(.go、.rs、.c、.h、.cpp、.cc、.cxx、.hpp)。沒有對應分支的語言(例如 .rb、.php、.lua、.sql)不納入,避免誤判。Markdown(.md/.markdown)刻意完全不掃。
掃描時跳過的東西:
- 雜訊目錄:
node_modules、dist、.git、coverage、.next、build、.turbo、.cache、.opencode、.obsidian、Swift/Xcode 產物(.build、DerivedData、.swiftpm)、Python 機器產生目錄(__pycache__、.venv)。 - 敏感路徑不讀(見「路徑守衛」;
.env.example除外)。 - dot 目錄與 dot 檔分開處理:一般的 dot 目錄(如
.github)放行,其下的可掃描檔照常列舉;只有 basename 以.開頭的隱藏檔才跳過。
expires=/due= 以真實今天判定:過了今天就發 warning(過期是提醒更新或收尾,不是錯誤)。結案 gate(實作見 src/modules/comment-signal/completion-gate.ts,判定以每個檔案的最新 per-file 報告為準):Comment Signal 開啟時,任一已檢查檔案還有未排除的問題就不給結案;問題修好並重新檢查後,乾淨報告會覆蓋舊的阻斷報告,自動解除,不會永久誤擋。
升級例外:如果結案訊息提到「升級前的阻斷記錄無法歸檔」,只重查單檔不會解除——那筆舊記錄說不出是哪個檔案,單檔的乾淨報告覆蓋不到它。解除方法是做一次完整且乾淨的重掃:先呼叫 comment_signal_check(不帶 path)重掃本工作階段;若該工作階段沒有可掃描的已修改檔(重掃掃到 0 個檔案),改以 path: "." 加 changedOnly: false 重掃整個專案。掃到檔案且結果乾淨後即解除。
計畫 ID 與任務 ID
ID 只用來指出是哪一個計畫、哪一個任務,不能寫進專案的內容。外掛在五個地方把關,五處用同一套比對:以任務註冊檔案裡實際存在的 ID 為準(src/modules/workflow/core/workflow-id.ts),不用正規表達式猜格式。
| 時機 | 行為 |
|---|---|
plan-state-sync/task-state-sync 的 create |
檢查格式。計畫 ID 是二到四個小寫的英文單字(版本寫成 v 加數字);任務 ID 是「主題-兩位數流水號」。不符合回 INVALID_PLAN_ID/INVALID_TASK_ID。只檢查新建立的 ID |
workflow-id-check |
唯讀。不帶參數檢查這次的 git 變更:新增的行、新檔案的內容和檔名。staged: true 只看已暫存的變更,text 檢查一段文字 |
task-state-sync 的 complete |
這次的變更裡有 ID 就回 WORKFLOW_ID_IN_PROJECT,並列出位置 |
shell 工具執行 git commit 之前 |
指令裡的訊息或已暫存的內容有 ID 就擋下這次執行 |
memory-write、skiller 的驗證 |
記憶的標題、description、正文,以及技能的內容有 ID 就回 WORKFLOW_ID_IN_MEMORY/WORKFLOW_ID_MARKER |
幾個要知道的界線:
- 只看這次新增的內容。既有的內容裡早就存在的 ID 不會擋住不相干的工作。
.ultrawork/底下不檢查,那裡是 ID 該出現的地方。- 不是 git repo、git 無法執行,或宿主沒有提供程序能力時,
workflow-id-check回available: false,complete不擋。沒檢查不等於沒問題。 - 新格式的計畫 ID 只有英文單字,和功能的名稱分不開,所以不比對;比對的是任務 ID,以及帶日期的舊格式計畫 ID。舊格式的任務 ID 短於四個字元或不含數字的不比對。
- 駝峰、底線和去掉連字號的寫法都算,例如任務
economy-review-02會找到EconomyReview02Test。
work-order-build 會在 Task Identity 的最後固定附上一段說明,交代 ID 只用在回報。plan-content-create 沒有給內容時,產生六個固定章節:目標與不做的事、已確認的證據、決定、任務、風險與復原、還沒決定的事。
內容引用診斷
workflow_doctor 有一項 Content Ref Path Integrity 檢查,逐項驗證任務與計畫註冊檔裡的內容引用(contentRef)是否還指得到正文;逐項清單放在回傳的 content_ref_issues 欄位,每筆含 owner(任務或計畫)、id、field、ref、kind、repair(實作見 src/modules/diagnostics/shared.ts)。kind 有四種:
legacy-prefix:還指著已搬走的舊位置(.opencode/前綴)。修法是把前綴換成.ultrawork/,或重跑搬遷讓外掛改寫註冊檔複本。outside-store:指到.ultrawork/plans/之外的位置。讀寫工具會擋下這類引用,修法是把引用改回內容庫內。not-a-file:位置在庫內,但目標是目錄或讀不到檔案型態。目錄永遠不是合法的正文目標,修法是重新建立正文或修正引用。missing-file:引用格式沒問題,缺的只是正文檔。進行中/活躍的項目算失敗,已終態(完成/封存)的只算警告。
使用者遇到「建立/刪除計畫被擋」時:跑 workflow_doctor 看 content_ref_issues,照每筆的 repair 修引用即可。
skiller 的寫入位置與角色檔路由
skiller.personalSkillRoot 與 skiller.agentsDir 只採全域層的值(實作見 src/settings/load.ts 的 stripProjectSkillerRoots)。理由是這兩個 key 決定「寫到專案外哪裡」:專案層若能改寫它們,clone 來的 repo 自帶的設定就能把寫入導到任意路徑。專案層帶著這些 key 會被整段忽略並警告,呼叫端只會看到全域層的值(或全域缺席時的內建預設)。
角色檔的 skill 路由同時支援兩種形狀:V1(permission: 單數下的 skill: map)與 V2(permissions: 陣列)。V2 以最後一條命中的 effect 為準(last-match),與 V1 的語意一致;晉升/退役/還原在改寫路由時會保證新規則後面沒有 glob 能在 last-match 下把它蓋掉。混合形狀(同時有 V1 map 與 V2 陣列)的角色檔會被視為結構不明而拒絕改寫。V2 規則的值可以帶空白(例如 shell 規則 resource: git status *);帶空白的值只接受整段成對引號,或不含行尾註解( #)與巢狀映射(: 、結尾 :)的純量,其餘同樣拒絕改寫。三個 skiller 工具的描述不再寫死 permission.skill,以免誤導 V2 形狀的使用者。
寫入鎖卡住時怎麼辦
同時只能有一個寫入在進行;別的寫入還沒放鎖時,新的寫入會拿到 CONTENT_LOCK_BUSY,等對方做完再重試即可(實作見 src/kit/write-lock.ts)。如果同一個鎖一直卡住,先確認沒有其他寫入真的在進行,再用 plan-content-read 的 unlockStale:true 診斷:它會回報 content 鎖與 registry 鎖各自的狀態,並自動回收「持有者已死且夠舊」的孤兒鎖。
有一種情況工具永遠不會自動處理:卡住的回收資格(reclaimTicket.stale:true)。資格檔是防止兩個寫入同時回收同一把鎖的互斥機制,自動與顯式路徑都不刪除它——若診斷顯示 stale:true,請確認沒有其他寫入在進行後,按 hint 指出的路徑手動刪除那個檔案(例如 rm <hint 給的路徑>),再重試。工具不動手是刻意的:誤刪資格檔會讓兩個寫入同時回收,鎖就失去意義了。
路徑守衛
全外掛共用同一份路徑守衛(實作見 src/kit/path-guard.ts),各模組不再各寫一份:
- 以下路徑不能當作專案根或寫入目標:空字串、系統根
/、..這類相對逃逸、macOS 系統關鍵目錄(/Users、/Volumes),以及家目錄本身(家目錄下的一般子目錄不受影響)。realpath 後的真實路徑會再檢查一次,所以指到這些位置的 symlink 別名也過不了;無法確認 containment 時一律拒絕(fail closed)。 - 敏感檔名(比對路徑的每一段,不分大小寫)一律不讀不寫:私鑰(
id_rsa、id_dsa、id_ecdsa、id_ed25519)、憑證與 token 設定(credentials.json、service-account.json、.npmrc、.netrc、.git-credentials)、.env與.env.*、.pem/.key/.p12/.pfx結尾的檔案。唯一的例外是.env.example(範例檔,可以讀)。
從設定 repo 版本遷移
以下是在設定 repo(~/.config/opencode 那一側)要做的事:
opencode.jsonc加上"plugins": [..., "opencode-ultrawork@^3.0.0"](或用github:形式釘住某個 commit,見「安裝」);刪掉plugins/opencode-ultrawork.ts、plugins/opencode-ultrawork/、tests/ultrawork/、scripts/generate-ultrawork-baseline.ts。skills-policy.json、skills-personal.json、skill-drafts/、skill-quarantine/搬到<全域設定資料夾>/.ultrawork/(外掛第一次啟動會自動搬;但設定 repo 裡讀它們的scripts/skill-approval.ts、scripts/skill-profile.ts、lib/skill-capability.ts和tests/config/的契約測試要改路徑)。AGENTS.md、agents/*.md、commands/*.md裡的.opencode/memory/…、.opencode/plans/…字串改成.ultrawork/…。lib/dcp-evidence-policy.ts列的是 ultrawork 工具名稱,名稱沒變就不用改。- 把那條讀設定 repo 技能政策的使用者專屬測試移回設定 repo 的
tests/config/。
升級到 3.1.0
- 新建立的計畫和任務要用新的 ID 格式,不符合會被
create拒絕。已經存在的計畫和任務不受影響,照常可以轉換狀態、連結和結案。 - 新增第 50 個工具
workflow-id-check。 角色的權限採預設拒絕的話,要替會改檔案、核對 diff 或結案的角色放行它。 complete和git commit多了一道檢查:這次的變更(或已暫存的內容、commit 訊息)裡有已登記的 ID 就擋下。舊專案裡既有的 ID 不算,但是只要這次新增的行裡出現,就要先改掉。memory-write拒絕含 ID 的內容,也拒絕正文開頭夾著 frontmatter 的寫入(之前會被寫成兩份 frontmatter)。更新既有主題時只檢查這次送進來的欄位。work-order-build組出來的工作說明多了一段固定文字;plan-content-create的預設章節改成中文的六個章節。既有的內容檔不會被改動。
升級到 3.0.0
這一節是給已經在用 2.x 的使用者。3.0.0 是大版本:^2.3.0 這類範圍不會自動升到 3.0.0,一定要手動改釘選。
同一次更新要做的兩件事
- 改 plugins 釘選:
"opencode-ultrawork@^3.0.0"(要完全可重現就寫死"opencode-ultrawork@3.0.0")。 - 在同一次設定更新補上四個清單:
verification.runAllowedAgents、verification.scopeCheckAllowedAgents、memory.writerAgents、workflow.evidencePack.gatedSubagents。
{
"verification": {
"runAllowedAgents": ["momus"],
"scopeCheckAllowedAgents": ["build", "ultra"]
},
// writerAgents 只採全域層的值,寫在專案層會被忽略並警告
"memory": { "writerAgents": ["memorizer"] },
"workflow": {
"evidencePack": {
"gatedSubagents": ["implementer", "debugger", "ultra-coder"]
}
}
}
缺了第 2 步會直接影響可用性:v3 這四個清單預設皆為空(fail closed)——沒設就沒有 agent 能呼叫該驗證工具、沒有 writer、七節檢查停用。2.x 沒寫這些清單時還有內建名單可用;升上 3.0.0 卻沒補,驗證工具、記憶寫入與派發檢查會全部被拒絕或停用——載入設定時不會警告,呼叫時才會看到明確的拒絕訊息(驗證工具回 AGENT_NOT_ALLOWED、記憶工具回 WRITER_REQUIRED),workflow_doctor 與 workflow_health_check 也會以警告提示清單為空。
相容與不變的東西
- 既有匯出相容:
loadSettings、resolveVerificationRunAllowedAgents、resolveChangeScopeCheckAllowedAgents、searchSkills、insertAgentSkillAllow、removeAgentSkillLine、loadPersonalPins、resolvePersonalPinsPath、PERSONAL_PINS_SCHEMA_VERSION的名稱與路徑不變(由tests/v2/native/compat-exports.test.ts把關)。 - 全部 50 個工具的名稱、參數、回傳格式,以及全部註解功能(七個
comment_signal工具、AI_DO_NOT_EDIT:P0阻斷、掃描政策、結案檢查)保留不變。 - 設定欄位不變:2.x 的
ultrawork.jsonc不用改寫就能讀,差別只有四個清單的預設改為空。
退場:escalation
升級限制(escalation)已退場:既有 session/<id>/escalation 舊紀錄會原樣保留,外掛不再讀寫,不需要手動清理。
回退限制
- 回退時把 plugins 釘選指回舊版本,並用事前備份的設定還原(
.ultrawork/預設不進版控,升級前先自行備份)。 - 2.x 資料格式可被 v3 回讀(2.x 形狀副本的讀寫已由
tests/v2/native/v2-data-readback.test.ts驗證);但完整雙向回退(v3 寫入後再拿 2.x 讀)沒有驗證,不要假設一定可行。
共用核心與宿主支援
- 工具實作與設定格式共用同一份宿主無關核心:入口
src/index.ts經src/opencode/adapter.ts把平台能力轉成宿主能力,再交給src/core/setup.ts。 - 但不承諾跨宿主共享任務、記憶或工作階段資料;目前只提供 OpenCode 宿主適配器,其他宿主(例如 pi)尚未支援。
開發
bun install
bun test
bun run typecheck
src/** 零 V1 import 的 gate:tests/v2/native/v1-free-surface.test.ts。三者都要通過才算完成一個階段(AGENTS.md)。
提醒:50 個工具的名稱、參數與回傳格式是對外介面,凍結清單由 tests/v2/native/diagnostics/tool-parity.test.ts 把關;任何改變都要先問使用者(AGENTS.md)。
授權
MIT,見 LICENSE。
同类生态推荐
Tell Sessions
opencode-tell-sessions
Inter-session direct messaging (DM) for OpenCode V1 & V2: agents in different sessions can message each other
Oc Solomemory
oc-solomemory
Persistent memory plugin for OpenCode — your AI agent remembers across sessions and projects
Oc Solomemory Dev
oc-solomemory-dev
Persistent memory plugin for OpenCode — your AI agent remembers across sessions and projects