Skip to content

fix: generate one path-scoped Language Policy so the Constitution and entry config agree - #91

Merged
benwu95 merged 5 commits into
mainfrom
benwu95/fix/align-language-policy-scope
Jul 25, 2026
Merged

fix: generate one path-scoped Language Policy so the Constitution and entry config agree#91
benwu95 merged 5 commits into
mainfrom
benwu95/fix/align-language-policy-scope

Conversation

@benwu95

@benwu95 benwu95 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

背景

prospec init 在同一次執行裡寫出兩份互相矛盾的 [MUST] Language Policy:languagePolicyRule() seed 進 CONSTITUTION.md 的措辭要求「change artifacts and AI Knowledge」都用專案語言,而同一次 agent syncentry.md.hbs 產生的 CLAUDE.md/AGENTS.md 卻宣告 Knowledge base「always remain in English — exempt」。

因為 /prospec-verify 只稽核 Constitution 且 MUST→FAIL,非英文專案照 L0 的 CLAUDE.md 寫英文 Knowledge,第一次 verify 就吃 MUST 違反;照 Constitution 寫母語,則每個 session 載入的 L0 都在反對該產出。沒有任何檔案宣告兩者優先順序。

issue #67 的 US-360/REQ-TEMPLATES-141 已決定豁免並要求「三方對齊」,但只落在 prospec 自身的 CONSTITUTION.mdentry.md.hbs漏了產生器 —— 所以每個下游 prospec init 專案至今仍拿到互相矛盾的兩份文件。

做法

把語言範圍變成資料:新增 src/lib/language-policy.tsresolveLanguageScope(),由 paths.base_dir / knowledge.base_path 推導母語集、英文集與具名例外集;languagePolicyRule(scope) 與 entry config 的 entryLanguageContext(scope) 都從這一份渲染。散文措辭兩邊仍不同(一份是 [MUST] 規則、一份是 agent 指令),但路徑集合同源,並由 contract test 釘死兩份產出的路徑集字面相等。

  • 條文改為路徑式,verify 稽核可按檔案位置判定:母語適用 .prospec/changes/**.prospec/archive/**specs/_archived-history/**;英文適用 CONSTITUTION.mdREADME.mdindex.mdspecs/product.mdspecs/features/**、knowledge base
  • 四項具名例外(alias/keyword 資料、ledger description+status 欄、_playbook.md 逐字引用證據、user-managed _glossary.md)只寫在 Constitution,不進 L0
  • prospec upgrade 對帶舊措辭的既有專案報告 stale 訊號,並附上以該專案路徑渲染好的替代條文;指令本身仍不修改 CONSTITUTION.md,改寫由 /prospec-upgrade 新增的 Step 2.5 徵詢同意後執行
  • 共用 partial _language-policy.hbs 改為「按文件路徑指派語言」—— 原本「所有產出文件同一語言」的指示,正是 feature spec 曾漂移成繁中、要靠一整個 change 翻回英文的成因

Review 揪出的三個關鍵缺陷

六個獨立 lens 平行審查 + 每個 critical 派獨立 verifier 重現,兩輪共 8 critical 全修:

  1. init 的 entry config 從沒被修好 —— entry.md.hbs 有兩個 render site,第一輪只接了 agent sync。更糟的是 init 只寫 AGENTS.md,而 agent sync 只重寫「已設定 agent 的 configPath」,所以 claude-only 專案那份空括號檔案會永久留著
  2. 遷移步驟本來是死路 —— Step 2.5 原本叫 agent 用 print-template init/constitution.md.hbs 取條文,但該範本只迴圈輸出注入的規則、本身沒有條文文字,且 print-template 回傳未渲染原始碼
  3. 守核心不變式的測試是假綠 —— find(...)!not.toContain(undefined) 恆過(把知識庫從英文區整段刪掉仍綠);只比對字面 glob 則讓「以 prose 重述同一矛盾」溜過

另有 round 2 揪出的新 critical:英文短路原按當前 config 語言判斷,於是「先以日文 init、後改 artifact_language: English」的專案訊號被吞;改為按 seed 自身語言判斷。

驗證

  • 乾淨 scratch 專案實測:init 單獨執行的 entry config 已帶完整路徑集;init + agent sync 兩份文件路徑集字面一致;英文專案取單一區塊分支
  • 舊措辭專案實測:prospec upgrade 印出訊號 + 完整替代條文,CONSTITUTION.md 未被修改;CONSTITUTION.md 為目錄(EISDIR)時仍 exit 0 並印出完整報告
  • 2191 tests / 94 檔全綠、typecheck / lint 0、counts in sync;4 項 mutation 實證守門測試會轉紅
  • verify grade A、prospec check 0 fail

13 條 REQ 已畢業至 project-setup、agent-integration、ai-knowledge、feedback-promotion 四份 feature spec。

benwu95 added 4 commits July 25, 2026 15:57
- add lib/language-policy: resolveLanguageScope derives the native/English
  path sets and named exceptions from paths.base_dir + knowledge.base_path
- languagePolicyRule now takes a LanguageScope and states the rule by path,
  so a verify audit decides by file location instead of re-reading what
  "AI-generated documents" covers; an English project gets a condensed form
- entry.md.hbs renders the same injected scope instead of a hand-written path
  list, so CONSTITUTION.md and CLAUDE.md/AGENTS.md cannot declare conflicting
  scopes (the #67 alignment reached this repo's files but not the generator)
- upgrade reports staleLanguagePolicy for a project still carrying the old
  seeded wording; the command still never edits CONSTITUTION.md
- prospec-upgrade gains Step 2.5: diff the Language Policy section and rewrite
  it only on consent — the one seeded wording the skill may migrate
- promotion-format declares the ledger description column as an in-zone
  exception, so downstream ledgers inherit it
- pin the scope with unit + cross-document contract assertions
  (mutation-verified); the previous tests asserted no scope at all
- README (both languages) scope artifact_language to change artifacts and
  their archived summaries, and name the English trust zone; the two files
  previously disagreed ("AI-generated documents" vs change specs/tasks)
- CONSTITUTION.md drops the same-sentence clash between "archived summaries
  in Traditional Chinese" and "specs in English": _archived-history follows
  the change narrative, specs/features stays English
- record the named in-zone exceptions (alias/keyword data, ledger
  description, playbook evidence quotes, user-managed glossary)
- sync factual test counts after the new suites
- entryLanguageContext feeds both entry-config render sites, so `prospec init`
  alone no longer writes empty path lists beside a fully-scoped Constitution
- resolve scope paths with path.posix.join and a '.' fallback, so a repo-root
  base_dir stops emitting root-anchored paths
- scope specs/product.md to English; it belonged to neither zone
- upgrade reports the stale seed with the rendered replacement rule, since no
  template carries the rule text; the English gate now keys off the seed's own
  language so a switched-language project still migrates
- prospec-upgrade Step 2.5 reads that block instead of an impossible
  print-template retrieval; the shared language-policy partial goes path-scoped
- pin the invariant with cross-document contract tests, a bundle-sync guard and
  heading-boundary cases; four mutations verified they bite
- sync module Knowledge for lib/types/services/templates/tests
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

Prospec Check

Prospec drift check (structural — deterministic, zero LLM)

  PASS  req-references
  PASS  file-paths
  PASS  import-direction
  WARN  knowledge-health
  SKIP  task-completion — source unavailable: .prospec/changes/ not found (not version-controlled)
  PASS  dangling-prefix
  PASS  feature-modules
  PASS  mcp-readme-counts
  SKIP  review-provenance — source unavailable: .prospec/changes/ not found (not version-controlled)
  SKIP  metadata-completeness — source unavailable: .prospec/changes/ not found (not version-controlled)
  WARN  knowledge-size

Findings:
  [warn] prospec/ai-knowledge/modules/lib/README.md
         stale knowledge: module "lib" source last commit 2026-07-25T18:13:11+08:00 is newer than README last commit 2026-07-25T17:34:02+08:00
  [warn] prospec/ai-knowledge/modules/templates/README.md
         stale knowledge: module "templates" source last commit 2026-07-25T18:13:11+08:00 is newer than README last commit 2026-07-25T17:34:02+08:00
  [warn] prospec/ai-knowledge/modules/tests/README.md
         stale knowledge: module "tests" source last commit 2026-07-25T18:13:11+08:00 is newer than README last commit 2026-07-25T17:34:02+08:00
  [warn] prospec/ai-knowledge/modules/lib/README.md
         L2 README over token budget: 1046 tokens (chars-per-token:4) > 1000 per-module budget
  [warn] prospec/ai-knowledge/modules/services/README.md
         L2 README over token budget: 1019 tokens (chars-per-token:4) > 1000 per-module budget
  [warn] prospec/ai-knowledge/modules/templates/README.md
         L2 README over token budget: 1045 tokens (chars-per-token:4) > 1000 per-module budget
  [warn] prospec/ai-knowledge/modules/types/README.md
         L2 README over token budget: 1007 tokens (chars-per-token:4) > 1000 per-module budget

Knowledge coverage: 6/6 modules documented, 3 stale

Checked 8/11 checks: 0 fail, 2 warn, 3 skipped (not counted as pass)
Semantic consistency: not-checked (run /prospec-review)
Report written: prospec-report.json

@github-actions

Copy link
Copy Markdown

Test Coverage

Metric Coverage Covered / Total
lines 96.29% 3245 / 3370
statements 95.78% 3567 / 3724
functions 95.83% 598 / 624
branches 93.24% 1974 / 2117

- move approval/scoring/retirement provenance out of `status` into
  `description` as a `| **Promotion**:` / `| **Retired**:` suffix, verbatim
  (10 rows; every chunk verified preserved)
- document `retired` in promotion-format's status set — the data used it three
  times, and mapping it onto `promoted`/`declined` would have misstated state
- state in promotion-format that provenance belongs in `description`, never
  appended to `status`, so the closed set every consumer reads stays closed
- narrow the Language Policy's ledger exception back to `description` only, in
  the generator, the Constitution, the ledger header and the graduated specs —
  with status now ASCII, claiming an exception for it would overstate
@benwu95

benwu95 commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

補一個 follow-up commit(56d0098),處理 review 升呈的那項裁定:

_lessons-ledger.mdstatus 欄同時違反 promotion-format 的封閉 enum(retired 非合法值、且附了繁中 provenance prose)。這是既有資料問題,但本 PR 新寫的例外清單原本把 status 一併豁免,等於替破口背書。改成:

  • 10 列的 approval/scoring/retirement provenance 原文搬進 description,以 | **Promotion**: / | **Retired**: 後綴呈現;已逐 chunk 驗證無內容遺失
  • retired 正式寫入 promotion-format 的 status 集合(資料已用了三次;硬映射成 promoteddeclined 會失真)
  • promotion-format 明文規定 provenance 屬 description,不得追加到 status
  • Language Policy 的 ledger 例外收回到僅 description —— status 現為純 ASCII enum,再宣稱豁免就是過度宣稱(產生器/Constitution/ledger header/已畢業的 feature spec 同步)

status 欄現為 33 列全數純 token(personal 23、promoted 6、retired 3、declined 1),2191 tests / typecheck / lint / counts 全綠,prospec check 0 fail。

@benwu95
benwu95 merged commit a00811e into main Jul 25, 2026
4 checks passed
@benwu95
benwu95 deleted the benwu95/fix/align-language-policy-scope branch July 25, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant