Skip to content

[FEAT] choice-first decision rendering doctrine (#190) - #191

Merged
kiki830621 merged 3 commits into
mainfrom
idd/190-choice-first-doctrine
Jun 19, 2026
Merged

kiki830621 merged 3 commits into
mainfrom
idd/190-choice-first-doctrine

Conversation

@kiki830621

Copy link
Copy Markdown
Member

Refs #190

Summary

跨 skill Choice-First Decision Rendering doctrine:決策 / 澄清點選項可列舉時,idd-* skills SHALL 用 AskUserQuestion render 候選(含推薦項),而非請 user 自由文字 articulate;free-text 是具名例外 fallback(只在選項空間真的開放、AI 無法列舉時,且須寫明為何無法列舉)。

Design 經 spectra-discuss(attended) 對齊 3 決策:cross-skill doctrine / SHALL + named-fallback / D.1 references it。

Verification

  • openspec validate:PASS
  • 1 個獨立 adversarial reviewer(idd-verify-style devil's advocate):抓到 1 P1 — doctrine 的 unattended clause 誤述 Layer V 取「推薦項」,實際 Layer V section F 取 proceed anyway(max_score 5/6 時推薦項是需要人的 clarify/escalate)→ 已修(c93c0c8,4 處措辭改為「安全 non-blocking 預設」)+ re-validate PASS。faithfulness(3 decisions)/ Non-Goals / spec-format 全 PASS。
  • 註:doctrine / prose change,未跑完整 6-AI code-verify(該 ensemble calibrated for code logic/security/regression,對 prose 較不適用);以 openspec validate + 獨立 adversarial doctrine review 替代。

Checklist

  • Diagnose (Spectra)
  • spectra-discuss(attended,3 決策對齊)
  • spectra-propose + apply
  • Verify(adversarial review;P1 抓到並修復)
  • verify-gated — ready for review → merge 後跑 /idd-close #190(manual gate + closing summary;無 auto-close trailer)

Related / Non-Goals

  • 不動 idd-clarify(不同軸:術語審計 vs 決策渲染);與 in-flight add-idd-clarify-skill(9/9 done)無碰撞
  • 不改 (PR/HITL) mode resolution;不把純資訊輸出納入 scope

🤖 Generated by /idd-all (attended). Do NOT add a close trailer (Closes/Fixes/Resolves) — IDD discipline requires manual /idd-close after merge.

spectra-propose artifacts for #190: a cross-skill doctrine that IDD skills
SHALL render enumerable decisions as AskUserQuestion choices (recommended
option first) rather than asking the human to articulate in free-text;
free-text is a named-exception fallback only when the option space is
genuinely open.

Design aligned via spectra-discuss (attended, 2026-06-19):
- Decision 1: cross-skill doctrine (MANIFESTO + new spec capability)
- Decision 2: SHALL + named fallback
- Decision 3: extract shared; idd-diagnose Layer V D.1 references it

Proposal only — implementation (MANIFESTO + idd-diagnose SKILL.md patches)
deferred to spectra-apply pending review.

Refs #190
Implement the spectra-apply step for #190:
- MANIFESTO.md: new "Choice-first decision rendering(人挑,不要人寫)"
  sub-section under the NSQL Confirmation Protocol section — enumerable
  decision points SHALL render AskUserQuestion choices; free-text is a
  named-exception fallback only when the option space is genuinely open.
- idd-diagnose SKILL.md Step 3.4 Layer V D.1: refactored to reference the
  doctrine as the single source of truth (vagueness-clarification instance)
  instead of re-specifying the choice-vs-free-text rule.
- idd-diagnose SKILL.md Step 4 Stage 1: new normative note applying the
  doctrine to stakeholder-decision surfacing (the #190 incident gap).
- tasks.md: implementation + validation checklist marked complete.

openspec validate: pass.

Refs #190
…verify P1)

Adversarial review (idd-verify) found the doctrine's unattended clause said
"auto-apply the recommended option, mirroring Layer V" — but Layer V's actual
unattended behavior (SKILL.md section F) applies `proceed anyway`, which is NOT
the recommended option when max_score >= 5 (recommended is clarify/escalate,
both human-requiring). The doctrine was unfaithful to the very instance it
cites as single source of truth.

Reworded the unattended clause in all 4 places (MANIFESTO, spec scenario 4,
idd-diagnose Step 4 note, proposal/design) to: "apply a safe non-blocking
default per the skill's existing unattended convention (NOT necessarily the
recommended option); e.g. Layer V's proceed-default". Also clarified that
unattended is signalled via the UNATTENDED MODE directive, not a literal
INTERACTION=unattended env toggle (P2). tasks.md updated.

Refs #190
@kiki830621
kiki830621 merged commit 1ef65bf into main Jun 19, 2026
@kiki830621
kiki830621 deleted the idd/190-choice-first-doctrine branch June 19, 2026 15:56
kiki830621 added a commit that referenced this pull request Jun 19, 2026
spectra-archive (openspec archive): promote the choice-first-decision-rendering
spec delta to openspec/specs/choice-first-decision-rendering/spec.md and move the
completed change to openspec/changes/archive/2026-06-19-add-choice-first-decision-doctrine/.

Mechanical housekeeping after PR #191 merged the doctrine implementation.
No behavior change — the doctrine prose (MANIFESTO + idd-diagnose) already landed in #191.

Refs #190
kiki830621 added a commit that referenced this pull request Jun 19, 2026
…ring doctrine (#190)

Bump plugin.json + marketplace.json 2.85.2 → 2.86.0 (feat → minor per convention)
+ CHANGELOG 2.86.0 entry. Doctrine + spec already landed via PR #191.

Refs #190
kiki830621 added a commit that referenced this pull request Aug 31, 2026
**invisible_line 的貪婪 regex(純引用 → compliant)**

`^[ \t]*<!--.*-->[ \t]*$` 的 `.*` 是貪婪的:在
`<!-- a --> <blockquote><!-- b -->` 上它從第一個 `<!--` 跑到最後一個 `-->`,
把中間的可見元素一起吞掉。引用的開啟標籤因此變成隱形,heading 成為 lead
line,一段純引用被判 `compliant` —— 那是唯一「不印在任何 section」的分類,
所以該 issue 同時對稽核靜默、也被 `--retroactive` 拒絕。

非貪婪救不了,這點值得寫下來因為它是所有人第一個會試的:`<!--.*?-->[ \t]*$`
一樣會 match,因為 `$` 會逼著懶惰量詞繼續延伸到尾端是空白為止。改用
tempered dot `(?:(?!-->).)*` 讓每個註解停在自己的終止符,`+` 再要求該行其餘
部分只能是另一個註解或空白。lookahead 對本機 Oniguruma 實測過、不是假設。

fixture #190(重現)/#191(邊界:不以 `-->` 結尾,貪婪版也沒 match,保留因為
那是日後「簡化」會落地的地方)/#192(控制組:真的只有註解的行仍須跳過,
否則修法等於把功能關掉換綠燈)。編號用 190-192 而非 187-189:第一版跟既有的
`#187 autolink then hash` 撞號,兩條 refutation 都對著**那一張**通過,我自己
新增的那張根本沒被檢查。

**unsafe_filename 的 null(verify 永久失敗)**

refuse 記成 `{filename: null}`,`jq -r '.files[].filename'` 印出字面 `null`、
`[ -z ]` 為假,於是去測 `-f "$ATTACH_DIR/null"`、記一筆 missing、exit 1 ——
而這是 idd-close Step 1.4 的 gate。與它形狀相同的 `download_failed` 差在:
後者是**暫時**的,重抓就清掉,正是這個 gate 該逼人做的事;refuse 是**確定
性**的,重抓重現同一個 refuse。沒有補救路徑的 gate 不是 gate,是把 issue 砌死。

- verify:`select(.filename != null)`,refuse 改為大聲揭露但不擋;真正的
  drift 照擋(f14e 控制組守住這件事)。
- check:refuse 的 URL 本來就在 KNOWN 裡,所以它印一句 bare「up-to-date」蓋過
  一個不在磁碟上、也永遠不會在的附件。改成照常報 up-to-date 但另外列出 refuse。
  兩個 consumer 原本朝相反方向壞:一個永久硬擋、一個靜默放行。

**測試環境的既有缺口**:這個 suite 從來沒有 stub `curl`,所以每個 fixture 的
「下載」其實都打了真網路並失敗、記成 download_failed —— 也就是說沒有任何斷言
分得出成功與失敗的下載,f13c 是在一個檔案從未存在的條目上通過的。補上 curl
stub,f13c 加驗 `.error == null` 與檔案真的落地。

**兩條自己寫的空洞斷言,mutation 抓到後才修**:
f14a 用 `bash -c 'run_pa ...'`,而 `run_pa` 是 shell function —— 子 shell 裡
沒有它,指令失敗、管線無輸出、否定 grep 因此通過;改成先導到檔案再斷言。
f14c/f14d 的 needle 是裸字 `refused`,而摘要行與逐 URL 行都含這個字,刪掉任一
行另一行都能滿足它;改成各自唯一的字串,並補「有沒有指出是哪個 URL」。

150 + 48 條,56 個 suite 全綠;每條新斷言都以 mutation 驗過重量。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant