feat(skills): spec-2.3 skill invocation#95
Conversation
Why: third leg of the Skills chain — let the model see, select, and enter skill scopes inside the diagnose loop. - invoke.SkillTool (wire name "Skill", CC parity Q3): body-as-tool_result (explicit deviation Q11), args five-shape validation, Q7 model notice, semantic failures recoverable / ctx fatal (two-track, FROZEN 1.21) - skills.PromptSection: name-sorted available-skills section; injected via llmapp.Options.SystemPrompt (production's first system prompt, empty→non-empty cache transition, Q10) - diagnose.ToolOutput.ToolFilter (additive) + Loop scope enforcement: applyFilter per turn (nil keeps slice identity), dispatch guard after lookup / before dedup with full results+emit pairing (denied tools never stuck Running; transcript stays paired — user regression mandate), scope update reads out (never gated on !cached), intra-turn immediate effect (Q12), "Skill" implicitly retained (Q13) - block adapter "Skill(<name>)" header + args summary - bootstrap skillsForChat: log+skip on contract violation (never panics — user mandate), R-10 plugin provenance + R-7 64KiB body warnings at startup - 3 errcodes (NOT_FOUND/INVOKE_INVALID in invoke; SCOPE_TOOL_DENIED in diagnose — loop composes the denial text, cycle-free placement) + central manifest + gen-error-codes blank-import - imports_test: app/skills leaf allowlist + directional parent-not- child rule; invoke allowlist - integration: golden code-reviewer.md full chain + cached replay Coverage: skills 96.4% / invoke 94.3% / diagnose 93.4% / adapter 92.1% / bootstrap 89.9%. Zero new deps; existing diagnose tests unmodified. Spec: spec-2.3-skill-invocation.md
Why: claude post-impl 双路 review (code-reviewer + go-reviewer 均 APPROVE-WITH-FIXES, 0 CRIT/0 HIGH) findings absorb: - cr MED-1: scopeDeniedContent → colon-style template per spec D-6 pinned contract (was bracket style; invisible to Contains-based tests) - cr MED-2: Layer-2 Skill adapter cases (running / args summary / no-skill placeholder) + parked fixture dirs + ParkedFixtures list - go MED-2: errcode.Newf sentinel references (ErrInvokeInvalid.Code() / skills.ErrNamespaceConflict.Code()) replace string literals — a code rename now breaks the build, not first runtime call - cr LOW-2 / go MED-1 (dual-hit): new logger.InfoForceFile (mirrors WarnForceFile); 'skills active' success line demoted to info band - cr LOW-1: skill discovery gated on perr == nil — no wasted fs scan on the provider-error path - go LOW-2: scope-update comment notes nil ToolFilter = inherit (never clears) on both fresh and cached paths - go LOW-3: applyFilter identity test tightened (len/cap guard before element-0 address compare) - cr NIT-1: PromptSection defensively dedupes duplicate keys (first occurrence wins; unreachable in production via 2.2 Active contract) make gate Layer-2 PASSED; existing tests still 0-modification green. Spec: spec-2.3-skill-invocation.md
Post-impl review trace — Route 1 (claude code-reviewer)Post-impl review trace — Route 2 (claude go-reviewer)R-fix (
|
| Finding | Fix |
|---|---|
| cr MED-1 模板 bracket→colon | scopeDeniedContent 改 spec D-6 pinned colon 形态 |
| cr MED-2 Layer-2 Skill 用例缺 | +3 cases (running / args summary / no-skill) + parked dirs + ParkedFixtures 清单 |
| go MED-2 裸字符串码 | ErrInvokeInvalid.Code() + skills.ErrNamespaceConflict.Code() sentinel 引用 |
| 双路收敛 log level | 新 logger.InfoForceFile(镜像 WarnForceFile); 成功行降 info 带 |
| cr LOW-1 error-path I/O | discovery gated on perr == nil |
| go LOW-2/LOW-3 + cr NIT-1 | 注释补 nil=inherit / identity 测试加 len/cap guard / PromptSection 防御 dedupe |
make gate Layer-2 PASSED;既有测试仍 0 修改全绿。
待 Route 3 (codex) post-impl review + 用户 review → FROZEN(registry append + 双仓 tag)。
Codex Route 3/3 — Post-Impl ReviewVerdict: APPROVE-WITH-FIXES FindingsMED-1 — Recommended fix: route NIT-1 — New source comments still contain Chinese rule references. Verified
|
Why: codex post-impl Route 3 (APPROVE-WITH-FIXES) absorb: - MED: InfoForceFile was routed through forceFile, whose forceFileAttrs clamps any level below WARN up to WARN — the normal 'skills active' startup line landed in the warning band as [WARN]. Now calls fileOnlyAttrs(LevelInfo, ...) directly (same bypass-debug-gate, no-stderr, no-op-before-Init semantics) + direct test asserting [INFO] band, file reached with debug disabled, no stderr, and explicitly NOT [WARN]. - NIT: Rule-13 sweep — Chinese rule references in NEW comments translated to English (scope.go / invoke errors.go+skilltool.go / bootstrap skills.go / 3 test files). Pre-existing FROZEN-era Chinese comments in diagnose errors.go/loop.go left untouched (out of scope). make gate Layer-2 PASSED. Spec: spec-2.3-skill-invocation.md
codex R-fix (
|
| Finding | Fix |
|---|---|
| MED: InfoForceFile 被 clamp 成 [WARN] | 根因确认:forceFileAttrs 把 < LevelWarn 一律抬到 WARN(warn/error 带归一器)。改为直走 fileOnlyAttrs(LevelInfo, ...)(同样绕过 debug gate、不进 stderr、Init 前 no-op)。Direct test TestInfoForceFileKeepsInfoBand:断言 [INFO] 落盘(DebugEnabled=false)、stderr 零写入、且显式断言非 [WARN]。 |
| NIT: Rule-13 中文注释扫尾 | 新增注释中 9 处中文规则引用全部英化(scope.go / invoke errors.go + skilltool.go / bootstrap skills.go / scope_test + invoke 两个 test)。diff 新增行中文计数 = 0。FROZEN 既有中文注释(diagnose errors.go/loop.go 的 1.21 时代文本)不在本 spec 范围,未动。 |
make gate Layer-2 PASSED + CI 三次绿(run 27048700756)。
三路 post-impl 全部闭合(cr + go + codex 均 APPROVE-WITH-FIXES → R-fix ×2 absorb)。待用户 review/merge → FROZEN(registry append ordinal 53 + tag-spec.sh 双仓 v0.53.0-stage2.53)。
Codex Route 3 — Final R-fix VerificationVerdict: APPROVE (recorded as a comment because GitHub does not allow the current authenticated user to formally approve their own PR). Verified head: Closed findings:
Validation:
This closes codex Route 3 for spec-2.3. Ready for merge/FROZEN/tag sequence. |
Why: PR #95 (spec-2.3 impl) omitted the git-hooks/spec-registry.txt fallback-copy row that prior spec PRs carried (e.g. #93 for 2.2). The pre-push hook prefers the sibling opendbrb registry (already updated @ opendbrb 5bb091e) so tagging was unblocked; this restores the fallback copy's sync. Tag v0.53.0-stage2.53 already pushed dual-repo. Spec: spec-2.3-skill-invocation.md Co-authored-by: sqlrush <sqlrush@sqlrushdeMacBook-Pro.local>
What
spec-2.3-skill-invocation implementation (design: opendbrb PR #80, APPROVED @ R2 2026-06-05, 三路 pre-impl review + 用户 path 4/4).
Third leg of the Skills chain: SkillTool + description injection + body injection + allowed-tools enforcement.
Deliverables
invoke.SkillTool— wire nameSkill(Q3 CC parity); args 5-shape validation; semantic failures recoverable / ctx fatal (FROZEN 1.21 two-track); Q7 model noticeinternal/app/skills/invoke/skills.PromptSection— name-sorted; pinned first-line extraction; leaf-pureinternal/app/skills/prompt.goToolOutput.ToolFilter(additive) + Loop enforcement: per-turn advertise filter (nil = slice identity), dispatch guard after lookup / before dedup with full results+emit pairing, scope update readsout(never!cached-gated), intra-turn immediate (Q12),Skillimplicitly retained (Q13)diagnose/{tool,loop,scope}.goSkill(<name>)per-tool adapter + args summaryblock/adapter/skill.goskillsForChat: log+skip, never panic (user mandate); real seamllmapp.Options.SystemPrompt(production's first system prompt — empty→non-empty, Q10); R-10 plugin provenance + R-7 64KiB warningsbootstrap/{skills,tui_launcher}.goSCOPE_TOOL_DENIEDregistered in diagnose (loop composes denial text; invoke→diagnose import is one-way — cycle-free placement,TOOL_TIMEOUTprecedent)invoke/errors.go+diagnose/errors.gocode-reviewer.mdfull chain (invoke→body→narrow→denied paired→implicit retention→replace→real exec) + cached replay keeps scopetests/integration/skillinvoke/skills/imports_test.goUser-mandated regressions (approve note 2026-06-05) ✅
TestRun_ScopeDenied_PairedAndNotStuck— denied tool emits EventToolCall+EventToolResult (UI never stuck Running), transcript paired-commit intactTestSkillsForChat_ContractViolation_LogSkipNoPanic— SkillTool construction error → log + skip, interact continues规则 21 全调用方验证(ToolOutput 加字段 + loop.go 改动)
ToolOutputcallers: tool.go(定义) / tool_clock.go / tool_echo.go(零值 nil 语义不变) / dedup.go(存整结构,重放测试) / loop.go(classifyToolErr 只拷 Content/IsError — wire 隔离,测) / 全部测试字面量 named-field(grep 证实 0 positional)→ 既有 diagnose 测试 0 修改全绿Quality
make gateLayer-2 PASSED;go mod tidy0-diff(0 新依赖);import-rules-check 0 violation;errcode manifest +3(sorted append)偏离点(实现层,post-impl review 注意)
SCOPE_TOOL_DENIED注册位置 =diagnose/errors.go(spec D-6 表写 invoke/errors.go)— invoke→diagnose 单向 import 强制;code 字符串/三件套/用途均按 spec。三路 pre-impl review 未覆盖此拓扑细节;建议 FROZEN 时 spec § D-6 加一行 errata 注。UI review(规则 20)
D-4 是新增 per-tool adapter(共用组件 registry 追加条目,不改既有 adapter):Layer 1 invariants + 既有 block golden/PTY 全绿。repo 无
area:renderlabel——Layer 4 触发待 spec-0.11.5 infra;Layer 5 截图随 post-impl 手验一并。Next
Spec: spec-2.3-skill-invocation.md (opendbrb PR #80)