Skip to content

模板總整修:技能與腳本重構、多來源章節切分、安全強化、中文搜尋層 - #6

Merged
wayne930242 merged 40 commits into
wayne930242:mainfrom
tool-jx3:feat/chinese-search
Aug 15, 2026
Merged

模板總整修:技能與腳本重構、多來源章節切分、安全強化、中文搜尋層#6
wayne930242 merged 40 commits into
wayne930242:mainfrom
tool-jx3:feat/chinese-search

Conversation

@tool-jx3

Copy link
Copy Markdown
Contributor

摘要

本 PR 合併兩條工作線:fix/project-review 的模板總整修(技能整併、腳本測試化、
範本重置、安全強化),以及在其上的 feat/chinese-search 中文搜尋強化層。

主要變更

中文搜尋強化層(docs/search/,新增)

Pagefind 內建 jieba 為簡體詞庫,正體內容退化為逐字切分(來源專案實測:術語成詞率僅 9%,
高雜訊查詢命中全站 94% 頁面)。改以 forceLanguage: 'en' 重建索引,由 glossary 最長匹配

  • Intl.Segmenter 自行斷詞,建置後注入隱藏鏡像,查詢端以 shim 包裝斷詞、子字串展開與
    摘要清理。來源專案實測成詞率 9% → 90%,索引縮小 0.5 MB。

模板化調整(相對 stormlight 原版):

  • 零術語降級:glossary 合法但尚無已核准術語(新專案初始狀態)時警告並退回純 ICU 斷詞,
    不再讓建置失敗;檔案缺失或 JSON 損壞維持失敗
  • 僅收 status: "approved" 詞條,斷詞字典與正文用詞一致(Law 7)
  • 驗收查詢集設定化:語料相關門檻移入 docs/search/verify-cases.json(附範例檔),
    健康句檢查於執行期自動剔除與 glossary 重疊的術語
  • /final-proofread 由三關改四關,Gate 4 執行建置後的 verify-search
  • 架構與查詢集撰寫指引見 docs/search/README.md

專案總整修

  • 範本重置為空白狀態:clean_sample_data 全面重置、空 sidebar、placeholder 首頁;
    new-project clone 後自動執行;移除維護者工作文件與已完成的 openspec change
  • 技能整併:移除 optimized-translatingpdf-translation(設計規格取代);模型路由
    寫進技能派遣步驟;review 迭代上限統一為 2;新增 md-review 技能與
    docs-conventions/python rules;移除漂移的 .agents/ 副本、補齊 .gemini/commands
  • hooks 修正:session-start 改用 uv、pytest hook 限定 scripts/、移除壞掉的
    permission-check hook;Python 版本由 .python-version 追蹤
  • 新增 opendataloader-pdf 抽取引擎(預設,含自動 fallback);win32 subprocess UTF-8 修正

多來源章節切分

  • split_chapters() 重構為多來源遞迴結構,新增 merge_multi.py 合併各 PDF 章節設定
  • 整合 starlight-auto-sidebar(write_meta_yml()),保留巢狀 sidebar 群組
  • 進度追蹤加入 source 欄位,progress_read.py --source 過濾

安全強化(預覽站 site-auth)

  • 認證 cookie 改為 HMAC 簽章 + 到期時間,驗證邏輯抽至 lib/site-auth-shared.ts 共用
  • 修補 open-redirect:sanitizeRedirect 先剝除 C0 控制字元(/\t/evil.com 類 payload
    原可繞過前綴檢查);成功/失敗兩條 redirect 分支各加獨立 origin 複驗

測試與 CI

  • 根目錄 tests/ 遷移為 scripts/tests/(16 個測試檔),移除過時重複
  • CI workflow 執行 pytest 與 style validation
  • 搜尋層以 node:test 撰寫 63 個單元測試(TDD)

主題

  • 介面層級與灰階色系改為相對 --bg-h--bg-l,移除純色背景下多餘遮罩
  • style_decisions.py 新增 set-theme 子命令,與 init-doc 預設對齊

驗證

  • scripts/tests/ pytest 全數通過(CI 同步執行)
  • docs/bun run test 63 個測試全綠
  • 空 glossary 狀態實跑 bun run build:零術語降級警告正確、404.html 正確跳過、索引重建完成
  • bun run verify-search:通用檢查通過(斷詞健康 11 個多字詞),exit 0

注意事項

  • 衍生專案(如 stormlight)已各自帶有搜尋層,不受本 PR 影響;新專案自模板建立後即內建
  • 各專案翻譯完成後需依 docs/search/README.md 建立自己的 verify-cases.json
  • 選配未做:子字串展開候選仍取 vocab 迭代順序前 12 個(無排序),留待有實際需求再處理

tool-jx3 and others added 30 commits April 11, 2026 17:10
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Remove the PostToolUse permission-check hook registration and script: it
was wired to the wrong event (PostToolUse cannot block anything), had an
infinite-loop bug on Windows drive roots, and duplicates Claude Code's
built-in permission system. Raise pytest hook timeout to 60s. Commit the
pending .python-version fix (3.10.12 violated requires-python >=3.11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…wed status

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Add reset_chapters, reset_style_decisions, remove_progress_files,
reset_astro_config, write_placeholder_index, and remove_plans_dir so
clean_sample_data.py fully resets a project back to template state
(chapters.json, style-decisions.json, translation-progress*.json,
docs/astro.config.mjs, docs index.mdx, plans/). main() now runs these
after clean_glossary in order. Also narrow clean_glossary's except
Exception to except (json.JSONDecodeError, OSError).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…laceholder index)

- Run scripts/clean_sample_data.py to strip Cairn/Year Zero Engine sample
  content: blank chapters.json/glossary.json/style-decisions.json, remove
  translation-progress.json, reset docs/astro.config.mjs title+sidebar,
  write placeholder docs/src/content/docs/index.mdx.
- Manually finish the docs reset: drop dead og:image/twitter:image meta
  tags from astro.config.mjs head, delete unused custom-light.css and the
  fake hero.jpg, drop the unused @astrojs/vercel dependency (no adapter
  configured) and refresh docs/bun.lock, drop the /fonts headers block
  from vercel.json (no such directory), and rewrite docs/README.md for
  the template pipeline.
- Add data/pdfs/.gitkeep and data/markdown/.gitkeep placeholders.
- Fix a real idempotence bug found while re-verifying the clean script:
  reset_astro_config's sidebar regex, when run a second time against an
  already-empty `sidebar: [],`, had no newline to anchor on and
  over-consumed past plugins/customCss/closing brackets, corrupting the
  file. Guard against the already-empty case and add a regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…om template

Deletes internal agent-system analysis, plans, superpowers specs, and the
completed-but-archived openspec opendataloader-engine change. These artifacts
were for project development and are not part of the template distribution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Add mandatory cleanup step to new-project/SKILL.md right after
repository creation, and sync README.md / scripts/README.md docs
with the script's full reset/delete behavior list so child projects
never inherit sample data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…perseded per design spec)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…fs to rules

- Remove disable-model-invocation from md-review, term-decision, check-consistency
  (these skills are invoked by other skills' workflows, so model must be able to call them)
- Add disable-model-invocation to final-proofread
  (entry-point skill kept restricted for consistency)
- Replace all AGENTS.md references with .claude/rules/docs-conventions.md
  across md-review, md-review/reviewer-prompt, and super-translate skills

Verification:
- git grep "AGENTS.md" -- .claude/skills: no output ✓
- git grep -l "disable-model-invocation" -- .claude/skills: 7 files ✓

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…g rule, unify iteration cap at 2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…k to md-review

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Replace English placeholder with brief-mandated Traditional Chinese text:
「刪節號必須使用 \`……\`,不得使用 \`...\`」

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…ft paths

Resolve translation_mode.mode before Step 1 preconditions so the
progress file, scope, and progress_read.py invocation are mode-aware
(bilingual vs. default), and route the Gate 2 broken-link check
through the resolved content root. Move chapter-split's topology
draft path under .state/chapter-split/ to match chapters.draft.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Review finding: the Gate 1a homepage path was a bare literal
indistinguishable from a missed bilingual-mode conversion. Annotate
it explicitly as the fixed, mode-independent site root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
Delete .agents/ (physical copy of .claude/skills that diverged; .codex/
and .gemini/skills remain as symlinks and cannot drift). Add the four
missing .gemini/commands TOMLs (md-review, bilingual-translate, fix-ref,
final-proofread) mirroring the existing translate.toml format.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…s, and engines

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…scripts README

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
- init_handoff_gate.run_cmd now decodes child stdout/stderr as utf-8
  instead of the cp950 default on win32, and check_bun_available()
  turns a missing `bun` into a clear gate failure entry instead of a
  FileNotFoundError traceback.
- _ocr_lib.py (tesseract --list-langs, tesseract OCR run) and
  _opendataloader_lib.py (java -version) subprocess.run calls also
  pin encoding="utf-8" so Traditional Chinese CLI output doesn't
  crash or mojibake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…resolve() roots, opendataloader engine choice

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…split_pages_content

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…ts, drop stale duplicates

根目錄 tests/(4 檔、~63 測、3 月起未被 pytest testpaths 收集)已整批處理:

- PORT 為 pytest 風格(tmp_path/monkeypatch/plain assert,移除 sys.path 自插):
  draft、style_decisions/validate_style_decisions、term_generate/term_edit/
  term_read、_term_lib、validate_glossary、init_create_progress、
  bilingual_prep、clean_sample_data 舊三函式、init_handoff_gate 三測
- 三個 bilingual 測試檔改寫為直接函式呼叫,消除硬編 macOS cwd 與
  無 encoding 的 subprocess/read_text
- DISCARD:TestExtractPdf 與 TestSplitChapters 中已由 test_layout_lib、
  test_epub_lib、test_image_analysis、test_split_chapters 覆蓋的重複測試
- 補回兩處實際未被覆蓋的邏輯:extract_pdf 的
  load_document_extraction_settings/resolve_page_text_strategy,
  split_chapters 的 group_images_by_page
- term_edit.mutate_term 的 auto-cal 行為已演進(自動跑 cal 後照常套用),
  測試改寫對齊現行行為,未動生產程式碼

280 -> 341 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…t, shared verification logic

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
…ion + origin re-validation

sanitizeRedirect now strips ASCII C0 control chars (tab/CR/LF/etc.) before
the prefix checks, since new URL() silently drops them and a payload like
"/\t/evil.com" previously passed the raw-string checks but resolved
cross-origin. api/site-auth.ts adds a second, independent origin check on
both the success and error redirect branches as defense in depth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
tool-jx3 and others added 9 commits July 26, 2026 16:25
C1 generate_nav.generate_index() unconditionally emitted a hero
`image.file: ../../assets/hero.jpg` block, but the blank-template reset
deletes that asset. Starlight binds hero.image.file to Astro's image()
helper, so `bun run build` died with [ImageNotFound]. The block is now
emitted only when HERO_IMAGE exists on disk.

C2 update_astro_sidebar's pattern required a newline after `sidebar: [`,
so it never matched the reset config's collapsed `sidebar: [],` — it
printed a warning, returned the input unchanged and still exited 0,
shipping a site with no navigation. The pattern now matches both the
collapsed and populated forms while anchoring the closing `],` to its own
line (so it cannot over-consume plugins/customCss/the starlight() close),
and a no-match now raises SidebarPatchError -> exit 1 instead of failing
silently.

I2 md-review SKILL.md still demanded `sidebar.order` on pages, which
contradicts docs-conventions.md (its own stated source) after this branch
moved ordering to _meta.yml.

I3 clean_sample_data.clean_docs_content filtered on .md/.mdx only, so
_meta.yml files survived the "complete reset", kept section dirs
non-empty and left starlight-auto-sidebar loading stale metadata.

M4 session-start.sh was missing progress_read.py and draft.py from the
script list required by design 3.2.

M5 add the missing .gemini/commands/terminology-management.toml so the
Gemini command set (13) matches .claude/skills/.

I1 document the social-crawler auth bypass in README as an accepted risk.
middleware.ts is deliberately unchanged: the OG-preview tradeoff is the
user's call, but the password gate must not be sold as a security
boundary.

Tests: 341 -> 349 (hero-image presence/absence, sidebar patching across
both config shapes plus the shipped config, loud-failure case, and
_meta.yml removal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US3TEe1Pu3mFHCjjwzq8Fg
模板預設 body 的 background-image 是註解掉的(純色背景),
但 --overlay-opacity 仍為 0.7,body::before 與 .main-pane 兩層遮罩
只是把 6% 亮度的背景再壓成近乎純黑,沒有作用對象。

- --overlay-opacity 0.7 -> 0,並註明僅在放入 /public/bg.jpg 後才調回
- 新增 --bg-chrome(+3%)、--bg-panel(+6%)、--bg-raised(+14%)
  三個相對 --bg-l 的層級變數
- 導覽列/側欄、分隔線、行內程式碼底、搜尋 UI、hero 漸層、
  LinkCard hover 原本寫死 8%/12%/20%/25%,--bg-l 一旦調高就會
  比背景暗而層級反轉,全部改為相對推導
- header.header 與 .sidebar 各重複宣告兩次且同為 !important,
  後者勝出導致遮罩設 0 時導覽列全透明;刪除「背景設定」區的重複
  規則,統一由「導航列」「側邊欄」兩節設定

--bg-l 維持 6% 時各層級算出 9%/12%/20%,與原本寫死值一致,
預設外觀不變;唯一差異為導覽列由 ~5.3%(比內文底暗)改為 9%。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjAPKNvmSWFd3tdJJ8xixx
灰階原本全部鎖在 hue 224 與絕對亮度,但檔頭宣稱「只需修改 H 值即可
換色」。gray-5/gray-6 又被 Starlight 內部引用約 48 處(表格框線、引言
左框線、摺疊區塊邊框、Header 分隔線、Pagination、行動選單、TOC 上框線、
expressive-code 邊框),所以先前把模板自身用法相對化後,這些內建元件
仍停在寫死的值。

- gray-1~4(文字色)色相改為 var(--bg-h),飽和度由 20/10/8/10% 降為
  12/8/6/8%,避免換色相後內文被染色
- gray-5 / gray-6(表面色)改為沿用 --bg-raised / --bg-panel
- 補上註解:層級採加法模型,僅適用深色模式

實測(astro build + astro preview,Playwright 讀取計算值):
  --bg-h 330 時表格框線由 rgb(44,48,58) 藍灰轉為 rgb(64,38,51) 粉,
  與底色色相一致;--bg-l 16% 時框線/底色對比由 1.17(幾乎沒入背景)
  回到 1.60。預設值下對比 1.46 -> 1.41,亮度相同、飽和度略增,
  外觀無實質變化。三種情境下各層級皆亮於底色,無反轉。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjAPKNvmSWFd3tdJJ8xixx
…faults

theme 區塊先前只能靠 merge-json 手寫,且 init-doc 仍會把 overlay 問成
0.7 —— 那會把剛改成相對層級的預設值又蓋回純色背景下多餘的遮罩。

- style_decisions.py 新增 set-theme,欄位為 mode / overlay / palette /
  bg-h / bg-l,對應 custom.css 目前實際暴露的旋鈕
- 新增 parse_overlay(0-1)、parse_hue(0-360)、parse_lightness(0-100,
  正規化為百分比字串,可填 6 或 6%)三個驗證器
- schema theme 區塊補上 bg_h / bg_l(optional string,向後相容)
- init-doc SKILL.md Step 6:改為詢問 --bg-h / --bg-l 兩個旋鈕,明確要求
  overlay 維持 0 除非真的放了 docs/public/bg.jpg,並補上 set-theme 指令

測試:新增 10 個案例涵蓋成功寫入、多次合併不覆蓋既有欄位、缺欄位報錯、
三個驗證器的邊界與非法值。全套 365 passed,schema 驗證通過。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjAPKNvmSWFd3tdJJ8xixx
Step 6 的編號從 7 直接跳到 9,第 8 項不存在。將 9-12 重新編為 8-11,
使該節維持連續的 1-11。其他 Step 的編號經檢查皆無跳號。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjAPKNvmSWFd3tdJJ8xixx
原樣搬移 docs/search/(斷詞、展開、摘要清理、HTML 鏡像改寫、
shim、驗收腳本)與 package.json 的 build/test/verify-search 腳本。
Pagefind 內建 jieba 為簡體詞庫,正體內容退化為逐字切分;改以
forceLanguage:en 重建索引,由 glossary 最長匹配 + Intl.Segmenter
自行斷詞。模板情境的調整(零術語降級、驗收設定化)於後續 commit。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179yzJfRj89SWDjSwydoAAu
模板新專案的 glossary 只有 _meta,原版在此直接讓建置失敗;
改為合法檔案零術語時警告並退回純 Intl.Segmenter 斷詞(仍優於
簡體 jieba),檔案缺失或 JSON 損壞維持失敗。同時只納入
status=approved 的詞條,讓斷詞字典與正文用詞一致(Law 7)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179yzJfRj89SWDjSwydoAAu
stormlight 版 verify.mjs 的查詢集、展開詞與 ICU 片語都綁定該書
語料,健康句更依賴人工核對 1277 個術語確認無重疊。模板化改為:
語料查詢集移入 docs/search/verify-cases.json(附範例檔),設定
缺失時僅跑通用檢查並提示;健康句於執行期以 healthTerms 剔除
與 glossary 重疊的詞條;成詞率在零術語時略過;術語抽取改用
load-terms 共用模組,消除與建置端的重複實作。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179yzJfRj89SWDjSwydoAAu
docs/search/README.md 收錄語料中立版的設計說明與 verify-cases
撰寫指引;final-proofread 由三關改四關,最後一關跑建置後的
verify-search;CLAUDE.md 補上搜尋層的技術棧與路徑;.gitignore
忽略 .superpowers/ 工作區。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179yzJfRj89SWDjSwydoAAu
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Correctness fixes:
- middleware.ts: replace unanchored string-splice error-param removal
  with URLSearchParams.delete(), which was corrupting redirect targets
- generate_nav.py: normalize flat slash-path keys before nav/sidebar
  generation so it matches the tree split_chapters.py actually writes
- split_chapters.py: normalize_files() now raises on flat-leaf/nested
  key collisions instead of crashing with KeyError or silently losing data
- merge_multi.py: merge_configs() no longer crashes on a missing optional
  "order" field; missing required fields now raise a clear error
- init_create_progress.py: malformed chapter entries now raise instead of
  being silently dropped from translation-progress.json
- docs/search/client/shim.mjs: fix a race in ensureBasePath() where
  concurrent search()/preload() calls could bypass basePath application
- progress_read.py: --source filter now recomputes the total count
  instead of reporting against the unfiltered chapter count
- api/site-auth.ts: wrap formData() parsing in try/catch to keep the old
  graceful-redirect behavior on unexpected content types
- clean_sample_data.py: broaden except clauses to also catch
  UnicodeDecodeError; reuse generate_nav's line-anchored SIDEBAR_PATTERN
  to avoid over-consuming past the sidebar array's own closing bracket
- .claude/hooks/pytest-check.py: raise the pytest timeout from 45s to 90s
  (measured cold-cache runs leave too little margin)

Cleanup:
- lib/site-auth-shared.ts: extract pinToOrigin() to remove the duplicated
  same-origin redirect check in api/site-auth.ts
- scripts/_markdown_utils.py: add a shared iter_leaves() walker, used by
  init_create_progress.py where its stricter "must have pages" contract
  matches split_chapters.py's; generate_nav.py's own leaf walkers keep
  their existing, deliberately more lenient contract
- merge_multi.py: add missing type hints

Also fixes a uv deprecation warning by moving dev-dependencies from
[tool.uv] to the standard [dependency-groups] table in pyproject.toml.
@wayne930242
wayne930242 merged commit 7f4d6e0 into wayne930242:main Aug 15, 2026
2 checks passed
@wayne930242

Copy link
Copy Markdown
Owner

辛苦了,這個 PR 內容很扎實!審查後發現 15 個問題,已經全部處理並合併,這邊做個總結給你參考。

已修正的正確性問題

  • middleware.ts:移除 ?error=1 時用了不加限定的字串取代,會吃掉查詢字串的 & 分隔符,或誤改到其他參數的值。改用 URLSearchParams.delete()
  • generate_nav.py:讀 chapters.json 時沒有呼叫 split_chapters.normalize_files(),扁平斜線路徑(如 combat/actions)沒展開,導致首頁連結/側邊欄跟 split_chapters.py 實際寫出的文件樹對不齊。
  • split_chapters.pynormalize_files() 遇到同一個 key 同時是扁平葉節點又是斜線巢狀前綴(例如 "combat""combat/actions" 並存)時,依處理順序會 KeyError 或靜默蓋掉資料,改成明確拋出 ValueError
  • merge_multi.pymerge_configs()order 用了嚴格索引,但 validate_merge()order 當作可選欄位,兩邊不一致會讓合法設定檔(缺 order)直接 crash。改成 order 真的可選,缺少必填欄位時給清楚的錯誤訊息。
  • init_create_progress.py:章節條目格式錯誤(pages/files 都沒有)會被靜默跳過,不會出現在 translation-progress.json 裡也沒有警告。改成拋出例外,行為對齊 split_chapters.py
  • docs/search/client/shim.mjsensureBasePath()core.options() resolve 前就先同步把旗標設成 truthy,search()preload() 並發呼叫時,後到的呼叫可能提早放行、繞過 basePath 套用。改成所有並發呼叫共享同一個 in-flight promise。
  • progress_read.py:加了 --source 篩選後,分母仍然用未篩選前的 total_chapters,篩選子集查進度時完成率會失真。
  • api/site-auth.tsrequest.formData() 沒包 try/catch,非表單內容類型的請求會直接 500,而不是回到原本優雅的錯誤導向頁。
  • clean_sample_data.pyexcept (JSONDecodeError, OSError) 漏抓 UnicodeDecodeError;另外側邊欄清空的正則沒有錨定行首,理論上可能吃過頭到後面的 plugins/customCss,改成重用 generate_nav.py 裡更安全的 SIDEBAR_PATTERN
  • .claude/hooks/pytest-check.py:45 秒逾時在 cold cache 情境下量測只有約 13% 安全邊際,拉高到 90 秒。

順手做的簡化

  • 新增 lib/site-auth-shared.tspinToOrigin(),收斂 api/site-auth.ts 裡重複兩份的「導向網址釘回同源」邏輯。
  • _markdown_utils.py 新增共用的 iter_leaves() walker,套用在 init_create_progress.py(它跟 split_chapters.py 一樣要求葉節點必須有 pages)。generate_nav.py 自己的三個 walker 沒有跟進套用——它們原本的規則是「沒有 files 就當葉節點」,不要求 pages,跟前面兩個檔案的規則不同,強行統一等於改變行為,所以保留原樣。
  • merge_multi.py 補上缺少的型別註記。

沒動的部分

  • 12 個 .gemini/commands/*.toml 都重複轉述了 SKILL.md/CLAUDE.md 已有的流程規則(例如 commit 訊息格式),沒有一併處理——牽動檔案較多,建議另開 PR 處理。

另外中途發現 pyproject.toml 用了 [tool.uv] dev-dependencies 這個已棄用寫法,順手改成 [dependency-groups],消除了每次 uv sync 都會跳出的 warning。

所有修正都補了對應的 pytest 測試(含錯誤情境),372 個 Python 測試 + 63 個 JS 測試全數通過,CI(terminology、GitGuardian)也是綠燈,已 squash merge 進 main。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants