From 45121bac1d2cf7ef3ab5177d512bcfcf30500c79 Mon Sep 17 00:00:00 2001 From: che cheng Date: Thu, 2 Jul 2026 16:14:51 +0800 Subject: [PATCH 1/2] feat: plugin-scoped binary install dir (.bin-cache) replaces shared ~/bin (#117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wrappers x3: INSTALL_DIR = $PLUGIN_ROOT/.bin-cache — cross-marketplace same-name collisions impossible by construction; plugin updates recycle the cache (re-download passes the full verification chain) - legacy ~/bin/ copy left untouched (may be a manual install); one-line stderr note on first spawn - shells bumped (word 3.20.2, pdf/pptx 0.1.2) via #116 decoupling; CHANGELOG + marketplace.json synced; .gitignore rule for .bin-cache E2E: fresh install into .bin-cache + legacy note; fast path; dual- marketplace simulation (two PLUGIN_ROOTs coexist, no interference). Refs #117 --- .claude-plugin/marketplace.json | 6 +++--- .gitignore | 3 +++ plugins/che-pdf-mcp/.claude-plugin/plugin.json | 2 +- plugins/che-pdf-mcp/CHANGELOG.md | 6 ++++++ plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh | 17 ++++++++++++++--- plugins/che-pptx-mcp/.claude-plugin/plugin.json | 2 +- plugins/che-pptx-mcp/CHANGELOG.md | 6 ++++++ .../che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh | 17 ++++++++++++++--- plugins/che-word-mcp/.claude-plugin/plugin.json | 2 +- plugins/che-word-mcp/CHANGELOG.md | 6 ++++++ .../che-word-mcp/bin/che-word-mcp-wrapper.sh | 17 ++++++++++++++--- 11 files changed, 69 insertions(+), 15 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8725a0a..f0aab30 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "plugins": [ { "name": "che-word-mcp", - "version": "3.20.1", + "version": "3.20.2", "description": "v3.20.0 cross-document OMath splice MCP tools (#160) — splice_omath_from_source + splice_paragraph_omath_from_source wrap ooxml-swift v0.24.0's spliceOMath API for verbatim copy of XML blocks between WordDocument paragraphs. Source via source_path (Direct mode read-only) or source_doc_id (Session mode); target requires session-mode doc_id. Position via atStart/atEnd/afterText/beforeText with optional anchor + instance. Carrier preservation (Run.rawXML stays inline, unrecognizedChildren stays direct-child); joint document-order index for omath_index across both source carriers. rpr_mode controls source Run rPr propagation (full default verbatim / omathOnly whitelist / discard empty); namespace_policy controls prefix vs URI handling (lenient default accepts mml: vs m: prefix mismatch with same URI per ECMA-376 / strict throws on any mismatch). Error taxonomy returned as structured strings: sourceHasNoOMath / omathIndexOutOfRange / targetParagraphOutOfRange / anchorNotFound / namespaceMismatch / contextAnchorNotFound (batch only). Unblocks kiki830621/collaboration_guo_analysis Phase 7 inline-math restoration pipeline. Tests: 8 new Issue160SpliceOMathFromSourceTests (Direct/Session source modes, atEnd/afterText positions, error taxonomy, batch mode, rPr discard). Full suite: 297 passing, 0 failures, 9 pre-existing skips. Bumps ooxml-swift dep 0.21.0 → 0.24.0. v3.19.0 caption detection (#136) + estimate_paragraph_for_page structural weights v2 (#142). #136: two-layer detection — Paragraph.style primary + expanded prefix set (English Tab./Fig./Listing + CJK 表3/圖1 no-separator + U+3000 ideographic space) with digit-after-prefix guard rejecting body sentences like \"Table reservations are required...\". 17 new tests. #142: walker upgrade getParagraphs → collectStructuralBlocks enum (.paragraph/.table/.imageOnlyParagraph/.displayEquationParagraph). Per-block weights (12pt thesis calibration): table = tableRows × avgCellChars (200/row fallback), image = +200/drawing, display eq = 120 chars. New structural_breakdown metadata (9 sub-fields). API method bumped char_count_heuristic → char_count_heuristic_v2. ~95× thesis figure-counting accuracy improvement. paragraph_index semantics + getParagraphs() preserved (30+ other callers unaffected). Tests: 6 new + #89 backward-compat passes (text-only fixtures unchanged, just method literal updated). 1 P3 follow-up filed (#159 display-eq fixture limitation). v3.18.1 transitive dependency bump — ooxml-swift v0.21.11 → v0.22.1. Closes PsychQuant/che-word-mcp#155 (parent / mirror) auto-resolves via Package.resolved bump alone (no MCP source change). Pre-fix: che-word-mcp__search_text MCP tool's Server.swift:10310 calls para.getText(), which was a legacy 7-line implementation that only joined runs.map { $0.text } + hyperlink.text — missed every walker enhancement landed in flattenedDisplayText() over the #85 / #92 / #99 / #100 / #101 / #102 / #103 cluster. Callers couldn't grep for inline math symbols (α / β / γ / θ / λ / t) — silent zero gaps in match positions. Post-fix: ooxml-swift#43 collapsed Paragraph.getText() to single-line return flattenedDisplayText(); two text-extraction paths now return identical strings. Server.swift:10310 still calls para.getText() but that method now traverses inline OMML correctly. Position arithmetic now matches before_text / after_text anchor-matching paths. Downstream impact: kiki830621/collaboration_guo_analysis#6 (thesis 30 inline math symbols) unblocked from automation — re-running search_text 進行t檢定 against 碩士論文.docx para 324 now returns a match. Released via /idd-all #43 --cwd cross-repo IDD orchestration (issue-driven-dev v2.40.0). Closing summary at https://github.com/PsychQuant/ooxml-swift/issues/43#issuecomment-4365430488. v3.18.0 insert_equation argument-contract hardening (closes #105 #106 #107). v3.17.8 closes PsychQuant/che-word-mcp#98 — insert_equation MCP handler refactored across 3 commits (91506f8/357cbe7/339ab77). Pre-fix three structural defects: (1) silent-clamp on out-of-range paragraph_index via non-throwing insertParagraph(_:at: Int) — tool reported success but inserted at wrong location; (2) lib's #84/#91 InsertLocation overload + InsertLocationError.inlineModeRequiresParagraphIndex / .invalidParagraphIndex(Int) structured errors never reached MCP callers (handler self-built OMML and bypassed lib); (3) inline mode (display_mode=false) always created a NEW Paragraph(runs: [eqRun]) instead of appending OMML run to the EXISTING paragraph at paragraph_index. v1 (91506f8) tried delegating to lib but Codex 6-AI verify caught P1 regression: lib's Document.insertEquation overload internally uses @available(*, deprecated, ...) MathEquation flat output (Field.swift:301) — emits truncated '(a)/(b' AND nested invalid OOXML. v2 (357cbe7) unified handler — both latex AND components paths use MathComponent.toOMML() for structurally correct OMML; display mode routes through lib's throwing insertParagraph(_:at: InsertLocation); inline mode handler-side appends OMML run to existing paragraph. v3 (339ab77) restored eqPara.properties.alignment = .center for display mode. BREAKING for inline mode callers: pre-fix inserted NEW paragraph; post-fix appends OMML run to EXISTING paragraph at paragraph_index. Migration: use display_mode=true for 'new paragraph with equation' or call insert_paragraph + insert_equation separately. 7 NEW Issue98InsertEquationLibBypassTests pin contracts (5 RED→GREEN scenarios + 2 quality regression tests including unzip -p document.xml verifying structure + centering survives + deprecated '(a)/(b)' pattern absent). 6-AI verify ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh) caught both P1 regressions in v1; Codex sanity check on v2 caught the centering P2. 6 P2/P3 follow-up issues filed (#105-#110). Suite: 236 → 243 tests, 0 failures, 9 pre-existing skips. Backward compatible except inline-mode BREAKING (documented). v3.17.7 ooxml-swift dep bump 0.21.10 → 0.21.11 — closes 5-issue cluster PsychQuant/che-word-mcp #99 + #100 + #101 + #102 + #103. Bilateral mirror coverage for direct-child OMML at 4 wrapper positions ( direct child for Pandoc display math / direct child / direct child / nested wrapper combos), plus 2 NEW library-wide spec capabilities. Pre-fix Paragraph.flattenedDisplayText AND Document.replaceInParagraphSurfaces shared a symmetric blind spot: direct-child / (not wrapped in ) was silently dropped → anchor lookups against paragraphs containing display math silently 0-matched. Spectra change flatten-replace-omml-bilateral-coverage. 2 NEW spec capabilities promoted to openspec/specs/: ooxml-paragraph-text-mirror (mirror invariant + ReplaceResult informative refusal contract) + ooxml-library-design-principles (Correctness primacy + Human-like operations as foundational normative invariants for all ooxml-swift mutators). Read side: flattenedDisplayText walks direct-child OMML at all 4 wrapper positions with source-XML position ordering. Write side: NEW public API WordDocument.replaceTextWithBoundaryDetection returns ReplaceResult enum (.replaced(count:) / .refusedDueToOMMLBoundary(occurrences:) / .mixed(replacedCount:, refusedOccurrences:)) with Occurrence(matchSpan:, ommlSpans:) carrying flattened-text coordinates. Mirror invariant — asymmetric by design: reads include OMML visibleText (anchor lookup universe extends to math); writes treat OMML as opaque structural units (refuse cross-OMML mutation rather than silently delete equations). Decision 4 raw passthrough preserved: direct-child OMML stays in Paragraph.unrecognizedChildren / HyperlinkChild.rawXML(_) / AlternateContent.rawXML — no parser change, no writer change, round-trip fidelity unaffected. MCP impact: replace_text and other anchor-lookup tools now find paragraphs containing direct-child OMML at all 4 wrapper positions; existing replace_text MCP tool unchanged (backward-compatible). Tests: 236 passing che-word-mcp / 813→829 ooxml-swift (+16 in Issue99FlattenReplaceOMMLBilateralTests). Backward compatible — strict superset of pre-fix behavior. v3.17.6 ooxml-swift dep bump 0.21.9 → 0.21.10 — closes PsychQuant/che-word-mcp#104. Form-level FieldParser canonical 5-run fldChar fix (orthogonal to v3.17.5's #94 container-level fix). Pre-fix update_all_fields returned silent no-op ('no SEQ fields found') on docs containing valid SEQ paragraphs at body top level when fldChar block was emitted in canonical 5-run form (each // in its own sibling — what DocxReader produces post-roundtrip and what native Word always emits). Pre-fix worked only on in-memory wrap_caption_seq output before save. Two ooxml-swift commits land via this dep bump: 537de62 FieldParser two-phase parse (Phase-1 baked form + Phase-2 parseFiveRunSpan state machine probing both Run.rawXML and Run.rawElements per recognizedRunChildren = ['rPr','t','drawing','oMath','oMathPara'] allowlist) + 58fe4f9 P1 sub-fix surfaced by 6-AI verify (Logic + Devil's Advocate runtime test): canonical-branch Run.text rewrite was silently overridden by Run.toXML() rawXML short-circuit; new rewriteCanonicalCachedText helper splices new value into embedded while preserving + xml:space=preserve, AND keeps Run.text in sync. MCP impact: update_all_fields now finds and updates SEQ fields in canonical 5-run form (post-roundtrip / native Word emission); list_captions benefits transitively via shared FieldParser. Verified by 6-AI ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh) — 4 PASS / 2 WARN / 0 BLOCK; production reproducer rescue-swift-v317.docx via DocxReader path confirmed working. 5 P3 follow-ups filed in ooxml-swift (#29 SEQ Table coverage / #30 multi-paragraph counter / #31 multi-SEQ same paragraph / #32 DoS hardening / #33 discriminator invariant). Tests: 236 passing che-word-mcp / 809→813 ooxml-swift (+4 sub-tests in Issue104FieldParserCanonicalFormTests). Backward compatible — strict superset of pre-fix behavior. v3.17.5 ooxml-swift dep bump 0.21.8 → 0.21.9 — triple #87 + #93 + #94 release. #87 (Comment.paragraphIndex flat-counter, observable behavior change): list_comments paragraph_index now consistently 0-indexed against get_paragraphs() flat list; pre-fix off-by-N for any docx with non-paragraph BodyChild siblings before commented paragraph; callers manually compensating with paragraph_index - 1 must remove compensation. #93 (wrap_caption_seq SEQ inherits source position, caption visual fix): pre-fix 「圖 4-1:xxx」 became 「圖 4-:xxx1」 because new SEQ run had position=nil while source-loaded preText/postText had position>0; one-line fix seqRun.position = preRun.position; insert_bookmark=true × source-loaded paragraph still has same gap (filed PsychQuant/ooxml-swift#24, default insert_bookmark=false unaffected). #94 (update_all_fields traverses .table and .contentControl containers): pre-fix body loop only processed top-level .paragraph BodyChild, silently skipped .table and .contentControl(_, children:) — SEQ fields inside table cells/block-level SDTs never updated, returning 'no SEQ fields found' for thesis docs (caption paragraphs commonly live inside the table they describe); same gap #68 closed for findBodyChildContainingText; new walkAndProcessBodyChildForFields recursive walker mirrors #68 pattern; heading-count semantics: only top-level direct .paragraph body children count toward chapter-reset. Known incompleteness (3 follow-ups filed): ooxml-swift#25 header/footer/footnote/endnote SEQ scans still flat .paragraphs view; ooxml-swift#26 FieldParser.parse(paragraph:) misses inline SDT/hyperlink/fieldSimple/alternateContent surfaces; ooxml-swift#27 verify-with-user-fixture for real thesis docx roundtrip; plus ooxml-swift#28 refactor candidate (extract BodyChildVisitor protocol). Verified by 6-AI ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh). Tests: 236 passing che-word-mcp / 805→809 ooxml-swift. Backward compatible except #87 documented behavior change. v3.17.4 paired #91 + #92 release. v3.17.3 bump ooxml-swift dep 0.21.6 → 0.21.7 (pure transitive bump, no MCP source changes; exposes public anchor lookup API). Three WordDocument APIs upgraded private → public (PsychQuant/che-word-mcp#86): findBodyChildContainingText(_:nthInstance:) instance method + bodyChildContainsText(_:needle:) static + tableContainsText(_:needle:) static. External Swift SPM consumers (rescue scripts, dxedit CLI, third-party tooling) can now call canonical anchor-lookup logic directly instead of reimplementing with diverging semantics (some skipped .contentControl recursion, some skipped table cell traversal pre-#68, some used different nthInstance counting rules). Result is exactly what insert_paragraph / insert_image_from_path / insert_caption etc. tools see when resolving after_text / before_text anchors. 10 new public-API surface tests in Issue86PublicAnchorLookupTests pin the canonical behavior across releases. Backward compatible: pure additive private → public visibility change; no API removals, no behavioral changes for existing callers. v3.17.2 bump ooxml-swift dep 0.21.2 → 0.21.6 (pure transitive bump, no MCP source changes; 4 ooxml-swift releases worth of hardening + new APIs land transparently). v0.21.3 (XML hardening, PsychQuant/ooxml-swift#7): DTD reject + 64KB attr-value cap + SAX-based root-element attribute parsing + name whitelist on emit. New XMLHardeningError throws on malicious .docx input. v0.21.4 (roundtrip loud-fail, PsychQuant/ooxml-swift#6): AlternateContent.fallbackRunsModified dirty flag throws RoundtripError.unserializedFallbackEdit on stale fallbackRuns mutation. Run.commentIds @available deprecated; migrate to commentRangeMarkers. v0.21.5 (insertEquation flexibility, PsychQuant/che-word-mcp#84 #85): InsertLocation overload for Document.insertEquation + flattenedDisplayText OMML coverage extends anchor lookup beyond plain runs. v0.21.6 (mutation surface, PsychQuant/ooxml-swift#5): Hyperlink.text setter @available deprecated (lossy); migrate to .runs property. Position field cascade Int = 0 → Int? = nil across 13 typed-child models. xml:space=preserve autosense in Run.toXMLThrowing emit. Plus 3 unreleased docs commits on ooxml-swift main (PsychQuant/ooxml-swift#14 #15 #17 + corrective cd841e7) covering needsPPr emit-gate ↔ Issue4 lock-in test bidirectional reference, parseRun vs parseParagraph walker pattern divergence rationale, foreign-namespace pPr asymmetry documentation. Tests: 236 passing (no regressions). Backward compatible: deprecation warnings only; no API removals. v3.17.1 bump ooxml-swift dep to v0.21.2 — pulls in pPr regression-guard + test infrastructure hardening from upstream (ooxml-swift#4 walker whitelist + #if DEBUG assert / ooxml-swift#13 empty self-closing test gap / ooxml-swift#16 countPPrOpenTags regex hardening excluding ). No public MCP tool change. v3.17.0 wrap_caption_seq MCP tool (Refs #62): Phase 2 of cross-repo work — exposes ooxml-swift v0.21.0 lib API as MCP tool. Bulk-wraps plain-text caption number portions in SEQ field runs across body paragraphs whose flattened text matches a regex (EXACTLY ONE numeric capture group). Captured digit becomes SEQ field cachedResult so Word's first-open render preserves user-typed numbering before F9. Rescues docs pasted from external sources (LaTeX-converted Word, Google Docs, Pandoc) so insert_table_of_figures / insert_table_of_tables produce populated TOFs. Idempotent: paragraphs already wrapping a SEQ field for sequence_name reported in skipped, never double-wrapped (detection covers both FieldSimple AND rawXML fldChar emissions). Phase 1 ships scope:body only (recurses into table cells + nestedTables + block-level SDT children); scope:all returns Error: scope_not_implemented for now (cross-container path lands in v3.17.x). Bookmark wrap opt-in (insert_bookmark + bookmark_template with literal ${number}) so default 23-caption rescue does NOT pollute list_bookmarks. Returns JSON: {matched_paragraphs, fields_inserted, paragraphs_modified:[idx,...], skipped:[{paragraph_index, reason},...]}. All preconditions checked BEFORE document mutation (regex compile + capture-group count + format/scope enums + bookmark_template invariant + doc_id opened). Tests: 5 new sub-tests in Issue62WrapCaptionSeqTests covering Scenarios 1-5. Suite 231 → 236 (+5, 0 fail / 9 skip). No ooxml-swift dep bump (still v0.21.0 from v3.16.2). v3.16.2 ooxml-swift dep bump 0.20.5 → 0.21.0 (Refs #62 #68): pure dep bump, no MCP source changes. Picks up two ooxml-swift fixes that surface transparently via existing tool dispatch. #68 (ooxml-swift v0.20.6): InsertLocation.findBodyChildContainingText now traverses .table (rows × cells × paragraphs + nestedTables) and .contentControl(_, children:) (recursive). MCP impact — insert_paragraph / insert_image_from_path / insert_equation / insert_caption calls using before_text / after_text now succeed when anchor text lives inside a table cell or block-level SDT (common in thesis docs with figure/table captions inside table cells). Returned position is top-level body.children index of the containing structure. Use into_table_cell for inside-cell inserts. Empty-needle guard: passing before_text:'' / after_text:'' now returns textNotFound instead of silently inserting at index 1. #62 (ooxml-swift v0.21.0): WordDocument.wrapCaptionSequenceFields(...) is now linked into the binary. Not yet exposed as an MCP tool — the wrap_caption_seq MCP wrapper ships in v3.17.0 (Phase 2 of the cross-repo work). Existing MCP tools unaffected. Suite: 231 → 231 (0 fail / 9 skip). v3.16.1 anchorPresence whitelist drift prevention (Refs #80): pure refactor, no runtime behavior change. New static toolAnchorWhitelists dict (single source of truth, keyed by MCP tool name → accepted anchor list) + new detectPresentAnchors(_:tool:) overload. 4 conflict-detection call sites switched from literal anchor arrays to (tool:) lookup. 4 new invariant/parity tests. Suite 227 → 231 (+4). Old (args, anchors:) overload preserved. Out-of-scope follow-ups: schema descriptions + dispatcher if-else chains still hardcode anchor names (pre-existing surfaces, not introduced by this PR). v3.16.0 Bundle B anchor DX consistency (Refs #70 #71 #72): BREAKING (input validation only) — three coordinated MCP-layer changes across the 4 #61-target tools. #71 (behavior) silent priority on conflicting anchors → structured error: insert_paragraph(after_text + index) was previously silent-priority; now returns 'Error: insert_paragraph: received conflicting anchors: after_text + index. Specify exactly one.' New static helper detectPresentAnchors with per-anchor type-aware predicates (null and wrong-type values do NOT count). #72 (validation) explicit text_instance ≤ 0 rejected — 'Error: : text_instance must be ≥ 1, got .' Omitted text_instance still defaults to 1. #70 (DX) all 32 'return Error:' lines in 4 #61-target tools rewritten as 'Error: : ' for AI-caller error attribution. throw WordError.* paths unchanged. Scope deliberately limited to 4 tools; remaining 41 return Error lines elsewhere deferred to error-prefix-sweep follow-up. SemVer rationale (minor not major): no schema break, no tool removal, restricting previously-undefined behavior. Tests: 201 → 227 (+26 sub-tests, 0 fail / 9 skip). No ooxml-swift dep bump (still v0.20.5). v3.15.3 Bundle A2 polish from v3.15.2 verify R3-R6 follow-ups (Refs #76 #77 #78 #79): #76 (docs) insert_caption description corrected from '三種 anchor' to enumerate all 5 (paragraph_index / after_image_id / after_table_index / after_text / before_text); insert_equation paragraph_index description clarified that the int is body.children-indexed (cross-references PsychQuant/ooxml-swift#10 for the lib-layer convention split). #77 (docs) insert_caption anchor set wording precision in CHANGELOG / manifest / marketplace.json / plugin.json — was 'its own anchor set including after_table_index' (implies disjoint), now 'shares after_image_id / after_text / before_text / paragraph_index, adds after_table_index + position, lacks into_table_cell' (explicit shared/adds/lacks). #78 (test) extends #69 append-index regression pin to bookmarkMarker / rawBlockElement / block-level contentControl body-children — the table case alone wouldn't catch a regression to getParagraphs().count - 1 that breaks for SDT / TOC bookmark / vendor extensions. #79 (test) adds round-trip depth: testInsertParagraphAppendIndexRoundTripsForInsertCalls demonstrates insert-family round-trip works (append + insert(N+1) + verify ordering); testInsertParagraphAppendIndexCannotRoundTripToUpdate pins the cross-family trade-off (update_paragraph(index=N) throws WordError.invalidIndex). Tests: 196 → 201 (+5 sub-tests, 0 fail / 9 skip). No production code change. No ooxml-swift dep bump (still v0.20.5). v3.15.2 closes Bundle A polish from #61 R2 verify (Refs #69 #73 #74 #75): #69 (bug) insert_paragraph append message reports body.children index instead of getParagraphs().count - 1 (mis-reported in docs with tables/SDTs by skipping table children); #74 (bug) insert_image_from_path debug log labels after_image_id correctly (was silently labeled 'index' since v3.15.1); #73 (test) regression pin for equation F5 partial-dict guard (existed since v3.15.1 but was untested); #75 (docs) clarifies '3 insert tools' wording — scope is the 3 #61-target tools (insert_paragraph / insert_equation / insert_image_from_path); insert_caption is a 4th insert tool with a partially-overlapping anchor set (shares after_image_id / after_text / before_text / paragraph_index, adds after_table_index + position, lacks into_table_cell), intentionally outside this unification scope. Tests: 194 → 196 (0 fail / 9 skip). No behavior change in normal call paths. No ooxml-swift dep bump (still v0.20.5). Word MCP Server - Swift 原生 OOXML 操作,233 個工具。v3.15.1 closes verify findings F1+F2+F3+F5 from v3.15.0 6-AI ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh):F1 (P1) `after_image_id` anchor 加到 insert_paragraph + insert_equation (display only) + insert_image_from_path — lib InsertLocation.afterImageId 從 #44 起就 ready 但只有 insert_caption 暴露 MCP-layer;v3.15.0 inherited 這個 gap,本 release 補齊。F2 (P1) `into_table_cell` 加到 insert_equation (display only) — display equation 是新建 paragraph,cell 放置 well-defined;inline mode 拒絕。F3 (P2) equation 成功訊息加 anchor info('Inserted equation (display mode: true, after text X (instance N))' 等)— 關閉同 v3.14.4 LOOKUP 的 over-claim 模式(caller 之前無法區分 anchor 命中 vs append fallthrough)。F5 (P2) malformed `into_table_cell` partial dict(傳 `{table_index: 0}` 缺 row + col)silent fallthrough → 走 next anchor / append → 結果在錯位置且 caller 不知。改回 structured 'Error: into_table_cell requires all three fields',3 #61-target tools 同步修(cross-cutting consistency)。Anchor priority unified across all 3 #61-target insert tools (`insert_paragraph` / `insert_equation` / `insert_image_from_path`; `insert_caption` has its own anchor set):into_table_cell > after_image_id > after_text > before_text > index > append。Inline equation 拒絕擴大 — 現在拒絕所有 4 個 anchor params(before/after_text + after_image_id + into_table_cell),不只 v3.15.0 的 2 個。Tests: Issue61V315PointReleaseTests (9 sub-tests cross 3 tools)。Suite 185 → 194 (0 fail / 9 pre-existing skips)。**No ooxml-swift dep bump** — 仍 v0.20.5(lib 從 #44 起就 ready)。Follow-up issues 另開:F4 inline equation 更通用設計 (e.g. into_paragraph_with_text) / F6 text anchor 擴及 table-cell paragraphs 與 block-level SDT / F7 getParagraphs().count - 1 message 在 doc 含 tables/SDTs 時 mis-report (pre-existing) / F8 error message 加 tool-prefix / F9 multiple anchor params 同時傳入 silent priority winner / F10 text_instance≤0 normalize。Backward compatible — schema additions optional,既有 v3.15.0 callers 不變;只有 malformed into_table_cell 從 silent fallthrough 改成 structured error(會被 buggy caller 注意到)+ equation message 加 suffix(substring 'Inserted equation' 仍存在)。v3.15.0 closes #61 — insert_paragraph 與 insert_equation 現在接受跟 insert_image_from_path 一致的 anchor 參數(after_text / before_text / text_instance / into_table_cell — into_table_cell 僅 insert_paragraph)。Pre-fix MCP 層 silently drop 這些參數 — JSON schema 接受但 handler dispatch 忽略,呼叫 fall through 到 legacy paragraph_index path 或 append at end。Lib API Document.insertParagraph(_: at: InsertLocation) 從 #44 起就支援所有六種 anchor cases(paragraphIndex / afterImageId / afterTableIndex / intoTableCell / afterText / beforeText),本 release 補齊 MCP 側 wire-up gap,無需 ooxml-swift dep bump(v0.20.5 已足夠)。Anchor priority mirror insert_image_from_path:into_table_cell > after_text > before_text > index > append。Errors(textNotFound / tableIndexOutOfRange / tableCellOutOfRange)回 structured 訊息而非 silent fallthrough — AI caller 能 surface failure 而非拿到位置錯誤的 misleading 'success'。**Inline equation explicit rejection**:insert_equation 在 display_mode=false(inline)時 explicitly 拒絕 after_text / before_text,回 structured error — 語意模糊('append OMML run into existing para containing this text' vs 'insert new para before/after target para'),inline placement 仍用 paragraph_index。Display-mode equation 建新 paragraph,anchor 語意明確。Tests: Issue61InsertParagraphAnchorsSmokeTests(5 sub-tests:after_text resolution / before_text resolution / text_instance disambiguation / into_table_cell append / textNotFound error)+ Issue61InsertEquationAnchorsSmokeTests(4 sub-tests:after_text + before_text in display mode / inline mode rejection / textNotFound error)。Suite 176 → 185 (0 fail / 9 pre-existing skips)。**No ooxml-swift dep bump** — v0.20.5 已有所有需要的 lib API。Backward compatible — anchor params 全 optional;既有 index / paragraph_index callers 不變;無 schema removal、無既有行為改動。**Real-world impact**:thesis-rescue / template-population workflow 不再需要 fall back 到「append at end + 手動 cut/paste in Word UI」或 binary-search 猜 paragraph_index,AI caller 對 3 #61-target insert tools(insert_image_from_path / insert_paragraph / insert_equation)對稱地用 surrounding context 定位 anchor。v3.14.5 closes Refs #63 verify F1 P1:擴充 findBodyChildContainingText 涵蓋所有 editable surfaces,補上 v3.14.4 CHANGELOG over-claim 的 insert anchor lookup gap。Pre-fix v3.14.4 只修了 REPLACE path(replace_text → Document.replaceInParagraphSurfaces 走 contentControls / hyperlinks / fieldSimples / alternateContents)但 LOOKUP path(findBodyChildContainingText 用於 InsertLocation.afterText / .beforeText 解析)只看 para.runs,所以 insert_image_from_path / insert_paragraph / insert_caption before_text/after_text 對 SDT-wrapped anchor 仍丟 textNotFound。Verify ensemble(5 Claude reviewers + Codex)的 requirements F1 P1 finding 抓到 CHANGELOG over-claim — 用戶選擇 Option B 擴充修而非縮 scope。ooxml-swift v0.20.5 新增 TextReplacementEngine.flatTextOfContentXML(read-only XML walker mirror replaceInContentXML flattening rules,跳過 / / nested subtrees)+ Paragraph.flattenedDisplayText 擴充 method 涵蓋 runs + hyperlinks + fieldSimples + alternateContents + contentControls(recursive into nested SDT children)。findBodyChildContainingText 改用 flattenedDisplayText 取代原本的 para.runs.map { $0.text }.joined()。新增 Issue63InsertAnchorInlineSDTTests(lib,3 wrappers × afterText/beforeText/insertImage = 3 sub-tests / 5 assertions)+ Issue63InsertAnchorInlineSDTSmokeTests(MCP,2 sub-tests pin lib-layer fix)。Suite 693 → 696 ooxml-swift / 174 → 176 che-word-mcp(0 fail)。基於 ooxml-swift v0.20.5。Backward compatible — strict superset of pre-fix lookup behavior(找到更多 anchors,既有 plain runs anchor 仍照常運作)。**Insert anchor lookup gap 此 release 完整補齊**,所有 inline wrappers 在 REPLACE + LOOKUP 兩個 path 都對稱覆蓋。v3.14.4 修 replace_text 對 inline `` content control 的 wrapper coverage gap(Refs #63):Document.replaceInParagraphSurfaces 之前覆蓋 paragraph.runs / hyperlinks / fieldSimples / alternateContents 但 **沒有** paragraph.contentControls — 包在 inline `` 裡的文字 silently 0-match。外部 converter(pandoc / Quarto / LaTeX→docx)習慣把 cross-ref placeholder([tab:foo] / [fig:bar] / [Smith 2020])包成 inline SDT,所以症狀跟 bracketed text 高度相關,但其實 **brackets 是 coincidence** — bracket-free needle 在 inline SDT 裡也 fail。Issue title「literal `[ ]` brackets」是誤導,差別測試(fldChar / fldSimple / hyperlink / inlineSDT 四個 inline wrapper × 四種 needle)證實只有 inline SDT case 失敗,其他三個 wrapper 從 v0.19.0+ #56 Phase 5 起就 typed-runs 覆蓋好了。Surgical fix architecture:ooxml-swift v0.20.4 新增 TextReplacementEngine.replaceInContentXML(XML DOM walker,wrap ContentControl.content 在 synthetic root xmlns:w,遍歷所有 `` descendants 在 document order,build flat string + offset map mirror flattenRuns invariant,run same literal/regex find logic,splice replacements 回 `` element string content;re-serialize wrapper children 去掉 wrapper tag)+ Document.replaceInContentControl(recursive helper 涵蓋 cc.content + cc.children 處理 nested SDT)。Wired 進 Document.replaceInParagraphSurfaces 接在 alternateContents loop 之後。設計上跳過:``(TC deletion text,不顯示)、``(field instruction code,不顯示)、nested `` subtrees(typed cc.children 由外層 recursion 處理避免 double-replacement)。Round-trip discipline:只 mutate `` element 的 string content;xml:space=\"preserve\" 與其他 attribute 完整保留(attribute set 從不被 touch)。新增 Issue63InlineSDTReplaceTests(4 個 wrapper × 4 個 needle 的 differential test + nested SDT recursion + round-trip wrapper preservation = 3 sub-tests / 18 assertions)+ MCP-layer Issue63ReplaceTextInlineSDTSmokeTests(2 sub-tests pin lib-layer fix)。Suite 690 → 693 ooxml-swift / 172 → 174 che-word-mcp(0 fail)。基於 ooxml-swift v0.20.4。Backward compatible — surgical fix 只新增 code path,沒改任何既有行為(runs/hyperlinks/fieldSimples/alternateContents replacement path 不動,ContentControl model 維持 raw XML storage 不重構)。Out-of-scope(separate follow-up):ContentControl 從 content:String 升級為 typed Run 列表(SDD-warranted refactor);smartTags / bidiOverrides / customXmlBlocks / unrecognizedChildren 維持 raw-carrier passthrough。v3.14.3 sub-stack E of paragraph-level content-equality (closes #66):Paragraph 新增 w14ParaId / w14TextId 欄位,提取並 round-trip opening tag 上的 w14:* 屬性(Word 用於 collaborative editing 和 comment threading 的 revision-tracking GUIDs)。Plain attribute passthrough,String? typing — Word 的 GUIDs 是 8-char hex tokens(NOT RFC 4122 UUIDs),所以 opaque-string round-trip 是正確選擇。Pre-fix v3.14.2 silently dropped 兩個 attributes — 佔了 NTPU 論文 fixture w14:* token loss 的 ~95%(2214 / 2359 lost tokens 是這兩個 attrs)。Post-E 量測:w14: 保留率 10.55% → 93.98%;document.xml 流失 10.95% → 8.02%。Combined with sub-stack D (#65), total impact since v3.14.1: 50% → 98.89% (D)、w14:* 5% → 93.98% (E)、document.xml 流失 16.66% → 8.02% (D+E, -8.64 pp)。Matrix-pin testDocumentContentEqualityInvariant 同步抬升 floor(w14: 0.04→0.90、sizeLossRatio 上限 0.12→0.10)— matrix-pin 現在 LOAD-BEARING across **5 preservation classes**(rFonts/noProof/lang/kern/w14:)spanning run-level + paragraph-level + paragraph-mark scope。Defensive design (R2 review fixes):openingPTag() routes attributes through escapeXMLAttribute;parseParagraph rejects schema-invalid empty-string GUIDs。基於 ooxml-swift v0.20.3。Backward compatible(兩個 fields 都 optional、default nil;openingPTag empty-attrs gate 防止 synthetic emit)。剩餘 8% 流失主要是其他 w14:* attribute classes(如 w14:* on )— tracked as separate follow-up SDD。v3.14.2 sub-stack D of paragraph-level content-equality (closes #65):ParagraphProperties 新增 markRunProperties 欄位,提取並 round-trip direct child of — paragraph-mark formatting per ECMA-376 §17.3.1.27 CT_PPrBase(控制 pilcrow ¶ 字符外觀的字型/顏色/語言/字距)。Reuses parseRunProperties verbatim — schema 跟 run-level CT_RPr 一致,所以 sub-stack C 的 typed extraction(rFonts 4-axis / noProof / kern / lang 3-axis)和 rawChildren passthrough(w14:* 效果)全部免費繼承。NTPU 論文 fixture 量測影響: 保留率 50% → 98.89%; 88% → 98.77%; 92% → 100%; 84% → 99.93%;document.xml 大小流失 16.66% → 10.95%。Matrix-pin testDocumentContentEqualityInvariant 同步抬升 floor(lang 0.45→0.95、rFonts/noProof/kern 0.95、sizeLossRatio 上限 0.175→0.12)。Sub-stack E (#66 w14:paraId/textId) 接著 ship 到 v3.14.3,把流失壓到 < 5%,達成「edit 一個字 → document.xml shrinks <1%」strong demo。基於 ooxml-swift v0.20.2。Backward compatible(markRunProperties optional、default nil、writer empty-gate 防止 synthetic empty )。v3.14.1 sub-stack C-CONT closes triple-confirmed P0 (R2 + R5 + Codex 6-AI verify):recognizedRprChildren Set 列了 ~16+ rPr child kinds 為 'recognized' 但 parseRunProperties 沒有 typed extraction → silent drop。受影響的常見元素:(character spacing)、/(run shading)、(CJK emphasis marks)、///////。Fix:trim Set 到 ONLY actually-typed-extracted-or-emitted kinds。Round-trip size loss: pre-fix v3.13.x 32% → v3.14.0 17.75% → v3.14.1 16.66%。Methodology lesson (6th):P2 from one reviewer can become P0 when another applies real-world impact lens. v3.14.0 closes #60(sub-stack C of #58/#59/#60)— RunProperties field-loss audit。Bump ooxml-swift v0.19.13→v0.20.0。新增 typed fields:4-axis rFonts (ascii/hAnsi/eastAsia/cs/hint — 之前被收斂成單一值)、noProof、kern、3-axis lang (val/eastAsia/bidi),加上 rawChildren passthrough 處理 unrecognized rPr children(如 w14:textOutline / w14:textFill / w14:glow)。**Pre-fix MCP 用戶看到 eastAsia/cs 字型(如 DFKai-SB 用於繁體中文)在 round-trip 時 silently 被替換成 ascii 值;v3.14.0 完整保留 4 個 axis**。Matrix-pin testDocumentContentEqualityInvariant 加上 preservation-class-3 ratio-floor assertions,現在 LOAD-BEARING — 任何未來 RunProperties regression 都會被 matrix-pin 抓到。Thesis fixture document.xml round-trip 大小:pre-fix 32% 損失 → post-sub-stack-C 17.75% 損失(改善 14.25 percentage points)。剩餘 17.75% 是 paragraph-mark rPr + w14:paraId/textId drops(separate out-of-scope follow-up SDD)。**'if not typed, preserve as raw' 原則架構性完成** — 從 sub-stack A (#58 BodyChild)、B (#59 WhitespaceOverlay) 一路發展到 C (#60 RunProperties)。Backward compatible — 保留 fontName field,mirror rFonts.ascii。v3.13.13 CRITICAL HOTFIX (sub-stack B-CONT-2-CONT) reverted v3.13.12 的 TIER-0 over-fix。v3.13.12 (DO NOT USE — 刪除 內容)。v3.13.11 sub-stack B-CONT。基於 ooxml-swift v0.20.0。", "author": { "name": "Che Cheng" @@ -30,7 +30,7 @@ }, { "name": "che-pdf-mcp", - "version": "0.1.1", + "version": "0.1.2", "description": "PDF 文件處理 MCP server — PDFKit 解析與文字提取、Vision OCR(原生 macOS)、圖片/區域擷取、亂碼區域偵測。v0.1.0: 首次 marketplace 發布(signed + notarized universal binary)。", "author": { "name": "Che Cheng" @@ -41,7 +41,7 @@ }, { "name": "che-pptx-mcp", - "version": "0.1.1", + "version": "0.1.2", "description": "PowerPoint (.pptx) MCP server — PresentationML 解析與生成:slides、shapes、tables、notes、theme、markdown 匯出。v0.1.0: 首次 marketplace 發布(signed + notarized universal binary)。", "author": { "name": "Che Cheng" diff --git a/.gitignore b/.gitignore index 278f42b..1ba322c 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,6 @@ reference/* !reference/textutil-manpage.txt .spectra/ openspec/.vector-search.db* + +# Plugin-scoped binary cache (#117) — downloaded MCP binaries + version sidecars +plugins/*/.bin-cache/ diff --git a/plugins/che-pdf-mcp/.claude-plugin/plugin.json b/plugins/che-pdf-mcp/.claude-plugin/plugin.json index d3a23ef..7126094 100644 --- a/plugins/che-pdf-mcp/.claude-plugin/plugin.json +++ b/plugins/che-pdf-mcp/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "che-pdf-mcp", "description": "PDF 文件處理 MCP server — PDFKit 解析與文字提取、Vision OCR(原生 macOS)、圖片/區域擷取、亂碼區域偵測。 v0.1.0: 首次 marketplace 發布。", - "version": "0.1.1", + "version": "0.1.2", "binary_version": "0.1.0", "author": { "name": "Che Cheng" diff --git a/plugins/che-pdf-mcp/CHANGELOG.md b/plugins/che-pdf-mcp/CHANGELOG.md index ca8b93b..79d934f 100644 --- a/plugins/che-pdf-mcp/CHANGELOG.md +++ b/plugins/che-pdf-mcp/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the che-pdf-mcp plugin shell will be documented in this f The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [0.1.2] - 2026-07-02 + +### Changed + +- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 `$PLUGIN_ROOT/.bin-cache/` — 跨 marketplace 同名 plugin 碰撞 by construction 不可能;plugin 更新自然汰換 cache(重下載走完整驗證鏈)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 + ## [0.1.1] - 2026-07-02 ### Changed diff --git a/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh b/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh index bf0d999..ad3abce 100755 --- a/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh +++ b/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh @@ -24,9 +24,6 @@ set -u REPO="PsychQuant/che-pdf-mcp" BINARY_NAME="ChePDFMCP" -INSTALL_DIR="$HOME/bin" -BINARY="$INSTALL_DIR/$BINARY_NAME" -VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" SCRIPT_ARGS=("$@") # Locate plugin root via wrapper's own path (more reliable than $CLAUDE_PLUGIN_ROOT @@ -34,6 +31,20 @@ SCRIPT_ARGS=("$@") PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json" +# Plugin-scoped install dir (#117): every marketplace x plugin x version gets +# its own binary copy inside the plugin's own cache dir — cross-marketplace +# same-name collisions are impossible by construction, and a plugin update +# naturally recycles the cache (re-download passes the full verification +# chain). The legacy shared ~/bin/ copy, if any, is left alone — +# it may be a user's manual install. +INSTALL_DIR="$PLUGIN_ROOT/.bin-cache" +BINARY="$INSTALL_DIR/$BINARY_NAME" +VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" + +if [[ -x "$HOME/bin/$BINARY_NAME" ]] && [[ ! -x "$BINARY" ]]; then + echo "$BINARY_NAME: note — legacy copy at ~/bin/$BINARY_NAME is no longer used by this plugin (now plugin-scoped); left untouched" >&2 +fi + verify_binary() { # Developer ID Application (marker OIDs) + Team OU pin. Runs on every # candidate before exec — download-time AND exec-time (#112 verify R2: diff --git a/plugins/che-pptx-mcp/.claude-plugin/plugin.json b/plugins/che-pptx-mcp/.claude-plugin/plugin.json index 1de4b8f..f69dbf8 100644 --- a/plugins/che-pptx-mcp/.claude-plugin/plugin.json +++ b/plugins/che-pptx-mcp/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "che-pptx-mcp", "description": "PowerPoint (.pptx) MCP server — PresentationML 解析與生成:slides、shapes、tables、notes、theme、markdown 匯出。 v0.1.0: 首次 marketplace 發布。", - "version": "0.1.1", + "version": "0.1.2", "binary_version": "0.1.0", "author": { "name": "Che Cheng" diff --git a/plugins/che-pptx-mcp/CHANGELOG.md b/plugins/che-pptx-mcp/CHANGELOG.md index e17e144..676d812 100644 --- a/plugins/che-pptx-mcp/CHANGELOG.md +++ b/plugins/che-pptx-mcp/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the che-pptx-mcp plugin shell will be documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [0.1.2] - 2026-07-02 + +### Changed + +- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 `$PLUGIN_ROOT/.bin-cache/` — 跨 marketplace 同名 plugin 碰撞 by construction 不可能;plugin 更新自然汰換 cache(重下載走完整驗證鏈)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 + ## [0.1.1] - 2026-07-02 ### Changed diff --git a/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh b/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh index e5a5504..9723259 100755 --- a/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh +++ b/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh @@ -24,9 +24,6 @@ set -u REPO="PsychQuant/che-pptx-mcp" BINARY_NAME="ChePPTXMCP" -INSTALL_DIR="$HOME/bin" -BINARY="$INSTALL_DIR/$BINARY_NAME" -VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" SCRIPT_ARGS=("$@") # Locate plugin root via wrapper's own path (more reliable than $CLAUDE_PLUGIN_ROOT @@ -34,6 +31,20 @@ SCRIPT_ARGS=("$@") PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json" +# Plugin-scoped install dir (#117): every marketplace x plugin x version gets +# its own binary copy inside the plugin's own cache dir — cross-marketplace +# same-name collisions are impossible by construction, and a plugin update +# naturally recycles the cache (re-download passes the full verification +# chain). The legacy shared ~/bin/ copy, if any, is left alone — +# it may be a user's manual install. +INSTALL_DIR="$PLUGIN_ROOT/.bin-cache" +BINARY="$INSTALL_DIR/$BINARY_NAME" +VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" + +if [[ -x "$HOME/bin/$BINARY_NAME" ]] && [[ ! -x "$BINARY" ]]; then + echo "$BINARY_NAME: note — legacy copy at ~/bin/$BINARY_NAME is no longer used by this plugin (now plugin-scoped); left untouched" >&2 +fi + verify_binary() { # Developer ID Application (marker OIDs) + Team OU pin. Runs on every # candidate before exec — download-time AND exec-time (#112 verify R2: diff --git a/plugins/che-word-mcp/.claude-plugin/plugin.json b/plugins/che-word-mcp/.claude-plugin/plugin.json index 1b082e6..5c84840 100644 --- a/plugins/che-word-mcp/.claude-plugin/plugin.json +++ b/plugins/che-word-mcp/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "che-word-mcp", "description": "v3.20.0 cross-document OMath splice MCP tools (#160) — splice_omath_from_source + splice_paragraph_omath_from_source wrap ooxml-swift v0.24.0's spliceOMath API for verbatim copy of XML blocks between WordDocument paragraphs. Source via source_path (Direct mode read-only) or source_doc_id (Session mode); target requires session-mode doc_id. Position via atStart/atEnd/afterText/beforeText with optional anchor + instance. Carrier preservation (Run.rawXML stays inline, unrecognizedChildren stays direct-child); joint document-order index for omath_index across both source carriers. rpr_mode controls source Run rPr propagation (full default verbatim / omathOnly whitelist / discard empty); namespace_policy controls prefix vs URI handling (lenient default accepts mml: vs m: prefix mismatch with same URI per ECMA-376 / strict throws on any mismatch). Error taxonomy returned as structured strings: sourceHasNoOMath / omathIndexOutOfRange / targetParagraphOutOfRange / anchorNotFound / namespaceMismatch / contextAnchorNotFound (batch only). Unblocks kiki830621/collaboration_guo_analysis Phase 7 inline-math restoration pipeline. Tests: 8 new Issue160SpliceOMathFromSourceTests (Direct/Session source modes, atEnd/afterText positions, error taxonomy, batch mode, rPr discard). Full suite: 297 passing, 0 failures, 9 pre-existing skips. Bumps ooxml-swift dep 0.21.0 → 0.24.0. v3.19.0 caption detection (#136) + estimate_paragraph_for_page structural weights v2 (#142). #136: two-layer detection — Paragraph.style primary + expanded prefix set (English Tab./Fig./Listing + CJK 表3/圖1 no-separator + U+3000 ideographic space) with digit-after-prefix guard rejecting body sentences like \"Table reservations are required...\". 17 new tests. #142: walker upgrade getParagraphs → collectStructuralBlocks enum (.paragraph/.table/.imageOnlyParagraph/.displayEquationParagraph). Per-block weights (12pt thesis calibration): table = tableRows × avgCellChars (200/row fallback), image = +200/drawing, display eq = 120 chars. New structural_breakdown metadata (9 sub-fields). API method bumped char_count_heuristic → char_count_heuristic_v2. ~95× thesis figure-counting accuracy improvement. paragraph_index semantics + getParagraphs() preserved (30+ other callers unaffected). Tests: 6 new + #89 backward-compat passes (text-only fixtures unchanged, just method literal updated). 1 P3 follow-up filed (#159 display-eq fixture limitation). v3.18.1 transitive dependency bump — ooxml-swift v0.21.11 → v0.22.1. Closes PsychQuant/che-word-mcp#155 (parent / mirror) auto-resolves via Package.resolved bump alone (no MCP source change). Pre-fix: che-word-mcp__search_text MCP tool's Server.swift:10310 calls para.getText(), which was a legacy 7-line implementation that only joined runs.map { $0.text } + hyperlink.text — missed every walker enhancement landed in flattenedDisplayText() over the #85 / #92 / #99 / #100 / #101 / #102 / #103 cluster. Callers couldn't grep for inline math symbols (α / β / γ / θ / λ / t) — silent zero gaps in match positions. Post-fix: ooxml-swift#43 collapsed Paragraph.getText() to single-line return flattenedDisplayText(); two text-extraction paths now return identical strings. Server.swift:10310 still calls para.getText() but that method now traverses inline OMML correctly. Position arithmetic now matches before_text / after_text anchor-matching paths. Downstream impact: kiki830621/collaboration_guo_analysis#6 (thesis 30 inline math symbols) unblocked from automation — re-running search_text 進行t檢定 against 碩士論文.docx para 324 now returns a match. Released via /idd-all #43 --cwd cross-repo IDD orchestration (issue-driven-dev v2.40.0). Closing summary at https://github.com/PsychQuant/ooxml-swift/issues/43#issuecomment-4365430488. v3.18.0 insert_equation argument-contract hardening (closes #105 #106 #107). v3.17.8 closes PsychQuant/che-word-mcp#98 — insert_equation MCP handler refactored across 3 commits (91506f8/357cbe7/339ab77). Pre-fix three structural defects: (1) silent-clamp on out-of-range paragraph_index via non-throwing insertParagraph(_:at: Int) — tool reported success but inserted at wrong location; (2) lib's #84/#91 InsertLocation overload + InsertLocationError.inlineModeRequiresParagraphIndex / .invalidParagraphIndex(Int) structured errors never reached MCP callers (handler self-built OMML and bypassed lib); (3) inline mode (display_mode=false) always created a NEW Paragraph(runs: [eqRun]) instead of appending OMML run to the EXISTING paragraph at paragraph_index. v1 (91506f8) tried delegating to lib but Codex 6-AI verify caught P1 regression: lib's Document.insertEquation overload internally uses @available(*, deprecated, ...) MathEquation flat output (Field.swift:301) — emits truncated '(a)/(b' AND nested invalid OOXML. v2 (357cbe7) unified handler — both latex AND components paths use MathComponent.toOMML() for structurally correct OMML; display mode routes through lib's throwing insertParagraph(_:at: InsertLocation); inline mode handler-side appends OMML run to existing paragraph. v3 (339ab77) restored eqPara.properties.alignment = .center for display mode. BREAKING for inline mode callers: pre-fix inserted NEW paragraph; post-fix appends OMML run to EXISTING paragraph at paragraph_index. Migration: use display_mode=true for 'new paragraph with equation' or call insert_paragraph + insert_equation separately. 7 NEW Issue98InsertEquationLibBypassTests pin contracts (5 RED→GREEN scenarios + 2 quality regression tests including unzip -p document.xml verifying structure + centering survives + deprecated '(a)/(b)' pattern absent). 6-AI verify ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh) caught both P1 regressions in v1; Codex sanity check on v2 caught the centering P2. 6 P2/P3 follow-up issues filed (#105-#110). Suite: 236 → 243 tests, 0 failures, 9 pre-existing skips. Backward compatible except inline-mode BREAKING (documented). v3.17.7 ooxml-swift dep bump 0.21.10 → 0.21.11 — closes 5-issue cluster PsychQuant/che-word-mcp #99 + #100 + #101 + #102 + #103. Bilateral mirror coverage for direct-child OMML at 4 wrapper positions ( direct child for Pandoc display math / direct child / direct child / nested wrapper combos), plus 2 NEW library-wide spec capabilities. Pre-fix Paragraph.flattenedDisplayText AND Document.replaceInParagraphSurfaces shared a symmetric blind spot: direct-child / (not wrapped in ) was silently dropped → anchor lookups against paragraphs containing display math silently 0-matched. Spectra change flatten-replace-omml-bilateral-coverage. 2 NEW spec capabilities promoted to openspec/specs/: ooxml-paragraph-text-mirror (mirror invariant + ReplaceResult informative refusal contract) + ooxml-library-design-principles (Correctness primacy + Human-like operations as foundational normative invariants for all ooxml-swift mutators). Read side: flattenedDisplayText walks direct-child OMML at all 4 wrapper positions with source-XML position ordering. Write side: NEW public API WordDocument.replaceTextWithBoundaryDetection returns ReplaceResult enum (.replaced(count:) / .refusedDueToOMMLBoundary(occurrences:) / .mixed(replacedCount:, refusedOccurrences:)) with Occurrence(matchSpan:, ommlSpans:) carrying flattened-text coordinates. Mirror invariant — asymmetric by design: reads include OMML visibleText (anchor lookup universe extends to math); writes treat OMML as opaque structural units (refuse cross-OMML mutation rather than silently delete equations). Decision 4 raw passthrough preserved: direct-child OMML stays in Paragraph.unrecognizedChildren / HyperlinkChild.rawXML(_) / AlternateContent.rawXML — no parser change, no writer change, round-trip fidelity unaffected. MCP impact: replace_text and other anchor-lookup tools now find paragraphs containing direct-child OMML at all 4 wrapper positions; existing replace_text MCP tool unchanged (backward-compatible). Tests: 236 passing che-word-mcp / 813→829 ooxml-swift (+16 in Issue99FlattenReplaceOMMLBilateralTests). Backward compatible — strict superset of pre-fix behavior. v3.17.6 ooxml-swift dep bump 0.21.9 → 0.21.10 — closes PsychQuant/che-word-mcp#104. Form-level FieldParser canonical 5-run fldChar fix (orthogonal to v3.17.5's #94 container-level fix). Pre-fix update_all_fields returned silent no-op ('no SEQ fields found') on docs containing valid SEQ paragraphs at body top level when fldChar block was emitted in canonical 5-run form (each // in its own sibling — what DocxReader produces post-roundtrip and what native Word always emits). Pre-fix worked only on in-memory wrap_caption_seq output before save. Two ooxml-swift commits land via this dep bump: 537de62 FieldParser two-phase parse (Phase-1 baked form + Phase-2 parseFiveRunSpan state machine probing both Run.rawXML and Run.rawElements per recognizedRunChildren = ['rPr','t','drawing','oMath','oMathPara'] allowlist) + 58fe4f9 P1 sub-fix surfaced by 6-AI verify (Logic + Devil's Advocate runtime test): canonical-branch Run.text rewrite was silently overridden by Run.toXML() rawXML short-circuit; new rewriteCanonicalCachedText helper splices new value into embedded while preserving + xml:space=preserve, AND keeps Run.text in sync. MCP impact: update_all_fields now finds and updates SEQ fields in canonical 5-run form (post-roundtrip / native Word emission); list_captions benefits transitively via shared FieldParser. Verified by 6-AI ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh) — 4 PASS / 2 WARN / 0 BLOCK; production reproducer rescue-swift-v317.docx via DocxReader path confirmed working. 5 P3 follow-ups filed in ooxml-swift (#29 SEQ Table coverage / #30 multi-paragraph counter / #31 multi-SEQ same paragraph / #32 DoS hardening / #33 discriminator invariant). Tests: 236 passing che-word-mcp / 809→813 ooxml-swift (+4 sub-tests in Issue104FieldParserCanonicalFormTests). Backward compatible — strict superset of pre-fix behavior. v3.17.5 ooxml-swift dep bump 0.21.8 → 0.21.9 — triple #87 + #93 + #94 release. #87 (Comment.paragraphIndex flat-counter, observable behavior change): list_comments paragraph_index now consistently 0-indexed against get_paragraphs() flat list; pre-fix off-by-N for any docx with non-paragraph BodyChild siblings before commented paragraph; callers manually compensating with paragraph_index - 1 must remove compensation. #93 (wrap_caption_seq SEQ inherits source position, caption visual fix): pre-fix 「圖 4-1:xxx」 became 「圖 4-:xxx1」 because new SEQ run had position=nil while source-loaded preText/postText had position>0; one-line fix seqRun.position = preRun.position; insert_bookmark=true × source-loaded paragraph still has same gap (filed PsychQuant/ooxml-swift#24, default insert_bookmark=false unaffected). #94 (update_all_fields traverses .table and .contentControl containers): pre-fix body loop only processed top-level .paragraph BodyChild, silently skipped .table and .contentControl(_, children:) — SEQ fields inside table cells/block-level SDTs never updated, returning 'no SEQ fields found' for thesis docs (caption paragraphs commonly live inside the table they describe); same gap #68 closed for findBodyChildContainingText; new walkAndProcessBodyChildForFields recursive walker mirrors #68 pattern; heading-count semantics: only top-level direct .paragraph body children count toward chapter-reset. Known incompleteness (3 follow-ups filed): ooxml-swift#25 header/footer/footnote/endnote SEQ scans still flat .paragraphs view; ooxml-swift#26 FieldParser.parse(paragraph:) misses inline SDT/hyperlink/fieldSimple/alternateContent surfaces; ooxml-swift#27 verify-with-user-fixture for real thesis docx roundtrip; plus ooxml-swift#28 refactor candidate (extract BodyChildVisitor protocol). Verified by 6-AI ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh). Tests: 236 passing che-word-mcp / 805→809 ooxml-swift. Backward compatible except #87 documented behavior change. v3.17.4 paired #91 + #92 release. v3.17.3 bump ooxml-swift dep 0.21.6 → 0.21.7 (pure transitive bump, no MCP source changes; exposes public anchor lookup API). Three WordDocument APIs upgraded private → public (PsychQuant/che-word-mcp#86): findBodyChildContainingText(_:nthInstance:) instance method + bodyChildContainsText(_:needle:) static + tableContainsText(_:needle:) static. External Swift SPM consumers (rescue scripts, dxedit CLI, third-party tooling) can now call canonical anchor-lookup logic directly instead of reimplementing with diverging semantics (some skipped .contentControl recursion, some skipped table cell traversal pre-#68, some used different nthInstance counting rules). Result is exactly what insert_paragraph / insert_image_from_path / insert_caption etc. tools see when resolving after_text / before_text anchors. 10 new public-API surface tests in Issue86PublicAnchorLookupTests pin the canonical behavior across releases. Backward compatible: pure additive private → public visibility change; no API removals, no behavioral changes for existing callers. v3.17.2 bump ooxml-swift dep 0.21.2 → 0.21.6 (pure transitive bump, no MCP source changes; 4 ooxml-swift releases worth of hardening + new APIs land transparently). v0.21.3 (XML hardening, PsychQuant/ooxml-swift#7): DTD reject + 64KB attr-value cap + SAX-based root-element attribute parsing + name whitelist on emit. New XMLHardeningError throws on malicious .docx input. v0.21.4 (roundtrip loud-fail, PsychQuant/ooxml-swift#6): AlternateContent.fallbackRunsModified dirty flag throws RoundtripError.unserializedFallbackEdit on stale fallbackRuns mutation. Run.commentIds @available deprecated; migrate to commentRangeMarkers. v0.21.5 (insertEquation flexibility, PsychQuant/che-word-mcp#84 #85): InsertLocation overload for Document.insertEquation + flattenedDisplayText OMML coverage extends anchor lookup beyond plain runs. v0.21.6 (mutation surface, PsychQuant/ooxml-swift#5): Hyperlink.text setter @available deprecated (lossy); migrate to .runs property. Position field cascade Int = 0 → Int? = nil across 13 typed-child models. xml:space=preserve autosense in Run.toXMLThrowing emit. Plus 3 unreleased docs commits on ooxml-swift main (PsychQuant/ooxml-swift#14 #15 #17 + corrective cd841e7) covering needsPPr emit-gate ↔ Issue4 lock-in test bidirectional reference, parseRun vs parseParagraph walker pattern divergence rationale, foreign-namespace pPr asymmetry documentation. Tests: 236 passing (no regressions). Backward compatible: deprecation warnings only; no API removals. v3.17.1 bump ooxml-swift dep to v0.21.2 — pulls in pPr regression-guard + test infrastructure hardening from upstream (ooxml-swift#4 walker whitelist + #if DEBUG assert / ooxml-swift#13 empty self-closing test gap / ooxml-swift#16 countPPrOpenTags regex hardening excluding ). No public MCP tool change. v3.17.0 wrap_caption_seq MCP tool (Refs #62): Phase 2 of cross-repo work — exposes ooxml-swift v0.21.0 lib API as MCP tool. Bulk-wraps plain-text caption number portions in SEQ field runs across body paragraphs whose flattened text matches a regex (EXACTLY ONE numeric capture group). Captured digit becomes SEQ field cachedResult so Word's first-open render preserves user-typed numbering before F9. Rescues docs pasted from external sources (LaTeX-converted Word, Google Docs, Pandoc) so insert_table_of_figures / insert_table_of_tables produce populated TOFs. Idempotent: paragraphs already wrapping a SEQ field for sequence_name reported in skipped, never double-wrapped (detection covers both FieldSimple AND rawXML fldChar emissions). Phase 1 ships scope:body only (recurses into table cells + nestedTables + block-level SDT children); scope:all returns Error: scope_not_implemented for now (cross-container path lands in v3.17.x). Bookmark wrap opt-in (insert_bookmark + bookmark_template with literal ${number}) so default 23-caption rescue does NOT pollute list_bookmarks. Returns JSON: {matched_paragraphs, fields_inserted, paragraphs_modified:[idx,...], skipped:[{paragraph_index, reason},...]}. All preconditions checked BEFORE document mutation (regex compile + capture-group count + format/scope enums + bookmark_template invariant + doc_id opened). Tests: 5 new sub-tests in Issue62WrapCaptionSeqTests covering Scenarios 1-5. Suite 231 → 236 (+5, 0 fail / 9 skip). No ooxml-swift dep bump (still v0.21.0 from v3.16.2). v3.16.2 ooxml-swift dep bump 0.20.5 → 0.21.0 (Refs #62 #68): pure dep bump, no MCP source changes. Picks up two ooxml-swift fixes that surface transparently via existing tool dispatch. #68 (ooxml-swift v0.20.6): InsertLocation.findBodyChildContainingText now traverses .table (rows × cells × paragraphs + nestedTables) and .contentControl(_, children:) (recursive). MCP impact — insert_paragraph / insert_image_from_path / insert_equation / insert_caption calls using before_text / after_text now succeed when anchor text lives inside a table cell or block-level SDT (common in thesis docs with figure/table captions inside table cells). Returned position is top-level body.children index of the containing structure. Use into_table_cell for inside-cell inserts. Empty-needle guard: passing before_text:'' / after_text:'' now returns textNotFound instead of silently inserting at index 1. #62 (ooxml-swift v0.21.0): WordDocument.wrapCaptionSequenceFields(...) is now linked into the binary. Not yet exposed as an MCP tool — the wrap_caption_seq MCP wrapper ships in v3.17.0 (Phase 2 of the cross-repo work). Existing MCP tools unaffected. Suite: 231 → 231 (0 fail / 9 skip). v3.16.1 anchorPresence whitelist drift prevention (Refs #80): pure refactor, no runtime behavior change. New static toolAnchorWhitelists dict (single source of truth, keyed by MCP tool name → accepted anchor list) + new detectPresentAnchors(_:tool:) overload. 4 conflict-detection call sites switched from literal anchor arrays to (tool:) lookup. 4 new invariant/parity tests. Suite 227 → 231 (+4). Old (args, anchors:) overload preserved. Out-of-scope follow-ups: schema descriptions + dispatcher if-else chains still hardcode anchor names (pre-existing surfaces, not introduced by this PR). v3.16.0 Bundle B anchor DX consistency (Refs #70 #71 #72): BREAKING (input validation only) — three coordinated MCP-layer changes across the 4 #61-target tools. #71 (behavior) silent priority on conflicting anchors → structured error: insert_paragraph(after_text + index) was previously silent-priority; now returns 'Error: insert_paragraph: received conflicting anchors: after_text + index. Specify exactly one.' New static helper detectPresentAnchors with per-anchor type-aware predicates (null and wrong-type values do NOT count). #72 (validation) explicit text_instance ≤ 0 rejected — 'Error: : text_instance must be ≥ 1, got .' Omitted text_instance still defaults to 1. #70 (DX) all 32 'return Error:' lines in 4 #61-target tools rewritten as 'Error: : ' for AI-caller error attribution. throw WordError.* paths unchanged. Scope deliberately limited to 4 tools; remaining 41 return Error lines elsewhere deferred to error-prefix-sweep follow-up. SemVer rationale (minor not major): no schema break, no tool removal, restricting previously-undefined behavior. Tests: 201 → 227 (+26 sub-tests, 0 fail / 9 skip). No ooxml-swift dep bump (still v0.20.5). v3.15.3 Bundle A2 polish from v3.15.2 verify R3-R6 follow-ups (Refs #76 #77 #78 #79): #76 (docs) insert_caption description corrected from '三種 anchor' to enumerate all 5 (paragraph_index / after_image_id / after_table_index / after_text / before_text); insert_equation paragraph_index description clarified that the int is body.children-indexed (cross-references PsychQuant/ooxml-swift#10 for the lib-layer convention split). #77 (docs) insert_caption anchor set wording precision in CHANGELOG / manifest / marketplace.json / plugin.json — was 'its own anchor set including after_table_index' (implies disjoint), now 'shares after_image_id / after_text / before_text / paragraph_index, adds after_table_index + position, lacks into_table_cell' (explicit shared/adds/lacks). #78 (test) extends #69 append-index regression pin to bookmarkMarker / rawBlockElement / block-level contentControl body-children — the table case alone wouldn't catch a regression to getParagraphs().count - 1 that breaks for SDT / TOC bookmark / vendor extensions. #79 (test) adds round-trip depth: testInsertParagraphAppendIndexRoundTripsForInsertCalls demonstrates insert-family round-trip works (append + insert(N+1) + verify ordering); testInsertParagraphAppendIndexCannotRoundTripToUpdate pins the cross-family trade-off (update_paragraph(index=N) throws WordError.invalidIndex). Tests: 196 → 201 (+5 sub-tests, 0 fail / 9 skip). No production code change. No ooxml-swift dep bump (still v0.20.5). v3.15.2 closes Bundle A polish from #61 R2 verify (Refs #69 #73 #74 #75): #69 (bug) insert_paragraph append message reports body.children index instead of getParagraphs().count - 1 (mis-reported in docs with tables/SDTs by skipping table children); #74 (bug) insert_image_from_path debug log labels after_image_id correctly (was silently labeled 'index' since v3.15.1); #73 (test) regression pin for equation F5 partial-dict guard (existed since v3.15.1 but was untested); #75 (docs) clarifies '3 insert tools' wording — scope is the 3 #61-target tools (insert_paragraph / insert_equation / insert_image_from_path); insert_caption is a 4th insert tool with a partially-overlapping anchor set (shares after_image_id / after_text / before_text / paragraph_index, adds after_table_index + position, lacks into_table_cell), intentionally outside this unification scope. Tests: 194 → 196 (0 fail / 9 skip). No behavior change in normal call paths. No ooxml-swift dep bump (still v0.20.5). Word MCP Server - Swift 原生 OOXML 操作,233 個工具。v3.15.1 closes verify findings F1+F2+F3+F5 from v3.15.0 6-AI ensemble (5 Claude reviewers + Codex gpt-5.5 xhigh):F1 (P1) `after_image_id` anchor 加到 insert_paragraph + insert_equation (display only) + insert_image_from_path — lib InsertLocation.afterImageId 從 #44 起就 ready 但只有 insert_caption 暴露 MCP-layer;v3.15.0 inherited 這個 gap,本 release 補齊。F2 (P1) `into_table_cell` 加到 insert_equation (display only) — display equation 是新建 paragraph,cell 放置 well-defined;inline mode 拒絕。F3 (P2) equation 成功訊息加 anchor info('Inserted equation (display mode: true, after text X (instance N))' 等)— 關閉同 v3.14.4 LOOKUP 的 over-claim 模式(caller 之前無法區分 anchor 命中 vs append fallthrough)。F5 (P2) malformed `into_table_cell` partial dict(傳 `{table_index: 0}` 缺 row + col)silent fallthrough → 走 next anchor / append → 結果在錯位置且 caller 不知。改回 structured 'Error: into_table_cell requires all three fields',3 #61-target tools 同步修(cross-cutting consistency)。Anchor priority unified across all 3 #61-target insert tools (`insert_paragraph` / `insert_equation` / `insert_image_from_path`; `insert_caption` has its own anchor set):into_table_cell > after_image_id > after_text > before_text > index > append。Inline equation 拒絕擴大 — 現在拒絕所有 4 個 anchor params(before/after_text + after_image_id + into_table_cell),不只 v3.15.0 的 2 個。Tests: Issue61V315PointReleaseTests (9 sub-tests cross 3 tools)。Suite 185 → 194 (0 fail / 9 pre-existing skips)。**No ooxml-swift dep bump** — 仍 v0.20.5(lib 從 #44 起就 ready)。Follow-up issues 另開:F4 inline equation 更通用設計 (e.g. into_paragraph_with_text) / F6 text anchor 擴及 table-cell paragraphs 與 block-level SDT / F7 getParagraphs().count - 1 message 在 doc 含 tables/SDTs 時 mis-report (pre-existing) / F8 error message 加 tool-prefix / F9 multiple anchor params 同時傳入 silent priority winner / F10 text_instance≤0 normalize。Backward compatible — schema additions optional,既有 v3.15.0 callers 不變;只有 malformed into_table_cell 從 silent fallthrough 改成 structured error(會被 buggy caller 注意到)+ equation message 加 suffix(substring 'Inserted equation' 仍存在)。v3.15.0 closes #61 — insert_paragraph 與 insert_equation 現在接受跟 insert_image_from_path 一致的 anchor 參數(after_text / before_text / text_instance / into_table_cell — into_table_cell 僅 insert_paragraph)。Pre-fix MCP 層 silently drop 這些參數 — JSON schema 接受但 handler dispatch 忽略,呼叫 fall through 到 legacy paragraph_index path 或 append at end。Lib API Document.insertParagraph(_: at: InsertLocation) 從 #44 起就支援所有六種 anchor cases(paragraphIndex / afterImageId / afterTableIndex / intoTableCell / afterText / beforeText),本 release 補齊 MCP 側 wire-up gap,無需 ooxml-swift dep bump(v0.20.5 已足夠)。Anchor priority mirror insert_image_from_path:into_table_cell > after_text > before_text > index > append。Errors(textNotFound / tableIndexOutOfRange / tableCellOutOfRange)回 structured 訊息而非 silent fallthrough — AI caller 能 surface failure 而非拿到位置錯誤的 misleading 'success'。**Inline equation explicit rejection**:insert_equation 在 display_mode=false(inline)時 explicitly 拒絕 after_text / before_text,回 structured error — 語意模糊('append OMML run into existing para containing this text' vs 'insert new para before/after target para'),inline placement 仍用 paragraph_index。Display-mode equation 建新 paragraph,anchor 語意明確。Tests: Issue61InsertParagraphAnchorsSmokeTests(5 sub-tests:after_text resolution / before_text resolution / text_instance disambiguation / into_table_cell append / textNotFound error)+ Issue61InsertEquationAnchorsSmokeTests(4 sub-tests:after_text + before_text in display mode / inline mode rejection / textNotFound error)。Suite 176 → 185 (0 fail / 9 pre-existing skips)。**No ooxml-swift dep bump** — v0.20.5 已有所有需要的 lib API。Backward compatible — anchor params 全 optional;既有 index / paragraph_index callers 不變;無 schema removal、無既有行為改動。**Real-world impact**:thesis-rescue / template-population workflow 不再需要 fall back 到「append at end + 手動 cut/paste in Word UI」或 binary-search 猜 paragraph_index,AI caller 對 3 #61-target insert tools(insert_image_from_path / insert_paragraph / insert_equation)對稱地用 surrounding context 定位 anchor。v3.14.5 closes Refs #63 verify F1 P1:擴充 findBodyChildContainingText 涵蓋所有 editable surfaces,補上 v3.14.4 CHANGELOG over-claim 的 insert anchor lookup gap。Pre-fix v3.14.4 只修了 REPLACE path(replace_text → Document.replaceInParagraphSurfaces 走 contentControls / hyperlinks / fieldSimples / alternateContents)但 LOOKUP path(findBodyChildContainingText 用於 InsertLocation.afterText / .beforeText 解析)只看 para.runs,所以 insert_image_from_path / insert_paragraph / insert_caption before_text/after_text 對 SDT-wrapped anchor 仍丟 textNotFound。Verify ensemble(5 Claude reviewers + Codex)的 requirements F1 P1 finding 抓到 CHANGELOG over-claim — 用戶選擇 Option B 擴充修而非縮 scope。ooxml-swift v0.20.5 新增 TextReplacementEngine.flatTextOfContentXML(read-only XML walker mirror replaceInContentXML flattening rules,跳過 / / nested subtrees)+ Paragraph.flattenedDisplayText 擴充 method 涵蓋 runs + hyperlinks + fieldSimples + alternateContents + contentControls(recursive into nested SDT children)。findBodyChildContainingText 改用 flattenedDisplayText 取代原本的 para.runs.map { $0.text }.joined()。新增 Issue63InsertAnchorInlineSDTTests(lib,3 wrappers × afterText/beforeText/insertImage = 3 sub-tests / 5 assertions)+ Issue63InsertAnchorInlineSDTSmokeTests(MCP,2 sub-tests pin lib-layer fix)。Suite 693 → 696 ooxml-swift / 174 → 176 che-word-mcp(0 fail)。基於 ooxml-swift v0.20.5。Backward compatible — strict superset of pre-fix lookup behavior(找到更多 anchors,既有 plain runs anchor 仍照常運作)。**Insert anchor lookup gap 此 release 完整補齊**,所有 inline wrappers 在 REPLACE + LOOKUP 兩個 path 都對稱覆蓋。v3.14.4 修 replace_text 對 inline `` content control 的 wrapper coverage gap(Refs #63):Document.replaceInParagraphSurfaces 之前覆蓋 paragraph.runs / hyperlinks / fieldSimples / alternateContents 但 **沒有** paragraph.contentControls — 包在 inline `` 裡的文字 silently 0-match。外部 converter(pandoc / Quarto / LaTeX→docx)習慣把 cross-ref placeholder([tab:foo] / [fig:bar] / [Smith 2020])包成 inline SDT,所以症狀跟 bracketed text 高度相關,但其實 **brackets 是 coincidence** — bracket-free needle 在 inline SDT 裡也 fail。Issue title「literal `[ ]` brackets」是誤導,差別測試(fldChar / fldSimple / hyperlink / inlineSDT 四個 inline wrapper × 四種 needle)證實只有 inline SDT case 失敗,其他三個 wrapper 從 v0.19.0+ #56 Phase 5 起就 typed-runs 覆蓋好了。Surgical fix architecture:ooxml-swift v0.20.4 新增 TextReplacementEngine.replaceInContentXML(XML DOM walker,wrap ContentControl.content 在 synthetic root xmlns:w,遍歷所有 `` descendants 在 document order,build flat string + offset map mirror flattenRuns invariant,run same literal/regex find logic,splice replacements 回 `` element string content;re-serialize wrapper children 去掉 wrapper tag)+ Document.replaceInContentControl(recursive helper 涵蓋 cc.content + cc.children 處理 nested SDT)。Wired 進 Document.replaceInParagraphSurfaces 接在 alternateContents loop 之後。設計上跳過:``(TC deletion text,不顯示)、``(field instruction code,不顯示)、nested `` subtrees(typed cc.children 由外層 recursion 處理避免 double-replacement)。Round-trip discipline:只 mutate `` element 的 string content;xml:space=\"preserve\" 與其他 attribute 完整保留(attribute set 從不被 touch)。新增 Issue63InlineSDTReplaceTests(4 個 wrapper × 4 個 needle 的 differential test + nested SDT recursion + round-trip wrapper preservation = 3 sub-tests / 18 assertions)+ MCP-layer Issue63ReplaceTextInlineSDTSmokeTests(2 sub-tests pin lib-layer fix)。Suite 690 → 693 ooxml-swift / 172 → 174 che-word-mcp(0 fail)。基於 ooxml-swift v0.20.4。Backward compatible — surgical fix 只新增 code path,沒改任何既有行為(runs/hyperlinks/fieldSimples/alternateContents replacement path 不動,ContentControl model 維持 raw XML storage 不重構)。Out-of-scope(separate follow-up):ContentControl 從 content:String 升級為 typed Run 列表(SDD-warranted refactor);smartTags / bidiOverrides / customXmlBlocks / unrecognizedChildren 維持 raw-carrier passthrough。v3.14.3 sub-stack E of paragraph-level content-equality (closes #66):Paragraph 新增 w14ParaId / w14TextId 欄位,提取並 round-trip opening tag 上的 w14:* 屬性(Word 用於 collaborative editing 和 comment threading 的 revision-tracking GUIDs)。Plain attribute passthrough,String? typing — Word 的 GUIDs 是 8-char hex tokens(NOT RFC 4122 UUIDs),所以 opaque-string round-trip 是正確選擇。Pre-fix v3.14.2 silently dropped 兩個 attributes — 佔了 NTPU 論文 fixture w14:* token loss 的 ~95%(2214 / 2359 lost tokens 是這兩個 attrs)。Post-E 量測:w14: 保留率 10.55% → 93.98%;document.xml 流失 10.95% → 8.02%。Combined with sub-stack D (#65), total impact since v3.14.1: 50% → 98.89% (D)、w14:* 5% → 93.98% (E)、document.xml 流失 16.66% → 8.02% (D+E, -8.64 pp)。Matrix-pin testDocumentContentEqualityInvariant 同步抬升 floor(w14: 0.04→0.90、sizeLossRatio 上限 0.12→0.10)— matrix-pin 現在 LOAD-BEARING across **5 preservation classes**(rFonts/noProof/lang/kern/w14:)spanning run-level + paragraph-level + paragraph-mark scope。Defensive design (R2 review fixes):openingPTag() routes attributes through escapeXMLAttribute;parseParagraph rejects schema-invalid empty-string GUIDs。基於 ooxml-swift v0.20.3。Backward compatible(兩個 fields 都 optional、default nil;openingPTag empty-attrs gate 防止 synthetic emit)。剩餘 8% 流失主要是其他 w14:* attribute classes(如 w14:* on )— tracked as separate follow-up SDD。v3.14.2 sub-stack D of paragraph-level content-equality (closes #65):ParagraphProperties 新增 markRunProperties 欄位,提取並 round-trip direct child of — paragraph-mark formatting per ECMA-376 §17.3.1.27 CT_PPrBase(控制 pilcrow ¶ 字符外觀的字型/顏色/語言/字距)。Reuses parseRunProperties verbatim — schema 跟 run-level CT_RPr 一致,所以 sub-stack C 的 typed extraction(rFonts 4-axis / noProof / kern / lang 3-axis)和 rawChildren passthrough(w14:* 效果)全部免費繼承。NTPU 論文 fixture 量測影響: 保留率 50% → 98.89%; 88% → 98.77%; 92% → 100%; 84% → 99.93%;document.xml 大小流失 16.66% → 10.95%。Matrix-pin testDocumentContentEqualityInvariant 同步抬升 floor(lang 0.45→0.95、rFonts/noProof/kern 0.95、sizeLossRatio 上限 0.175→0.12)。Sub-stack E (#66 w14:paraId/textId) 接著 ship 到 v3.14.3,把流失壓到 < 5%,達成「edit 一個字 → document.xml shrinks <1%」strong demo。基於 ooxml-swift v0.20.2。Backward compatible(markRunProperties optional、default nil、writer empty-gate 防止 synthetic empty )。v3.14.1 sub-stack C-CONT closes triple-confirmed P0 (R2 + R5 + Codex 6-AI verify):recognizedRprChildren Set 列了 ~16+ rPr child kinds 為 'recognized' 但 parseRunProperties 沒有 typed extraction → silent drop。受影響的常見元素:(character spacing)、/(run shading)、(CJK emphasis marks)、///////。Fix:trim Set 到 ONLY actually-typed-extracted-or-emitted kinds。Round-trip size loss: pre-fix v3.13.x 32% → v3.14.0 17.75% → v3.14.1 16.66%。Methodology lesson (6th):P2 from one reviewer can become P0 when another applies real-world impact lens. v3.14.0 closes #60(sub-stack C of #58/#59/#60)— RunProperties field-loss audit。Bump ooxml-swift v0.19.13→v0.20.0。新增 typed fields:4-axis rFonts (ascii/hAnsi/eastAsia/cs/hint — 之前被收斂成單一值)、noProof、kern、3-axis lang (val/eastAsia/bidi),加上 rawChildren passthrough 處理 unrecognized rPr children(如 w14:textOutline / w14:textFill / w14:glow)。**Pre-fix MCP 用戶看到 eastAsia/cs 字型(如 DFKai-SB 用於繁體中文)在 round-trip 時 silently 被替換成 ascii 值;v3.14.0 完整保留 4 個 axis**。Matrix-pin testDocumentContentEqualityInvariant 加上 preservation-class-3 ratio-floor assertions,現在 LOAD-BEARING — 任何未來 RunProperties regression 都會被 matrix-pin 抓到。Thesis fixture document.xml round-trip 大小:pre-fix 32% 損失 → post-sub-stack-C 17.75% 損失(改善 14.25 percentage points)。剩餘 17.75% 是 paragraph-mark rPr + w14:paraId/textId drops(separate out-of-scope follow-up SDD)。**'if not typed, preserve as raw' 原則架構性完成** — 從 sub-stack A (#58 BodyChild)、B (#59 WhitespaceOverlay) 一路發展到 C (#60 RunProperties)。Backward compatible — 保留 fontName field,mirror rFonts.ascii。v3.13.13 CRITICAL HOTFIX (sub-stack B-CONT-2-CONT) reverted v3.13.12 的 TIER-0 over-fix。v3.13.12 (DO NOT USE — 刪除 內容)。v3.13.11 sub-stack B-CONT。基於 ooxml-swift v0.20.0。", - "version": "3.20.1", + "version": "3.20.2", "binary_version": "3.20.0", "author": { "name": "Che Cheng" diff --git a/plugins/che-word-mcp/CHANGELOG.md b/plugins/che-word-mcp/CHANGELOG.md index 7e82823..b283d33 100644 --- a/plugins/che-word-mcp/CHANGELOG.md +++ b/plugins/che-word-mcp/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > `plugin.json` description field. Section categorization is best-effort — > review and refine `Added` / `Changed` / `Fixed` etc. as needed. +## [3.20.2] - 2026-07-02 + +### Changed + +- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 `$PLUGIN_ROOT/.bin-cache/` — 跨 marketplace 同名 plugin 碰撞 by construction 不可能;plugin 更新自然汰換 cache(重下載走完整驗證鏈)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 + ## [3.20.1] - 2026-07-02 ### Security diff --git a/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh b/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh index 511c935..3ec6aa6 100755 --- a/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh +++ b/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh @@ -24,9 +24,6 @@ set -u REPO="PsychQuant/che-word-mcp" BINARY_NAME="CheWordMCP" -INSTALL_DIR="$HOME/bin" -BINARY="$INSTALL_DIR/$BINARY_NAME" -VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" SCRIPT_ARGS=("$@") # Locate plugin root via wrapper's own path (more reliable than $CLAUDE_PLUGIN_ROOT @@ -34,6 +31,20 @@ SCRIPT_ARGS=("$@") PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json" +# Plugin-scoped install dir (#117): every marketplace x plugin x version gets +# its own binary copy inside the plugin's own cache dir — cross-marketplace +# same-name collisions are impossible by construction, and a plugin update +# naturally recycles the cache (re-download passes the full verification +# chain). The legacy shared ~/bin/ copy, if any, is left alone — +# it may be a user's manual install. +INSTALL_DIR="$PLUGIN_ROOT/.bin-cache" +BINARY="$INSTALL_DIR/$BINARY_NAME" +VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" + +if [[ -x "$HOME/bin/$BINARY_NAME" ]] && [[ ! -x "$BINARY" ]]; then + echo "$BINARY_NAME: note — legacy copy at ~/bin/$BINARY_NAME is no longer used by this plugin (now plugin-scoped); left untouched" >&2 +fi + verify_binary() { # Developer ID Application (marker OIDs) + Team OU pin. Runs on every # candidate before exec — download-time AND exec-time (#112 verify R2: From 05491052c751378c0339e89f9f3f836e6fd53fad Mon Sep 17 00:00:00 2001 From: che cheng Date: Thu, 2 Jul 2026 16:50:20 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20hoist=20.bin-cache=20to=20plugin=20l?= =?UTF-8?q?evel=20=E2=80=94=20survive=20shell-version=20bumps=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verify DA-1 (refuting three sibling reviews with cache-layout evidence): a version-scoped .bin-cache re-downloads the byte-identical binary on EVERY shell-only version bump (new version dir = fresh gitignored cache), nullifying #116's sidecar short-circuit and multiplying disk per retained version dir. INSTALL_DIR is now dirname(PLUGIN_ROOT)/.bin-cache — //.bin-cache — keeping cross-marketplace/cross-plugin isolation (both in path) while binary + sidecar persist across shell bumps. Dev-checkout context degrades to plugins/.bin-cache with BINARY_NAME scoping. Also: wrapper header sidecar-path comment fixed x3 (logic LOW-2), pdf/pptx README install-path wording (logic LOW-3, own files). E2E: two-version marketplace simulation — v0.1.2 installs, v0.1.3 shell bump fast-paths with ZERO re-download; cross-marketplace dual .bin-cache coexistence; dev-checkout install + gitignore clean tree. Refs #117 --- .gitignore | 1 + plugins/che-pdf-mcp/CHANGELOG.md | 2 +- plugins/che-pdf-mcp/README.md | 2 +- .../che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh | 21 ++++++++++++------- plugins/che-pptx-mcp/CHANGELOG.md | 2 +- plugins/che-pptx-mcp/README.md | 2 +- .../che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh | 21 ++++++++++++------- plugins/che-word-mcp/CHANGELOG.md | 2 +- .../che-word-mcp/bin/che-word-mcp-wrapper.sh | 21 ++++++++++++------- 9 files changed, 45 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 1ba322c..d01895b 100644 --- a/.gitignore +++ b/.gitignore @@ -140,4 +140,5 @@ reference/* openspec/.vector-search.db* # Plugin-scoped binary cache (#117) — downloaded MCP binaries + version sidecars +plugins/.bin-cache/ plugins/*/.bin-cache/ diff --git a/plugins/che-pdf-mcp/CHANGELOG.md b/plugins/che-pdf-mcp/CHANGELOG.md index 79d934f..c4af7c1 100644 --- a/plugins/che-pdf-mcp/CHANGELOG.md +++ b/plugins/che-pdf-mcp/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed -- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 `$PLUGIN_ROOT/.bin-cache/` — 跨 marketplace 同名 plugin 碰撞 by construction 不可能;plugin 更新自然汰換 cache(重下載走完整驗證鏈)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 +- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 **plugin 層級**的 `.bin-cache/`(`//.bin-cache/`,即 version 目錄的上一層)— 跨 marketplace / 跨 plugin 碰撞 by construction 不可能,且跨 shell 版本持久(保留 #116 sidecar 短路,shell-only bump 不重下載;binary_version 變更才重下載)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 ## [0.1.1] - 2026-07-02 diff --git a/plugins/che-pdf-mcp/README.md b/plugins/che-pdf-mcp/README.md index 06d1513..1e5efe8 100644 --- a/plugins/che-pdf-mcp/README.md +++ b/plugins/che-pdf-mcp/README.md @@ -9,7 +9,7 @@ claude plugin marketplace add PsychQuant/macdoc claude plugin install che-pdf-mcp@macdoc ``` -Wrapper 會自動從 [GitHub Releases](https://github.com/PsychQuant/che-pdf-mcp/releases) 下載 release 的 `ChePDFMCP` universal binary 到 `~/bin/`,安裝前與每次啟動時強制驗證 sha256(安裝時)與 Developer ID Application 簽章鏈(Team `6W377FS7BS`)。release 流程含 Apple notarization(wrapper 不重複檢查 notarization)。 +Wrapper 會自動從 [GitHub Releases](https://github.com/PsychQuant/che-pdf-mcp/releases) 下載 release 的 `ChePDFMCP` universal binary 到 plugin 層級的 `.bin-cache/`(跨 marketplace 隔離、跨版本持久),安裝前與每次啟動時強制驗證 sha256(安裝時)與 Developer ID Application 簽章鏈(Team `6W377FS7BS`)。release 流程含 Apple notarization(wrapper 不重複檢查 notarization)。 ## 原始碼 diff --git a/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh b/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh index ad3abce..3e8dfe6 100755 --- a/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh +++ b/plugins/che-pdf-mcp/bin/che-pdf-mcp-wrapper.sh @@ -3,7 +3,7 @@ # # Design: # - Reads desired version from plugin.json (plugin's intended binary version) -# - Compares against ~/bin/.ChePDFMCP.version sidecar +# - Compares against the plugin-level .bin-cache/.ChePDFMCP.version sidecar # - Re-downloads when plugin has been updated but binary is stale # - Unique temp file (mktemp, same fs) + atomic mv so partial downloads never break things # - Pinned version does NOT fall back to releases/latest (supply-chain pinning); @@ -31,13 +31,18 @@ SCRIPT_ARGS=("$@") PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json" -# Plugin-scoped install dir (#117): every marketplace x plugin x version gets -# its own binary copy inside the plugin's own cache dir — cross-marketplace -# same-name collisions are impossible by construction, and a plugin update -# naturally recycles the cache (re-download passes the full verification -# chain). The legacy shared ~/bin/ copy, if any, is left alone — -# it may be a user's manual install. -INSTALL_DIR="$PLUGIN_ROOT/.bin-cache" +# Plugin-scoped install dir (#117): the binary lives at the PLUGIN level — +# one dir ABOVE the version-scoped PLUGIN_ROOT (cache layout is +# ///). Cross-marketplace and cross-plugin +# same-name collisions stay impossible by construction (marketplace + plugin +# are both in the path), while the binary + sidecar PERSIST across shell-only +# version bumps — preserving the #116 sidecar short-circuit (verify DA-1: +# a version-scoped cache re-downloaded 14-26MB on every shell bump and +# multiplied disk per retained version dir). In a git-checkout dev context the +# parent is plugins/, where BINARY_NAME scoping keeps copies distinct. The +# legacy shared ~/bin/ copy, if any, is left alone — it may be a +# user's manual install. +INSTALL_DIR="$(dirname "$PLUGIN_ROOT")/.bin-cache" BINARY="$INSTALL_DIR/$BINARY_NAME" VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" diff --git a/plugins/che-pptx-mcp/CHANGELOG.md b/plugins/che-pptx-mcp/CHANGELOG.md index 676d812..830a784 100644 --- a/plugins/che-pptx-mcp/CHANGELOG.md +++ b/plugins/che-pptx-mcp/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed -- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 `$PLUGIN_ROOT/.bin-cache/` — 跨 marketplace 同名 plugin 碰撞 by construction 不可能;plugin 更新自然汰換 cache(重下載走完整驗證鏈)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 +- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 **plugin 層級**的 `.bin-cache/`(`//.bin-cache/`,即 version 目錄的上一層)— 跨 marketplace / 跨 plugin 碰撞 by construction 不可能,且跨 shell 版本持久(保留 #116 sidecar 短路,shell-only bump 不重下載;binary_version 變更才重下載)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 ## [0.1.1] - 2026-07-02 diff --git a/plugins/che-pptx-mcp/README.md b/plugins/che-pptx-mcp/README.md index 555d7d2..37d6719 100644 --- a/plugins/che-pptx-mcp/README.md +++ b/plugins/che-pptx-mcp/README.md @@ -9,7 +9,7 @@ claude plugin marketplace add PsychQuant/macdoc claude plugin install che-pptx-mcp@macdoc ``` -Wrapper 會自動從 [GitHub Releases](https://github.com/PsychQuant/che-pptx-mcp/releases) 下載 release 的 `ChePPTXMCP` universal binary 到 `~/bin/`,安裝前與每次啟動時強制驗證 sha256(安裝時)與 Developer ID Application 簽章鏈(Team `6W377FS7BS`)。release 流程含 Apple notarization(wrapper 不重複檢查 notarization)。 +Wrapper 會自動從 [GitHub Releases](https://github.com/PsychQuant/che-pptx-mcp/releases) 下載 release 的 `ChePPTXMCP` universal binary 到 plugin 層級的 `.bin-cache/`(跨 marketplace 隔離、跨版本持久),安裝前與每次啟動時強制驗證 sha256(安裝時)與 Developer ID Application 簽章鏈(Team `6W377FS7BS`)。release 流程含 Apple notarization(wrapper 不重複檢查 notarization)。 ## 原始碼 diff --git a/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh b/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh index 9723259..925b184 100755 --- a/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh +++ b/plugins/che-pptx-mcp/bin/che-pptx-mcp-wrapper.sh @@ -3,7 +3,7 @@ # # Design: # - Reads desired version from plugin.json (plugin's intended binary version) -# - Compares against ~/bin/.ChePPTXMCP.version sidecar +# - Compares against the plugin-level .bin-cache/.ChePPTXMCP.version sidecar # - Re-downloads when plugin has been updated but binary is stale # - Unique temp file (mktemp, same fs) + atomic mv so partial downloads never break things # - Pinned version does NOT fall back to releases/latest (supply-chain pinning); @@ -31,13 +31,18 @@ SCRIPT_ARGS=("$@") PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json" -# Plugin-scoped install dir (#117): every marketplace x plugin x version gets -# its own binary copy inside the plugin's own cache dir — cross-marketplace -# same-name collisions are impossible by construction, and a plugin update -# naturally recycles the cache (re-download passes the full verification -# chain). The legacy shared ~/bin/ copy, if any, is left alone — -# it may be a user's manual install. -INSTALL_DIR="$PLUGIN_ROOT/.bin-cache" +# Plugin-scoped install dir (#117): the binary lives at the PLUGIN level — +# one dir ABOVE the version-scoped PLUGIN_ROOT (cache layout is +# ///). Cross-marketplace and cross-plugin +# same-name collisions stay impossible by construction (marketplace + plugin +# are both in the path), while the binary + sidecar PERSIST across shell-only +# version bumps — preserving the #116 sidecar short-circuit (verify DA-1: +# a version-scoped cache re-downloaded 14-26MB on every shell bump and +# multiplied disk per retained version dir). In a git-checkout dev context the +# parent is plugins/, where BINARY_NAME scoping keeps copies distinct. The +# legacy shared ~/bin/ copy, if any, is left alone — it may be a +# user's manual install. +INSTALL_DIR="$(dirname "$PLUGIN_ROOT")/.bin-cache" BINARY="$INSTALL_DIR/$BINARY_NAME" VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version" diff --git a/plugins/che-word-mcp/CHANGELOG.md b/plugins/che-word-mcp/CHANGELOG.md index b283d33..6a157ec 100644 --- a/plugins/che-word-mcp/CHANGELOG.md +++ b/plugins/che-word-mcp/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 `$PLUGIN_ROOT/.bin-cache/` — 跨 marketplace 同名 plugin 碰撞 by construction 不可能;plugin 更新自然汰換 cache(重下載走完整驗證鏈)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 +- **Plugin-scoped 安裝目錄(PsychQuant/macdoc#117)**:binary 與 sidecar 從共享 `~/bin/` 遷至 **plugin 層級**的 `.bin-cache/`(`//.bin-cache/`,即 version 目錄的上一層)— 跨 marketplace / 跨 plugin 碰撞 by construction 不可能,且跨 shell 版本持久(保留 #116 sidecar 短路,shell-only bump 不重下載;binary_version 變更才重下載)。舊 `~/bin` 副本不主動刪除(可能為使用者手動安裝),首次啟動 stderr 註記一次。 ## [3.20.1] - 2026-07-02 diff --git a/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh b/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh index 3ec6aa6..b9d1868 100755 --- a/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh +++ b/plugins/che-word-mcp/bin/che-word-mcp-wrapper.sh @@ -3,7 +3,7 @@ # # Design: # - Reads desired version from plugin.json (plugin's intended binary version) -# - Compares against ~/bin/.CheWordMCP.version sidecar +# - Compares against the plugin-level .bin-cache/.CheWordMCP.version sidecar # - Re-downloads when plugin has been updated but binary is stale # - Unique temp file (mktemp, same fs) + atomic mv so partial downloads never break things # - Pinned version does NOT fall back to releases/latest (supply-chain pinning); @@ -31,13 +31,18 @@ SCRIPT_ARGS=("$@") PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PLUGIN_JSON="$PLUGIN_ROOT/.claude-plugin/plugin.json" -# Plugin-scoped install dir (#117): every marketplace x plugin x version gets -# its own binary copy inside the plugin's own cache dir — cross-marketplace -# same-name collisions are impossible by construction, and a plugin update -# naturally recycles the cache (re-download passes the full verification -# chain). The legacy shared ~/bin/ copy, if any, is left alone — -# it may be a user's manual install. -INSTALL_DIR="$PLUGIN_ROOT/.bin-cache" +# Plugin-scoped install dir (#117): the binary lives at the PLUGIN level — +# one dir ABOVE the version-scoped PLUGIN_ROOT (cache layout is +# ///). Cross-marketplace and cross-plugin +# same-name collisions stay impossible by construction (marketplace + plugin +# are both in the path), while the binary + sidecar PERSIST across shell-only +# version bumps — preserving the #116 sidecar short-circuit (verify DA-1: +# a version-scoped cache re-downloaded 14-26MB on every shell bump and +# multiplied disk per retained version dir). In a git-checkout dev context the +# parent is plugins/, where BINARY_NAME scoping keeps copies distinct. The +# legacy shared ~/bin/ copy, if any, is left alone — it may be a +# user's manual install. +INSTALL_DIR="$(dirname "$PLUGIN_ROOT")/.bin-cache" BINARY="$INSTALL_DIR/$BINARY_NAME" VERSION_FILE="$INSTALL_DIR/.${BINARY_NAME}.version"