refinement — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited refinement (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
這一站只做一件事:把成功的定義變成人簽得下去的 assertion,鎖起來。
鎖起來之後,做法怎麼變都不用回來問人;只有成功的定義本身錯了才需要回到這裡重簽。
要不要立案的判斷不在這裡,在 driving-work-to-done。走到這一站表示那個判斷已經做過 而且說出來了。
手上正在做別的事、但長出了一個不能消失的東西時,不要走完這一整站。 開一張種子單, 記下前因後果就好,然後回去做原本那張:
bash .claude/skills/refinement/scripts/open-seed-issue.sh \
--issues issues --namespace <命名空間> --slug <名字> --note '<前因後果>'它建目錄、寫下前因後果、記一個「還沒簽 assertion」的狀態、commit,然後就結束——不簽 assertion、 不決定領域、不開 worktree。那些是接手的人在這一站要做的事,而那時候才有人真的想過怎麼 算成功。種子單會出現在 next --across-issues 的答案裡(標成 seed:),所以它拿得給另一個 session 開工。
接手一張種子單就是從這一站的第一步開始走,走到 init——init 認得它身上那個種子狀態, 會把它升級成真的輪次並說出來,不需要先手動刪掉任何東西。
一張單是一個目錄:
issues/ 你自己的 git repo,框架 repo 忽略它
{命名空間}/ 自己的框架工作、某家公司、某個專案——你決定怎麼分
{格}/
{單號}/
index.md 正文含凍結塊 fence,其餘是可以改的部分
.spine/loop-state.json 輪次
.spine/measurement-ledger.json 量測命令登錄
.spine/placement.json 它現在該在哪一格——流程算出來寫在這裡,不搬目錄命名空間叫什麼不影響任何判定——流程逐個走過去,不從名字推導行為。開一張新的單時, 放進它該屬於的命名空間;不確定放哪就開一個新的,命名空間本身沒有註冊表要維護。
`issues/` 不歸框架版控。 它記的是你在做什麼、為什麼這樣定義成功;換一個人用同一套 框架,這裡的內容完全不一樣。空殼由這一站自己帶著——它在這支 skill 的 templates/issues/ 底下,跟 skill 一起搬走。第一次使用時:
R=.claude/skills/refinement
mkdir -p issues
cp $R/templates/issues/README.md issues/README.md
cp $R/templates/issues/gitignore.example issues/.gitignore
git -C issues init
git -C issues add . && git -C issues commit -m "issues: 開始"它仍然必須是一個 git repo——理由見下方〈凍結 = commit〉。verify 從檔案自己的路徑解析 repo,會自動跟著 issues/ 進它自己的歷史,不需要告訴它。
凍結塊與可以改的那部分同檔——一個工作被迫產生的東西是這一份與 code,純文件類的只有這一份。這是 設計意圖,不是一個被量的門檻:那幾個檔案是流程自己寫的,數量恆定,對常數設門檻只會是儀式。 交付時被真的判定的是「舊層還撐著沒有」,見 verify-ac。
assertion 簽的是「怎麼算成功」。但有幾件事同樣只有人回答得出來,而且它們決定assertion 本身寫得對 不對:這一版到底要做什麼、什麼時候要、提出的人真正想解決什麼、拿什麼測。
單裡寫的「這一層不用改」「維持現狀」「out of scope」是待證主張,不是事實。 那是提單的 人當時的推測,而範圍抄錯的代價要到判定那一站才看得到。定範圍之前實跑一次去驗它——證實了就 寫進去並註明驗過,證偽了就把它拉回範圍內。直接抄進 assertion 等於把別人的假設簽成自己的定義。
單裡的〈待確認〉預設是雜訊,不是關卡。 那一段是開單的人自己列的,而開單的人常常就是 能回答它的人——沒有道理開完單之後,再回頭要他確認自己寫的問題。逐條拿去對這張單的意圖 與範圍驗:不知道這一題的答案,工作做不做得下去?做得下去就當它不存在,連提都不要提。 真的擋住的才帶去問人,而且在這一站問完——留到施工途中才拿出來,等於每一條都變成一次 停工。
2026-08-27 的標本:一張單七條〈待確認〉,五條單上自己就寫了「需要另開票」「本票不做」, 兩條的答案就在 repo 的註解裡,零條擋得住任何工作。其中一條被當成關卡,把〈Scope〉做什麼 早就定案的一項工作退回去問人。那張單的 reporter 與 creator 是同一個人,description 在建立 後五十分鐘內改了六版——〈待確認〉是起草過程的殘留物,不是交付契約。
沒問的那一刻不會停下來,會自己填一個。 那比空著糟:空著看得出來,填過的看不出來。 而判定那一站只驗實作有沒有達成那份 assertion——assertion 若照著一個編出來的意圖寫,整條流程會全綠地 交付錯的東西。
這一站處理的是整張工作,不是一條 AC。 一張單裝得下一整個 Epic——同一份 {issue}/index.md 裡 可以有好幾組具名的凍結塊(A、B、C…),各自算校驗值、各自驗。所以先把整張讀完、消化 成一份計劃,不要讀一段簽一段。
然後交一份填好的草案出去,讓人在草案上改:
git log 知道誰動過這塊,package.json 知道怎麼跑測試),推得出來的先推一版。把自己查得到的東西做成問題,是把判斷推回去——而這個 repo 的規矩是〈有標準就直接套,提一個方案〉。
source 就是「事實」與「決策」的分界,它不需要靠問答活著:environment 是我查來的、inferred_confirmed 是我推的等你點頭、human 是只有你知道 的,我把我需要什麼寫在那一格裡等你填。
幾十輪往返,而每一輪的回答都比不上直接在草案上劃掉一行。
列做法的時候,先分成「畫面不動」與「畫面會動」兩堆。 會動到畫面的做法,沒有明確授權 就不列進草案;不該動畫面的單,AC 預設帶一條「畫面前後相同」。一個沒人要求的畫面改動列進 草案,會被當成選項認真比較,而它本來不在範圍裡。
寫進 {issue}/index.md 的 frontmatter,跟 destination 同一個地方:
plan:
# what 這一版要做什麼、when 什麼時候要、why 想解決什麼、how 拿什麼測
what:
answer: "…"
source: inferred_confirmed
when:
answer: "…"
source: human
why:
answer: "…"
source: human
how:
answer: "本機起後台 + mock 掉上游 API"
source: human
environments: none上面那一份照抄就會過,這是刻意的。environments 真的要列東西的時候寫成 environments: [admin-console, upstream-api-mock]——但列進去的每一個都要有人宣告會起它, 不然下面那支檢查會指名它們(那就是〈有些答案每張單都一樣〉在講的訊號)。所以範例裡放的是 none:一個抄下去就被自己的檢查退回的範例,教的是「這份文件不能信」。
每一項都是一個區塊,不要寫成 `what: { answer: …, source: … }`。 讀它的是一支手寫的 逐行 parser,不是 YAML 函式庫——行內寫法它讀不懂,而它對讀不懂的行拒絕(略過的話, 一個縮排打錯的區塊會靜默地變成「沒有區塊」,那正是它要抓的東西)。同一個理由,註解只能 自己占一行——what: # 說明 那樣寫,那一行就不是它認得的形狀了。抄完立刻跑一次下面那支 check-plan-answers.sh 確認,不要等到算校驗值才發現。
source 三種:human、environment(查出來的)、inferred_confirmed。
`human` 問的是誰想的,不是那段字掛在誰的帳號底下。 這兩件事會分開,而分開的時候沒有 任何顯示看得出來:對外送出的留言(ticket 的 comment、PR 的 review 與回覆)掛的是那個人的 帳號,內容卻可能是上一輪的我寫的。帶著「由 Claude Code 代發」這個標記的東西就是那一種 ——它是我自己上一輪的輸出,不是任何人的意圖。
所以那樣的一段字:
inferred_confirmed 都不是——那一格是「我推的、他點頭了」,而一則沒有人回過的留言沒有那個點頭。
2026-08 量到的一條六步的鏈:提單的人在描述裡列了六個建議之一 → 我在自己的留言裡把它 升格成一個工作項目 → 那則留言用他的帳號送出 → 下一輪讀回來,「規格權威:那則留言,不是 描述正文」 → plan.what 照抄它並標 source: inferred_confirmed → 斷言照它簽。使用者事後 的原話是「你創造一個荒謬的做法然後說是我做的」。
沒有標記的舊留言分不出來,這一點要說出來。 標記是這一版才開始加的,在那之前送出去的 東西讀起來就是那個人寫的。碰到分不出來的,問人,不要挑一個看起來比較順的答案。 這張單真的不需要某一項時,明講並說為什麼——空著與不適用是兩件事:
when:
not_applicable: "框架自用,一單一版隨時釋出,沒有外部時程"`how` 要多帶 `environments`。 「要起哪些東西」寫在句子裡沒有人讀得懂;列出來之後, 「哪個環境還沒有人會起」就算得出來——而那正是〈有些答案每張單都一樣〉在講的訊號。真的 不需要起任何東西就寫 environments: none:那是一個答案,不是欄位不見。
上面那條「單裡寫的『這一層不用改』是待證主張」的規矩,寫下之後 18 次都沒有被執行。落差不在 規矩本身——它沒有落腳處。一句驗過的現況跟一句沒驗過的現況,在檔案裡長得一模一樣,而長得 一樣就是不會發生的意思。
所以現況主張跟計劃那四格放在一起,形狀相同:一個答案,加上它從哪來。
plan:
assumes_legacy_has_no_pipeline:
claim: "這個 repo 沒有任何 pipeline 會跑 packages/legacy 的測試"
verified_by: "rg -l 'packages/legacy' .ci/"
observed: "(無輸出,0 個檔案)"三個欄位缺一不可,而且修法各不相同:沒有 claim 是這一條在主張什麼看不出來;有 claim 沒有 verified_by 跟直接把它寫進斷言沒有差別;有命令沒有 observed,那條命令證明不了任何 事——沒有輸出就把「無輸出」寫出來,那跟沒跑過長得不一樣。
一張單真的不依賴任何別處的現況時,那要是一個說得出來的答案:
plan:
assumes:
not_applicable: "這一版只改自己這支腳本的行為,不依賴任何別處的現況"哪些句子算現況主張?可以被證偽的那些。「送出之後 X 會發生」是意圖,錯了要重想; 「packages/legacy 沒有 pipeline 會跑」是現況,錯了跑一次就知道。跨 repo 的單特別要列——別的 repo 的現況你不會順手看到,而自己這個 repo 的你以為你知道。DP-608 全樹量到的:assertion_wrong 停點在單一 repo 每張 0.17 個、兩個 repo 2.00、三個 repo 5.00;而單一 repo 那 20 條裡有 13 條 也是「一句沒驗過的現況」——「以為知道」跟「沒看過」一樣貴。
症狀類的現況量一次不算數。 回 0、回 404、回 503 這一類是狀態,一天的快取就能讓它看起來像 bug。隔至少一天再量一次,兩次都成立才寫進去。observed 只放自己量到的,從單的描述或別處 抄來的不算。2026-09-18 的標本:一份索引有 17 個分頁量到 0 筆,單開了、還推出一個要權限才做得 到的下一步;兩天後重量,17 個全部有資料。證據表裡四格「沒有」是從單的描述抄的。
*寫任何 `assumes_` 之前,先讀這家公司的領域常識。** 常識是公司裡每個人都知道、而 code 與 樣本不會告訴你的事:某個網址上的編號是哪一種 id、兩個名字相近的欄位差在哪。那份常識由公司 自己的 skill 指路,在它的 SKILL.md 寫一行,路徑相對於那份 SKILL.md:
<!-- {任意前綴}-DOMAIN-GLOSSARY: {常識那份檔案的路徑} -->check-plan-answers.sh 掃得到這一行就把路徑印出來,不擋人。沒有任何一支 skill 宣告時它 什麼都不印——那代表這個環境沒有寫下來的常識,不代表沒有常識。
`claim` 裡出現常識表上的識別字時,樣本至少要有一顆兩者不同的案例。 兩種 id 在大多數 資料上相等的時候,全部相等的樣本證明不了你拿的是哪一種,它只證明你挑的樣本分不開它們。 樣本全部相同,那一條就還沒驗過。claim 牽涉公司的識別字、系統之間的邊界、或環境時, verified_by 除了抽樣本,還要寫出查過常識那一份;那份沒寫到的,照它說的「查不到時去哪查」 再查一次。常識常常早就寫在公司的某個地方,缺的是簽之前去查的那一步。2026-10-05 的標本:一張單把網址上的商品編號當成方案 id 去查,實際上它是主商品 id。assumes_* 用九條樣本「驗過」,九條剛好兩種 id 都相等。假設、 實作、單元測試、測試環境驗收、review 全綠,上線才發現。
一條前提被推翻的時候,先列出哪幾張單建立在它上面。 給它那條前提的關鍵字,它掃單的根目錄 底下每一張單的 assumes_*(claim、observed)與凍結塊,逐處印單、格、條目與 path:line。 哪一條要重簽由讀的人判:
python3 .claude/skills/refinement/scripts/scan-premise.py --issues issues <關鍵字>...施工途中發現的常識,當天寫回那份常識,由發現的那條線自己寫。 留在單裡或對話裡的常識, 下一張單不會讀到,而它會在下一張單上再被發現一次。
bash .claude/skills/refinement/scripts/check-plan-answers.sh {issue}/index.md這一步在 seal 之前跑。 凍結的那個 commit 落下去之後才發現缺,那個 commit 已經在那裡了。
單上寫的「要先做 X 才做得到 Y」也是一條現況主張。 開工前先去量 Y 現在有沒有在發生。 2026-09-18 的標本:一張單寫著某個行為要等前置工作做完才做得到,量下去,那個行為已經在線上 跑了十五個月。
拿「某一類有 N 條」去乘量級之前,先抽樣實測其中真的會發生的比率。 一類東西的數量不等於 那件事發生的次數。2026-09-15 的標本:「看起來會轉址的連結」被當成每一條都會轉址,抽樣實測 只有約四分之一。
這一版要在某個既有元件加一條規矩或改一條規矩時,「沒有跟既有的打架」也是一條現況主張。 元件指 skill、腳本、規則。那一條 assumes_* 列出那個元件、以及流程核心 (driving-work-to-done 與各站)講同一件事的既有規矩,observed 逐條帶 path:line。新的跟其中一條相反,就不簽; 把那幾條 path:line 當成異議回給下令的人。方向是指揮官或別人給的,不是簽下去的理由—— 下令的人手上不一定有那幾行,簽的這一站才是一定會讀到那個元件的地方。
2026-10-21 的標本:一條規矩被派成「施工填完回寫待確認就繼續」,加進施工那一站。同一支 skill 自己寫著凍結塊不要動、assertion 不對要回頭重簽,流程核心也寫著判不出會不會改到意圖的算會改到。 下令的與簽的都沒列出來,釋出之後被使用者指出,隔一版改回。使用者原話: 「我們 /driving-work-to-done 不是有定義 /engineering 不能改定義,要退回重新 /refinement 嗎」
判準是「這個值換了來源之後,誰還在讀它」,而清單的邊界是那個值真的流到的每一個地方, 不是列清單的人站的那一個 repo。 共用欄位、共用回應、共用索引欄位——只要這一版改的是它的 來源、或就地覆蓋它的值,這一條就適用。
那份清單是一條現況主張,所以它照上面那三欄寫(claim/verified_by/observed),不另立 格式。要多寫的只有一件事:`verified_by` 那條命令掃過哪些地方,以及哪些地方你沒有掃、 為什麼。 不在本機的(另一個團隊的服務、mobile 那一端、只在 CI 裡存在的中間層)也要寫出來 ——寫出「這裡我看不到」跟沒有提到它,在下一個人眼裡差很多。
2026-09-15 的標本:一張單把商品簡述換成讀新欄位,verified_by 是 grep -rni introduction <一個 repo>/app/,observed 逐處列了四個讀取點——清單有、驗過了、 行號都對。漏掉的兩個消費者不在那個 repo 裡:一個把這個值當作沒設特色時的商品特色印在 畫面上,另一個把它印成搜尋卡與高亮卡的文字。同一個 needle 逐 repo 掃過去, 命中數是 17/26/41/68/0(2026-09-15 量到的五個 repo,作者掃的是第一個), 而清單只涵蓋第一個。needle 沒有錯,範圍錯了。
那一版的 assertion 因此只涵蓋作者自己改到的三處 SEO 欄位,判定全綠,問題是看 diff 的人問 出來的,時間點在上線前。
這一格沒有關卡在守,是刻意的(照 CLAUDE.md〈檢查類腳本的門檻〉):那一次擋下它的正是 看 diff 的人,所以「看 diff 的人看不出來」那一條不成立,三個條件缺一就不要寫。
追到消費者之後,第一句是問意圖,不是判成 bug。「這個值印在畫面上」是程式碼的事實;「所以 改它是錯的」是對欄位用途的判斷,只有定這個欄位用途的人答得出來。所以要說的是「這些地方會 跟著換,這是不是你的意圖」。同一天不連續反轉一張單的形狀:第二次要反轉時停下來,把兩種讀法 並列給人看。上面那張單後來就是這樣:追到消費者之後被講成「畫面會壞」,形狀當天改了一次, 問過之後才知道那個欄位本來就是給那個用途的,原本的實作是對的。清單還是要列—— 錯的是列出來之後直接判成 bug,不是列清單這件事。
簽 assertion 之前,先列出這個值從產生到被使用者看見,中間經過的每一層。 產生它的服務、 轉手的中間層、組裝畫面的那一端,都是一層。然後逐層問同一句:在這一層改是不是最對? 問完再選落點。不要預設只在自己站的那個 repo 裡找修法——那是你打開的地方,不是答案所在的 地方。
上一段問的是「改了之後誰還在讀」,往下游看;這一段問的是「該在哪裡改」,上下游都要看。 兩段列的常常是同一條資料流,問的問題不同。
那份逐層清單是一條現況主張,照〈這一版假設的現況〉的三欄寫,不另立格式。跟上一段一樣, 哪幾層沒看、為什麼沒看,也要寫出來。 選了哪一層、其他層為什麼不選,寫進單上的正文: 下一個人要推翻這個選擇,需要先知道當時比過哪些。
沒有推的權限,不使那一層出局。 讀得到程式碼就能從 fork 開一顆參考 PR,交給維護那一層 的人決定怎麼改。連讀都讀不到的那一層,寫出「這一層我讀不到」,不要略過——略過的那一層在 清單上看起來就像比過了。
2026-09-29 的標本:一張單要讓搜尋結果頁的連結改讀可讀網址。修法停在前端那一層,結論寫成 「上游的搜尋服務沒回這個欄位」,然後就去找前端能湊出什麼。上游那個服務本來就可以改,而 那才是最對的落點。「上游沒回」是一個現況,不是一個結論——它說出了該去哪一層看,卻被 拿來當成不去看的理由。
下游缺一個欄位時,先找它在哪一層被丟掉。 它常常在上游某一層進來過,只是那一層的資料結構 沒宣告它。修法是在那一層補上、往下傳,不是在下游再打一次上游把它撿回來。每天或每次請求都要 付的成本,要對上受益的量級;對不上就建議不做,並寫出什麼時候值得再看。2026-09-30 的標本: 為了讓列表帶一個欄位,一顆 PR 在每日全量索引裡每批多打一次上游,每次多約兩萬七千次呼叫。上游 另一層本來就拿過同一份回應,只是沒宣告那個欄位;而有那個欄位的資料是二十六萬多筆裡的六筆。 三顆 PR 全部關掉。
這一格沒有關卡在守(照 CLAUDE.md〈檢查類腳本的門檻〉):選錯落點的後果看得到, 看計劃的人問得出「為什麼不在上游改」。
「測試環境怎麼起」「owner 是誰」「推不出來時去哪裡問」「部署到哪裡測」——這些對同一個 repo 的每一張單答案都相同。它們不進單,進那個 repo 的領域知識。 塞進每一張單等於每次 重問一次同樣的東西,而重複的儀式會被學會跳過。
什麼時候知道該生一份?計劃自己會說。 environments 列出來的每一個,都要有某一份領域 知識宣告它會起它:
<!-- {任意前綴}-ENVIRONMENT-{環境名}: {起它的命令} -->check-plan-answers.sh 掃所有 skill 的 SKILL.md 找這一行。找不到的那個環境,就是該 沉澱的那一份。 這件事在凍結之前就說得出來,那時候計劃還改得動——比簽完 assertion、開輪次時 才撞 POLARIS_SPINE_PACK_UNRESOLVED 早一站。
生的時候用上面同一套做法:先讀、先推、交草案、人改。宣告的命令要真的跑得起來——一份 沒被執行過的知識跟沒有知識一樣安靜,差別只在它看起來很完整。核心不認得鍵名也不認得環境 名,前綴由那份知識自己定。
init 指名的領域知識不存在時,殼也會拒絕開輪次,訊息會指回這裡。那是同一件事的第二道 網——不是第二個判準。
產出物的讀者是 PM、使用者代表與 RD,而不是自己的時候,照 [references/prd-template.md](references/prd-template.md) 寫。 上面那份草案是寫給自己與施工的人看的;開發計畫是寫給要讀懂、照著配合的人看的,兩者的形狀不同。
{issue}/requirement-snapshot.md。使用者口頭給的要求,日期加原句記進 {issue}/index.md 的〈使用者原話〉一節,轉述的標是誰轉述。推不出來的困難總會出現,唯一的解法是去問一個人。訊息送出去是不可逆的,所以順序是 擬稿 → 人看過 → 才送:
bash .claude/skills/refinement/scripts/record-outreach.sh draft \
--issue {issue} --id <slug> --to '<哪裡>' --body '<擬稿全文>'
bash .claude/skills/refinement/scripts/record-outreach.sh confirm \
--issue {issue} --id <slug> --by <人> --quote '<那個人自己說的話>'
# 送出(用你手上的工具),然後:
bash .claude/skills/refinement/scripts/record-outreach.sh sent --issue {issue} --id <slug> --link <URL>
bash .claude/skills/refinement/scripts/record-outreach.sh reply --issue {issue} --id <slug> --body '<回覆>'送出動作本身腳本攔不到——那是別的工具做的。它攔得住的是紀錄:沒有人的原話就記不下 送出,而一個留不下紀錄的動作,事後看起來就是沒發生。回覆寫回來之後它就留在單裡, 下一個人不用再問一次同樣的問題。
陳述句,不是要求句。「當 X 時,Y 發生」可以被注入情境驗證;「應該要有 X」不行。
正負兩表都要有。 只有負向表列時,「什麼都不做」可以拿滿分;只有正向表列時,副作用 沒人管。負向表列不定義成功,它圈出即使達標也不接受的做法。
只寫意圖,不寫儀器。 assertion 承載意圖鎖死,量測是 assertion 的代理放開。把「用哪支腳本、閾值 多少」寫進凍結區是分層錯誤——那些東西在活區,會換。
改程式碼的單,AC 對輸出與行為簽,不對註解或隨 code 附上的散文的形狀簽。「某段程式 自己說出它為什麼這樣做」這類要求不寫進 AC。施工會照做,而照做的結果是一段前因後果寫進 code,之後過期也沒有人回來改。註解該寫多少,由 swe-knowledge 那一條管,不由 AC 管。 一張本身就在改文件的單,文件就是它的輸出,照舊對文件簽。
2026-09-30 的標本(那一天開的 PR):一張單的 AC 要求某個 method 自己說出它不帶某個欄位、 說出為什麼,並指得到這張單的結論,「讀的人不需要離開這個檔案」。施工照做,那段 doc-comment 新加了五行:上游欄位反映什麼、上游的另一顆 PR、另一張單、協定上合不合法。這些都是單上本來 就有的東西。
指名一個會吃輸入的東西時,要說出那組輸入。「當 X 時,Y 發生」裡的 X 常常不是一個值, 是一整組——而漏掉的那幾種不會被量到,因為量測只做 assertion 叫它做的事。所以 assertion 裡要出現「哪幾 種」,不是留給施工的人自己挑一個最順的。
這不是在寫儀器:「排序欄位被清空時送出什麼」是意圖,「用哪支測試框架填空字串」才是儀器。 判準是列不出三種通常就是還沒想過——一個吃使用者輸入的東西至少有「空的、超出範圍的、 型別不對的」,一個吃回應的東西至少有「缺欄位的、空集合的、錯誤碼的」。
2026-08-07 的標本:一條 assertion 寫「送出的排序與內容跟畫面一致」,而「一致」只在一個合法值上 被量過。清空輸入欄位那一種從來沒進過 assertion,於是十六條 assertion 全綠、交付紀錄寫成、流程判定 通過,而送出去的是一個空字串。沒有任何一站做錯自己的事——那正是這條要擋的形狀。
這一版換掉或拿掉一個既有機制時,負向表列要有一條「它服務的使用者主流程行為不變」。 換掉機制的單,正向表列寫的都是新東西長什麼樣。舊機制順帶保證的事沒有人寫下來,於是也沒有 人量。那一條照上一段的規矩列出主流程會走的輸入,至少包含在選項之間切換、改掉先前的選擇、 送出。只走預設那一條路的量測,量不到舊機制在切換時做的事。
2026-09-30 的標本:一張單拿掉了元件每次開啟時的重建,改成原地重算。這一端的十一條斷言 講外框、遮罩、骨架高度與開閉,另有一條講計價什麼時候觸發、算出什麼。同一份計劃裡另一端有一條「選擇、 選日期、選數量、送出的行為與改動前相同」,這一端沒有。重建原本會依當下的選項重設選取;拿掉之後,換選項時舊的選取留著,結帳金額算成 0, 上線約十九天才被回報。
這一版新增的查詢會掃大表、或要靠逾時切斷才停的時候,負向表列要有一條正式環境規模的負載 assertion。 那一條講三件事:用正式環境的資料量與同時使用的人數,回應在多久以內;逾時切斷 之後,那條 query 不留在 DB 上繼續跑;同時打進來的請求,不拖垮共用同一個 DB 的其他服務。 測試環境的資料量通常小很多,在那裡量到的「慢,但會回」說明不了正式環境。門檻數字需求沒給的, 照〈交給 PM 與使用者的開發計畫〉列進待確認,不自己填。
2026-10-21 的標本:一頁在 stage 冷點量到 db 時間 9.1 秒、三條 query 整表掃。那張單的 assertion 講畫面與內容,沒有一條講負載;修它的 index 沒趕上那一班,到上線前才被問到。使用者原話: 「所以你沒評估過做上去之後,光 query 就可能造成 service 癱瘓嗎!」
寫進 fence 之間:
<!-- POLARIS-FROZEN-A-BEGIN -->
#### 正向表列(達成)
- **A-P1 …**:當 … 時,… 發生。
#### 負向表列(副作用殼)
- **A-N1 …**:當 … 時,… 不發生。
<!-- POLARIS-FROZEN-A-END --># 1. 只有人知道的那幾項都有答案了(見〈問出只有人知道的事〉)。缺項就不要往下算校驗值——
# 凍結的那個 commit 落下去之後才發現缺,那個 commit 已經在那裡了。
bash .claude/skills/refinement/scripts/check-plan-answers.sh {issue}/index.md
# 2. 算校驗值(算的是 fence 內文,寫校驗值本身不會讓它失效)
bash .claude/skills/refinement/scripts/frozen-assertion-fence.sh seal {issue}/index.md --by {簽的人}
# 3. commit —— 這一步才是凍結
git add {issue}/index.md && git commit -m "freeze: {issue} assertion"
# 4. 隨時可重算比對(預設就會與 git 歷史比,不需要參數)
bash .claude/skills/refinement/scripts/frozen-assertion-fence.sh verify {issue}/index.md
# 5. 開輪次。領域的決定是這一步的一部分,不是之後補的欄位——「這件工作屬於哪個領域」
# 沒被回答就往下走,等於流程不知道它要滿足什麼條件。
# --where 是「這張單的改動會落在哪些地方」,一個地方給一次;它不是「我現在站在哪」。
bash .claude/skills/driving-work-to-done/scripts/spine-loop-state.sh init \
--state {issue}/.spine/loop-state.json --pack swe-knowledge \
--where <工作區路徑> [--where <另一個>]...
# 不改程式碼的工作(報告、調查、文件、資料分析)要說出理由:
bash .claude/skills/driving-work-to-done/scripts/spine-loop-state.sh init \
--state {issue}/.spine/loop-state.json --pack none --why '<為什麼這件工作沒有領域完成條件>'落腳處要在這一站問出來。 一張單住在 issues/ 而程式碼落在某個產品 repo 是常態,而 「落在哪」只有提單與施工的人知道——它跟計劃那四格一樣,是別人回答不了的東西。細節(怎麼 記、怎麼比、下游怎麼讀)寫在 driving-work-to-done〈載入領域知識〉,這裡不抄第二份。
`init` 會跑該領域宣告的開工條件,不成立就不開輪次。 條件是什麼這裡不說——它寫在 那個領域自己的知識裡,寫在這裡就是第二份。拒絕的訊息會說出缺的是哪一條、怎麼修,照著做 再跑一次就是了。
所以第 2 步之前先跑一次 init 是划算的:條件沒滿足的話,凍結的那個 commit 會落在一個 不該落的地方,而那時候它已經在那裡了。
seal 也有一個宣告接點,以命名空間為鍵。 寫校驗值之前掃所有 SKILL.md 找這一行,對這張單所屬命名空間的 那一條把單的目錄接在命令後面跑,非 0 就不封、檔案不動:
<!-- {任意前綴}-SEAL-CONDITION-{命名空間}: {命令} -->它在這裡是因為寫 assertion 那一刻該有什麼,只有那個命名空間知道,而這一站不會去載入它的知識。 所以那條命令紅的時候,印出來的就是那一刻要讀的東西。沒有人宣告的命名空間照常封。
凍結 = commit,不是 = 算校驗值。 校驗值只證明 fence 內文與 frontmatter 自洽——改了 fence 再重簽一次,校驗值一樣對得上。--by 只是一個字串,agent 也打得出來。真正擋住偷改的是 git 歷史:verify 預設把 fence 內文與該檔在 HEAD 的版本比,不同就 fail-closed,重簽不構成 授權。所以人的確認不是那個參數,是那個會出現在 diff 裡、有人看得到的 commit。
沒 commit 的 assertion 等於還沒凍結。單若不在 git 裡,verify 直接回 POLARIS_FROZEN_FENCE_HISTORY_UNAVAILABLE——不讓「放在未追蹤的位置」買回豁免。
重簽不是「把被點名取消的那幾條刪掉」。 設計換了之後,fence 上每一條都要被問一次同樣的 問題:這一條當初是為了什麼而在?那個理由現在還在嗎? 理由消失的要跟著退場——沒有人會 來點名它,而它會留在 fence 上,看起來跟還成立的那幾條一模一樣。
範圍變了的時候特別會留下這種條目:搬出改成共存、兩層收成一層、某一端不做了。被拿掉的那一 端原本有幾條 assertion 在守,那幾條現在守的是一個不存在的東西。
重簽的依據要留在停點那一格,不是留在 commit 訊息裡。 停 assertion_wrong 再解停點的 時候,advance --authorization 收的那句話就是依據,它落在 {issue}/.spine/loop-state.json 的 clearances[](帶 by 與 at)。所以一顆重簽的 commit 要對得上一筆 clearance—— 對不上就表示這一次重簽沒有人授權過,而 commit 訊息裡寫不寫得漂亮不改變這件事。
反過來也要說:commit 訊息裡看不到依據,不等於沒有依據。 依據的家是 clearances[], 要查就查那裡。
施工的那一站不自己往 fence 裡加 A-P 條目。 施工途中發現 assertion 不夠,那是 assertion_wrong 停點,走回這一站——那條路會留下一筆 clearance 與一顆有人看得到的 commit; 就地擴寫不會,而擴寫過的 fence 跟一開始就簽成那樣的 fence 讀起來完全一樣。
施工方自己寫下的達成判定,不等於人簽過。 要對人說「這是你簽過的」之前,先找得到簽的 那個人自己的字;找不到,就明講那只是施工方的判定。2026-09-15 的標本:一條由施工 session 寫下的判定,在設計重簽時被說成「你簽的」。
宣告去猜誠實。
engineering。抄進去等於每張單都重簽一次同樣幾行不承載新資訊的東西。
driving-work-to-done,只寫在那裡。這一站簽的是這張單獨有的成功條件。這一類工作共用的那份(definition of done)由 driving-work-to-done 判定領域、載入對應的 pack、記在單的狀態裡。seal 完就回殼把那件事做掉, 它不是這一站的活。
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.