universal-dev-standards 5.15.1 → 5.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/bundled/ai/standards/acceptance-criteria-traceability.ai.yaml +31 -0
  2. package/bundled/ai/standards/forward-derivation-standards.ai.yaml +23 -0
  3. package/bundled/ai/standards/knowledge-graph-memory.ai.yaml +1 -1
  4. package/bundled/core/acceptance-criteria-traceability.md +46 -0
  5. package/bundled/core/forward-derivation-standards.md +19 -0
  6. package/bundled/core/knowledge-graph-memory.md +2 -2
  7. package/bundled/locales/zh-CN/CHANGELOG.md +13 -3
  8. package/bundled/locales/zh-CN/README.md +1 -1
  9. package/bundled/locales/zh-CN/core/acceptance-criteria-traceability.md +46 -0
  10. package/bundled/locales/zh-CN/core/forward-derivation-standards.md +19 -0
  11. package/bundled/locales/zh-CN/skills/ac-coverage/SKILL.md +194 -0
  12. package/bundled/locales/zh-CN/skills/adr-assistant/SKILL.md +135 -40
  13. package/bundled/locales/zh-CN/skills/brainstorm-assistant/SKILL.md +217 -63
  14. package/bundled/locales/zh-CN/skills/brainstorm-assistant/guide.md +599 -0
  15. package/bundled/locales/zh-CN/skills/commands/brainstorm.md +92 -25
  16. package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +78 -16
  17. package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +85 -26
  18. package/bundled/locales/zh-CN/skills/deploy-assistant/SKILL.md +189 -0
  19. package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +110 -0
  20. package/bundled/locales/zh-CN/skills/dev-methodology/guide.md +255 -0
  21. package/bundled/locales/zh-CN/skills/dev-workflow-guide/SKILL.md +70 -11
  22. package/bundled/locales/zh-CN/skills/journey-test-assistant/SKILL.md +209 -0
  23. package/bundled/locales/zh-CN/skills/knowledge-graph/SKILL.md +58 -0
  24. package/bundled/locales/zh-CN/skills/knowledge-graph/guide.md +74 -0
  25. package/bundled/locales/zh-CN/skills/migration-assistant/SKILL.md +125 -8
  26. package/bundled/locales/zh-CN/skills/observability-assistant/guide.md +188 -0
  27. package/bundled/locales/zh-CN/skills/orchestrate/SKILL.md +173 -0
  28. package/bundled/locales/zh-CN/skills/plan/SKILL.md +240 -0
  29. package/bundled/locales/zh-CN/skills/push/SKILL.md +242 -0
  30. package/bundled/locales/zh-CN/skills/retrospective-assistant/SKILL.md +104 -36
  31. package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +88 -32
  32. package/bundled/locales/zh-CN/skills/runbook-assistant/guide.md +216 -0
  33. package/bundled/locales/zh-CN/skills/skill-builder/SKILL.md +149 -0
  34. package/bundled/locales/zh-CN/skills/slo-assistant/guide.md +188 -0
  35. package/bundled/locales/zh-CN/skills/spec-derivation/SKILL.md +86 -0
  36. package/bundled/locales/zh-CN/skills/spec-derivation/guide.md +476 -0
  37. package/bundled/locales/zh-CN/skills/spec-driven-dev/SKILL.md +155 -81
  38. package/bundled/locales/zh-CN/skills/sweep/SKILL.md +151 -0
  39. package/bundled/locales/zh-CN/skills/testing-guide/SKILL.md +207 -110
  40. package/bundled/locales/zh-TW/CHANGELOG.md +13 -3
  41. package/bundled/locales/zh-TW/README.md +1 -1
  42. package/bundled/locales/zh-TW/core/acceptance-criteria-traceability.md +46 -0
  43. package/bundled/locales/zh-TW/core/browser-compatibility-standards.md +222 -5
  44. package/bundled/locales/zh-TW/core/contract-testing-standards.md +184 -5
  45. package/bundled/locales/zh-TW/core/cross-flow-regression.md +192 -5
  46. package/bundled/locales/zh-TW/core/forward-derivation-standards.md +19 -0
  47. package/bundled/locales/zh-TW/core/knowledge-graph-memory.md +2 -2
  48. package/bundled/locales/zh-TW/core/release-readiness-gate.md +186 -5
  49. package/bundled/locales/zh-TW/skills/adr-assistant/SKILL.md +21 -42
  50. package/bundled/locales/zh-TW/skills/brainstorm-assistant/SKILL.md +212 -59
  51. package/bundled/locales/zh-TW/skills/brainstorm-assistant/guide.md +266 -579
  52. package/bundled/locales/zh-TW/skills/commands/brainstorm.md +91 -26
  53. package/bundled/locales/zh-TW/skills/commit-standards/SKILL.md +77 -15
  54. package/bundled/locales/zh-TW/skills/contract-test-assistant/SKILL.md +75 -16
  55. package/bundled/locales/zh-TW/skills/dev-methodology/guide.md +255 -0
  56. package/bundled/locales/zh-TW/skills/dev-workflow-guide/SKILL.md +125 -64
  57. package/bundled/locales/zh-TW/skills/knowledge-graph/SKILL.md +5 -5
  58. package/bundled/locales/zh-TW/skills/knowledge-graph/guide.md +74 -0
  59. package/bundled/locales/zh-TW/skills/migration-assistant/SKILL.md +128 -11
  60. package/bundled/locales/zh-TW/skills/observability-assistant/guide.md +188 -0
  61. package/bundled/locales/zh-TW/skills/orchestrate/SKILL.md +3 -2
  62. package/bundled/locales/zh-TW/skills/plan/SKILL.md +3 -2
  63. package/bundled/locales/zh-TW/skills/push/SKILL.md +3 -2
  64. package/bundled/locales/zh-TW/skills/retrospective-assistant/SKILL.md +94 -28
  65. package/bundled/locales/zh-TW/skills/reverse-engineer/SKILL.md +84 -28
  66. package/bundled/locales/zh-TW/skills/runbook-assistant/guide.md +216 -0
  67. package/bundled/locales/zh-TW/skills/slo-assistant/guide.md +188 -0
  68. package/bundled/locales/zh-TW/skills/spec-derivation/guide.md +476 -0
  69. package/bundled/locales/zh-TW/skills/spec-driven-dev/SKILL.md +148 -77
  70. package/bundled/locales/zh-TW/skills/testing-guide/SKILL.md +141 -44
  71. package/bundled/skills/brainstorm-assistant/SKILL.md +142 -106
  72. package/bundled/skills/brainstorm-assistant/guide.md +256 -661
  73. package/bundled/skills/commands/brainstorm.md +51 -30
  74. package/bundled/skills/knowledge-graph/SKILL.md +5 -5
  75. package/bundled/skills/knowledge-graph/guide.md +4 -4
  76. package/package.json +2 -2
  77. package/src/commands/check.js +11 -2
  78. package/src/lint/i18n.js +109 -23
  79. package/standards-registry.json +4 -4
  80. package/bundled/locales/zh-TW/docs/SKILL-FALLBACK-GUIDE.md +0 -407
@@ -6,9 +6,9 @@ argument-hint: "[problem or feature idea | 問題或功能構想]"
6
6
 
7
7
  # Brainstorm Assistant | 腦力激盪助手
8
8
 
9
- Structured ideation before specification writing. Transform vague ideas into actionable feature proposals through guided brainstorming.
9
+ Structured ideation before specification writing. Transform vague ideas into actionable feature proposals through a persona-ensemble brainstorm.
10
10
 
11
- 在撰寫規格前進行結構化發想。透過引導式腦力激盪,將模糊構想轉化為可執行的功能提案。
11
+ 在撰寫規格前進行結構化發想。透過 persona 集成式腦力激盪,將模糊構想轉化為可執行的功能提案。
12
12
 
13
13
  ## Usage | 用法
14
14
 
@@ -19,33 +19,37 @@ Structured ideation before specification writing. Transform vague ideas into act
19
19
  ## Workflow | 工作流程
20
20
 
21
21
  ```
22
- FRAME ──► DIVERGE ──► CONVERGE ──► OUTPUT
23
- 定義問題 發散思考 收斂評估 輸出提案
22
+ PRE-FLIGHT ──► FRAME ──► DIVERGE ──────────► CONVERGE ─────────► OUTPUT
23
+ 防錨定 定義問題 persona 集成+透鏡 多評審面板+硬角色反駁 輸出提案
24
24
  ```
25
25
 
26
- | Phase | Goal | Key Techniques | 目標 |
27
- |-------|------|----------------|------|
26
+ | Phase | Goal | Key Techniques (v3) | 目標 |
27
+ |-------|------|---------------------|------|
28
+ | **PRE-FLIGHT** | Prevent AI anchoring | User writes 3 ideas first; no analogy seed | 防止錨定 |
28
29
  | **FRAME** | Define problem clearly | 5 Whys, HMW, Stakeholder Map | 清楚定義問題 |
29
- | **DIVERGE** | Generate many ideas | HMW, SCAMPER, Six Thinking Hats | 產生大量想法 |
30
- | **CONVERGE** | Evaluate and rank | Evaluation Matrix, Dot Voting | 評估與排序 |
30
+ | **DIVERGE** | Force viewpoint diversity | Persona ensemble + diversity lenses | 逼出視角多樣性 |
31
+ | **CONVERGE** | Bias-checked selection | Multi-critic panel + Devil's Advocate/Steelman | 降偏誤選擇 |
31
32
  | **OUTPUT** | Actionable report | Brainstorm Report template | 可執行的報告 |
32
33
 
33
34
  ## Techniques | 技法
34
35
 
35
36
  | Technique | Best For | 適用場景 |
36
37
  |-----------|----------|----------|
37
- | **5 Whys** | Finding root causes | 找到根本原因 |
38
- | **HMW** | Reframing problems as opportunities | 將問題重構為機會 |
39
- | **SCAMPER** | Improving existing features | 改善現有功能 |
40
- | **Six Thinking Hats** | Multi-perspective analysis | 多角度分析 |
41
- | **Dot Voting** | Quick prioritization | 快速排序 |
38
+ | **Persona ensemble** | Forced viewpoint diversity (v3 core) | 強制視角多樣性(v3 核心) |
39
+ | **Diversity lenses** | Analogical / reversal / morphological | 突破顯而易見區 |
40
+ | **Multi-critic panel** | Bias-reduced scoring (v3 core) | 降偏誤評分(v3 核心) |
41
+ | **Devil's Advocate + Steelman** | Hard-role rebuttal | 硬角色反駁 |
42
+ | **5 Whys / HMW / SCAMPER / Six Hats** | Classic framing & divergence | 經典框定與發散 |
42
43
 
43
44
  ## Examples | 範例
44
45
 
45
46
  ```bash
46
- /brainstorm # Start interactive session
47
- /brainstorm "user retention" # Brainstorm around a topic
48
- /brainstorm --technique scamper # Use a specific technique
47
+ /brainstorm # Start interactive session
48
+ /brainstorm "user retention" # Brainstorm around a topic
49
+ /brainstorm --enhanced "user retention" # Parallel persona ensemble (if host supports it)
50
+ /brainstorm --personas "designer,economist,skeptic" # Custom personas
51
+ /brainstorm --lens analogical "onboarding" # Force the analogical lens
52
+ /brainstorm --quick "reduce checkout friction" # Fast 3-idea mode
49
53
  ```
50
54
 
51
55
  ## Output Format | 輸出格式
@@ -57,9 +61,10 @@ FRAME ──► DIVERGE ──► CONVERGE ──► OUTPUT
57
61
  [Refined problem from FRAME phase]
58
62
 
59
63
  ## Top 3 Recommendations
60
- 1. **[Idea]** — Score: X.X — [Why recommended]
61
- 2. **[Idea]** — Score: X.X — [Why recommended]
62
- 3. **[Idea]** — Score: X.X — [Why recommended]
64
+ 1. **[Idea]** — Agg. X.X ✓ Passed rebuttal Persona: [..] — [Why recommended]
65
+
66
+ ## Diversity Note
67
+ [How many distinct personas/lenses the surviving ideas span]
63
68
 
64
69
  ## Next Steps
65
70
  - [ ] Proceed to `/requirement` with top idea
@@ -83,26 +88,36 @@ After brainstorming, the typical workflow is:
83
88
 
84
89
  | Input | AI Action |
85
90
  |-------|-----------|
86
- | `/brainstorm` | 詢問要探討的主題或問題,進入 FRAME 階段 |
87
- | `/brainstorm "topic"` | 以指定主題開始,進入 FRAME 階段 |
88
- | `/brainstorm --technique <name>` | 使用指定技法開始 DIVERGE 階段 |
91
+ | `/brainstorm` | 啟動 PRE-FLIGHT,請使用者先寫 問題+3 想法+反向排除 |
92
+ | `/brainstorm "topic"` | 以指定主題啟動 PRE-FLIGHT,再進入 FRAME |
93
+ | `/brainstorm --personas "a,b,c"` | 以自訂 persona 組進入 DIVERGE |
94
+ | `/brainstorm --lens <name>` | 以指定多樣性透鏡為主進入 DIVERGE |
95
+ | `/brainstorm --enhanced` | 若宿主支援子代理則平行跑 persona/評審,否則靜默退回 baseline |
89
96
 
90
97
  ### Interaction Script | 互動腳本
91
98
 
99
+ #### PRE-FLIGHT Phase
100
+ 1. 請使用者先寫 問題(一句)+3 個初始想法+最不想要的解法類型
101
+ 2. 拒絕「像 X 但給 Y」種子,改寫為底層問題
102
+
103
+ 🛑 **STOP**: 收到三項輸入前不生成任何想法(`--skip-preflight` 例外,顯示錨定警告)
104
+
92
105
  #### FRAME Phase
93
- 1. 釐清問題陳述(使用 5 Whys 或 HMW)
94
- 2. 確認問題定義
106
+ 1. 釐清問題陳述(5 Whys 找根因)
107
+ 2. 重構為 HMW 問題;識別利害關係人
95
108
 
96
109
  🛑 **STOP**: 問題定義後等待使用者確認
97
110
 
98
111
  #### DIVERGE Phase
99
- 1. 使用選定技法生成想法
100
- 2. 鼓勵數量優先於品質
101
- 3. 列出所有想法
112
+ 1. 逐一以預設 persona(領域專家/懷疑者/跨域類比者/成本約束者/使用者代言)思維鏈生成想法
113
+ 2. **分支隔離**:生成各 persona 時不互相預覽,全部產完才一起呈現
114
+ 3. 至少套用一個多樣性透鏡(類比/假設反轉/形態矩陣)
115
+ 4. 以多樣性(覆蓋的 persona/透鏡數)而非數量為繼續門檻
102
116
 
103
117
  #### CONVERGE Phase
104
- 1. 以評估矩陣(可行性、影響力、成本)評分
105
- 2. 排序前 3
118
+ 1. 以 3 個獨立評審(工程可行性/使用者影響/策略一致)各自評分後聚合
119
+ 2. 對前 3 名跑硬角色 Devil's Advocate(論證會失敗)+ Steelman;使用者以 (a)修改/(b)反駁/(c)移除 回應
120
+ 3. 排序並標記通過反駁的想法
106
121
 
107
122
  🛑 **STOP**: 展示 Brainstorm Report 後等待使用者決定下一步
108
123
 
@@ -110,7 +125,9 @@ After brainstorming, the typical workflow is:
110
125
 
111
126
  | Stop Point | 等待內容 |
112
127
  |-----------|---------|
128
+ | PRE-FLIGHT 提交前 | 收到使用者三項輸入 |
113
129
  | 問題定義後 | 確認問題正確 |
130
+ | 反駁輪每個反對理由 | 使用者 (a)/(b)/(c) 回應 |
114
131
  | 報告展示後 | 決定進入 `/requirement` 或 `/sdd` |
115
132
 
116
133
  ### Error Handling | 錯誤處理
@@ -118,10 +135,14 @@ After brainstorming, the typical workflow is:
118
135
  | Error Condition | AI Action |
119
136
  |-----------------|-----------|
120
137
  | 主題太廣泛 | 引導縮小範圍 |
121
- | 指定的技法不存在 | 列出可用技法供選擇 |
138
+ | 使用「像 X 但給 Y」種子 | 改寫為底層問題再繼續(反種子 guardrail) |
139
+ | 指定的 persona/透鏡不存在 | 列出可用選項供選擇 |
140
+ | `--enhanced` 但宿主不支援子代理 | 靜默退回 baseline,照常進行 |
141
+ | 前 3 名全來自同一 persona/透鏡 | 標示並在輸出前再跑一個透鏡 |
122
142
 
123
143
  ## References | 參考
124
144
 
125
145
  * [Brainstorm Assistant Skill](../brainstorm-assistant/SKILL.md)
146
+ * [Brainstorm Assistant Guide](../brainstorm-assistant/guide.md)
126
147
  * [Requirement Assistant](../requirement-assistant/SKILL.md)
127
148
  * [Spec-Driven Development](../spec-driven-dev/SKILL.md)
@@ -14,7 +14,7 @@ Answer structural questions across specs, decisions, and code — *"what is the
14
14
 
15
15
  回答橫跨規格、決策與程式碼的結構性問題——*「XSPEC-205 的完整影響鏈是什麼?」*——依據[知識圖記憶標準](../../core/knowledge-graph-memory.md)的關係 schema。有無圖引擎皆可運作。
16
16
 
17
- > **Implements**: XSPEC-237 Phase 5 — knowledge-graph skill (CodeSage opt-in)
17
+ > **Implements**: XSPEC-237 Phase 5 — knowledge-graph skill (EngramGraph opt-in)
18
18
 
19
19
  ## Mode Selection | 模式選擇
20
20
 
@@ -22,7 +22,7 @@ Detect which mode to use **before** answering:
22
22
 
23
23
  | Condition | Mode |
24
24
  |-----------|------|
25
- | `CODESAGE_URL` set, or a local graph engine responds on `/health` | Service mode (engine) |
25
+ | `ENGRAM_URL` set, or a local graph engine responds on `/health` | Service mode (engine) |
26
26
  | Otherwise | Degraded mode (Markdown) |
27
27
 
28
28
  ## Workflow | 工作流程
@@ -31,7 +31,7 @@ Detect which mode to use **before** answering:
31
31
  2. **Choose mode** — probe for a graph engine (service) else fall back (degraded).
32
32
  3. **Service mode (AC-5b)** — issue a single multi-hop query and present the returned chain, including cross-domain links (code → spec → decision):
33
33
  ```bash
34
- curl -s -X POST "$CODESAGE_URL/graph/impact-analysis" \
34
+ curl -s -X POST "$ENGRAM_URL/graph/impact-analysis" \
35
35
  -H 'content-type: application/json' \
36
36
  -d '{"nodeId":"XSPEC-205","maxHops":3}'
37
37
  ```
@@ -47,12 +47,12 @@ See [knowledge-graph-memory](../../core/knowledge-graph-memory.md). Front-matter
47
47
 
48
48
  ## Next Steps Guidance | 下一步引導
49
49
 
50
- - If degraded mode hit a reading-depth limit, tell the user a graph engine (e.g. CodeSage) would give a complete chain, and how to set `CODESAGE_URL`.
50
+ - If degraded mode hit a reading-depth limit, tell the user a graph engine (e.g. EngramGraph) would give a complete chain, and how to set `ENGRAM_URL`.
51
51
  - If a referenced id was not found, surface it as a dangling reference to fix.
52
52
  - Offer to add missing `impacts`/`impacted_by` front-matter to the documents you traversed.
53
53
 
54
54
  ## Reference | 參考
55
55
 
56
56
  - Standard: [core/knowledge-graph-memory.md](../../core/knowledge-graph-memory.md)
57
- - Engine (opt-in): [CodeSage](https://github.com/AsiaOstrich/CodeSage) — `@asiaostrich/codesage`
57
+ - Engine (opt-in): [EngramGraph](https://github.com/AsiaOstrich/EngramGraph) — `engramgraph`
58
58
  - Detailed guide: [guide.md](guide.md)
@@ -8,12 +8,12 @@ Companion to [SKILL.md](SKILL.md). Worked examples of both operating modes and t
8
8
 
9
9
  ## 1. Service Mode (graph engine) | 服務模式
10
10
 
11
- When a CodeSage-compatible engine is reachable, a single query returns the full chain.
11
+ When an EngramGraph-compatible engine is reachable, a single query returns the full chain.
12
12
 
13
13
  ### Impact analysis | 影響分析
14
14
 
15
15
  ```bash
16
- curl -s -X POST "$CODESAGE_URL/graph/impact-analysis" \
16
+ curl -s -X POST "$ENGRAM_URL/graph/impact-analysis" \
17
17
  -H 'content-type: application/json' \
18
18
  -d '{"nodeId":"XSPEC-205","maxHops":3}'
19
19
  # => { "nodeId": "XSPEC-205",
@@ -26,7 +26,7 @@ curl -s -X POST "$CODESAGE_URL/graph/impact-analysis" \
26
26
  ### Confidence feedback (SAGE) | 信心回饋
27
27
 
28
28
  ```bash
29
- curl -s -X POST "$CODESAGE_URL/graph/ingest" \
29
+ curl -s -X POST "$ENGRAM_URL/graph/ingest" \
30
30
  -H 'content-type: application/json' \
31
31
  -d '{"type":"test_fail","functionId":"src/a.ts#execute"}'
32
32
  ```
@@ -66,4 +66,4 @@ Both modes produce the **same answer shape** (a list of connected Specs/Decision
66
66
  ## Reference | 參考
67
67
 
68
68
  - [core/knowledge-graph-memory.md](../../core/knowledge-graph-memory.md)
69
- - [CodeSage](https://github.com/AsiaOstrich/CodeSage)
69
+ - [EngramGraph](https://github.com/AsiaOstrich/EngramGraph)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-dev-standards",
3
- "version": "5.15.1",
3
+ "version": "5.16.0",
4
4
  "description": "CLI tool for adopting Universal Development Standards",
5
5
  "keywords": [
6
6
  "documentation",
@@ -70,7 +70,7 @@
70
70
  "@vitest/coverage-v8": "^4.1.5",
71
71
  "ajv": "^8.20.0",
72
72
  "ajv-formats": "^3.0.1",
73
- "eslint": "10.4.0",
73
+ "eslint": "10.4.1",
74
74
  "glob": "^13.0.1",
75
75
  "globals": "17.6.0",
76
76
  "husky": "^9.1.7",
@@ -1861,13 +1861,14 @@ function displayWorkflowStatus(projectPath) {
1861
1861
  */
1862
1862
  async function runI18nLint(projectPath, options = {}) {
1863
1863
  const findings = lintI18nAll({ projectPath });
1864
- const { errors, warnings } = partitionI18nFindings(findings);
1864
+ const { errors, warnings, infos } = partitionI18nFindings(findings);
1865
1865
 
1866
1866
  if (options.json) {
1867
1867
  console.log(JSON.stringify({
1868
1868
  summary: {
1869
1869
  errors: errors.length,
1870
1870
  warnings: warnings.length,
1871
+ infos: infos.length,
1871
1872
  },
1872
1873
  findings,
1873
1874
  }, null, 2));
@@ -1900,8 +1901,16 @@ async function runI18nLint(projectPath, options = {}) {
1900
1901
  console.log();
1901
1902
  }
1902
1903
 
1904
+ // Info findings are collapsed to a summary line to avoid flooding output
1905
+ // (e.g. many locale files still lack source_hash). They never fail the gate.
1906
+ if (infos.length > 0) {
1907
+ console.log(chalk.cyan(` ℹ ${infos.length} locale file(s) lack source_hash — silent content drift cannot be detected for them.`));
1908
+ console.log(chalk.gray(' Stamp source_hash (sha256[:12] of canonical) when a locale is verified in sync to enable detection.'));
1909
+ console.log();
1910
+ }
1911
+
1903
1912
  console.log(chalk.gray('─'.repeat(50)));
1904
- console.log(` ${chalk.red('Errors:')} ${errors.length} ${chalk.yellow('Warnings:')} ${warnings.length}`);
1913
+ console.log(` ${chalk.red('Errors:')} ${errors.length} ${chalk.yellow('Warnings:')} ${warnings.length} ${chalk.cyan('Info:')} ${infos.length}`);
1905
1914
  console.log();
1906
1915
 
1907
1916
  if (errors.length > 0) {
package/src/lint/i18n.js CHANGED
@@ -8,7 +8,9 @@
8
8
  * locale:description-must-match-language (error)
9
9
  * locale:must-have-source-frontmatter (error)
10
10
  * canonical:l3-language-consistency (warn)
11
- * translation-drift-warn (warn)
11
+ * translation-drift-warn (warn) version-gap (needs a version on the canonical)
12
+ * translation-content-drift-warn (warn) source_hash mismatch — catches SILENT drift, version-independent (XSPEC-248)
13
+ * translation-hash-missing (info) blind-spot marker: no canonical version AND no source_hash → freshness unverifiable (XSPEC-248)
12
14
  *
13
15
  * `adopter-must-match-installed-locale` is deferred (needs project context,
14
16
  * not source-tree level).
@@ -18,6 +20,7 @@
18
20
 
19
21
  import { existsSync, readFileSync, readdirSync } from 'fs';
20
22
  import { join, dirname, basename, resolve } from 'path';
23
+ import { createHash } from 'crypto';
21
24
 
22
25
  // CJK Unified Ideographs ranges (excluding fullwidth punctuation, but
23
26
  // including ranges Ext-A/B used by zh-TW/zh-CN). Hangul, Hiragana,
@@ -30,6 +33,11 @@ const CJK_REGEX = /[㐀-䶿一-鿿豈-﫿぀-ゟ゠-ヿ가-힯]/;
30
33
  // eslint-disable-next-line no-control-regex
31
34
  const NON_ASCII_REGEX = /[^\x09\x0A\x0D\x20-\x7E]/;
32
35
 
36
+ // Global variant of CJK_REGEX (same character ranges, derived to avoid
37
+ // re-typing the ranges) — used to *count* CJK characters for the
38
+ // l3-language-consistency ratio check (bilingual templates vs. real drift).
39
+ const CJK_GLOBAL_REGEX = new RegExp(CJK_REGEX.source, 'g');
40
+
33
41
  /**
34
42
  * Parse a minimal YAML front matter from markdown content.
35
43
  * Returns { fm: {key→value}, fmEndLine: number, body: string }
@@ -112,6 +120,26 @@ function readSourceVersion(filePath) {
112
120
  return null;
113
121
  }
114
122
 
123
+ /**
124
+ * Compute a stable content hash of a canonical file: sha256 of its content
125
+ * with CRLF normalized and trailing whitespace stripped, truncated to 12 hex.
126
+ *
127
+ * Used for `source_hash` drift detection (XSPEC-248), which — unlike the
128
+ * version-gap check — does NOT depend on the canonical carrying a `version:`
129
+ * field. This is what catches "silent drift": canonical content changed but
130
+ * nobody bumped the locale's version metadata.
131
+ *
132
+ * @param {string} filePath - Absolute path to canonical file
133
+ * @returns {string|null} 12-hex digest, or null if file is missing
134
+ */
135
+ export function computeSourceHash(filePath) {
136
+ if (!existsSync(filePath)) return null;
137
+ const normalized = readFileSync(filePath, 'utf-8')
138
+ .replace(/\r\n/g, '\n')
139
+ .replace(/\s+$/, '');
140
+ return createHash('sha256').update(normalized).digest('hex').slice(0, 12);
141
+ }
142
+
115
143
  /**
116
144
  * Lint a canonical SKILL.md or core/*.md file for English-only frontmatter
117
145
  * and L3 language consistency.
@@ -119,14 +147,20 @@ function readSourceVersion(filePath) {
119
147
  * @param {string} skillMdPath - Absolute path to canonical file
120
148
  * @returns {Array<{rule, severity, line, message, file}>}
121
149
  */
122
- export function lintCanonical(skillMdPath) {
150
+ export function lintCanonical(skillMdPath, opts = {}) {
151
+ // opts.isGuide — canonical guide.md (XSPEC-248): guide descriptions
152
+ // legitimately carry CJK discoverability keywords (e.g. "可觀測性"), so the
153
+ // ASCII-description rule does NOT apply. The l3 body check still does (and
154
+ // runs regardless of description language, since there is no ASCII rule to
155
+ // double-report against).
156
+ const { isGuide = false } = opts;
123
157
  const findings = [];
124
158
  if (!existsSync(skillMdPath)) return findings;
125
159
  const content = readFileSync(skillMdPath, 'utf-8');
126
160
  const { fm, body } = parseFrontmatter(content);
127
161
 
128
- // Rule: canonical:description-must-be-ascii
129
- if (fm && fm.description) {
162
+ // Rule: canonical:description-must-be-ascii (SKILL.md / core only; not guides)
163
+ if (!isGuide && fm && fm.description) {
130
164
  if (NON_ASCII_REGEX.test(fm.description.value)) {
131
165
  findings.push({
132
166
  rule: 'canonical:description-must-be-ascii',
@@ -140,9 +174,13 @@ export function lintCanonical(skillMdPath) {
140
174
 
141
175
  // Rule: canonical:l3-language-consistency
142
176
  // Heuristic: scan code-block-style output templates (```...```) for
143
- // non-English example response text. Only fire when description is ASCII
144
- // (otherwise the description-must-be-ascii error already covers it).
145
- if (fm && fm.description && !NON_ASCII_REGEX.test(fm.description.value)) {
177
+ // non-English example response text. For SKILL.md/core only run when the
178
+ // description is ASCII (otherwise the description-must-be-ascii error already
179
+ // covers it); for guides (no ASCII rule) always scan the body.
180
+ const runL3 = isGuide
181
+ ? true
182
+ : Boolean(fm && fm.description && !NON_ASCII_REGEX.test(fm.description.value));
183
+ if (runL3) {
146
184
  const fenceRegex = /```([a-zA-Z0-9_-]*)\n([\s\S]*?)```/g;
147
185
  let match;
148
186
  while ((match = fenceRegex.exec(body)) !== null) {
@@ -158,19 +196,28 @@ export function lintCanonical(skillMdPath) {
158
196
  if (skipLangs.has(lang.toLowerCase())) continue;
159
197
 
160
198
  // Likely an output template / example response (e.g., ```markdown,
161
- // ```text, ```output, ```response). Flag if it contains CJK.
199
+ // ```text, ```output, ```response). Only flag when the block is
200
+ // *predominantly* CJK — i.e. an untranslated example dumped into
201
+ // canonical. Deliberate bilingual `English | 中文` templates (house
202
+ // style) stay English-dominant, so they are allowed; a Chinese-only
203
+ // example response is CJK-dominant and still caught. (XSPEC-248 l3
204
+ // refinement — rule vs. bilingual-convention tension.)
162
205
  if (CJK_REGEX.test(block)) {
163
- // Compute approx. line number of the fence start
164
- const lineNum = content.substring(0, match.index).split('\n').length;
165
- findings.push({
166
- rule: 'canonical:l3-language-consistency',
167
- severity: 'warn',
168
- line: lineNum,
169
- file: skillMdPath,
170
- message: `Canonical body contains a non-English example response inside a non-code fenced block (lang=\`${lang || 'plain'}\`). Move translated examples to the locale variant.`
171
- });
172
- // Only report first occurrence per file to keep output readable
173
- break;
206
+ const cjkCount = (block.match(CJK_GLOBAL_REGEX) || []).length;
207
+ const asciiLetterCount = (block.match(/[A-Za-z]/g) || []).length;
208
+ if (cjkCount > asciiLetterCount) {
209
+ // Compute approx. line number of the fence start
210
+ const lineNum = content.substring(0, match.index).split('\n').length;
211
+ findings.push({
212
+ rule: 'canonical:l3-language-consistency',
213
+ severity: 'warn',
214
+ line: lineNum,
215
+ file: skillMdPath,
216
+ message: `Canonical body contains a predominantly-CJK example response inside a non-code fenced block (lang=\`${lang || 'plain'}\`). Move the translated example to the locale variant (bilingual \`EN | 中文\` templates are allowed).`
217
+ });
218
+ // Only report first occurrence per file to keep output readable
219
+ break;
220
+ }
174
221
  }
175
222
  }
176
223
  }
@@ -233,12 +280,14 @@ export function lintLocale(skillMdPath, locale) {
233
280
  }
234
281
  }
235
282
 
236
- // Rule: translation-drift-warn
283
+ // Rules: translation-drift-warn (version) + translation-content-drift-warn (hash) + translation-hash-missing
237
284
  if (fm.source && fm.translation_version) {
238
285
  // Resolve source path relative to this file's directory
239
286
  const sourceRel = fm.source.value;
240
287
  const sourcePath = resolve(dirname(skillMdPath), sourceRel);
241
288
  const actualSourceVersion = readSourceVersion(sourcePath);
289
+
290
+ // (a) version-gap drift — only works when the canonical carries a version
242
291
  if (actualSourceVersion) {
243
292
  const gap = isMajorOrMinorGap(fm.translation_version.value, actualSourceVersion);
244
293
  if (gap) {
@@ -254,14 +303,40 @@ export function lintLocale(skillMdPath, locale) {
254
303
  });
255
304
  }
256
305
  }
306
+
307
+ // (b) content-hash drift — version-independent; catches SILENT drift (XSPEC-248)
308
+ if (fm.source_hash) {
309
+ const currentHash = computeSourceHash(sourcePath);
310
+ if (currentHash && currentHash !== fm.source_hash.value) {
311
+ findings.push({
312
+ rule: 'translation-content-drift-warn',
313
+ severity: 'warn',
314
+ line: fm.source_hash.line,
315
+ file: skillMdPath,
316
+ message: `Canonical content changed since last sync (source_hash \`${fm.source_hash.value}\` != current \`${currentHash}\`). Re-translate and update source_hash.`
317
+ });
318
+ }
319
+ } else if (!actualSourceVersion) {
320
+ // (c) blind-spot marker: neither a canonical version NOR a source_hash exists,
321
+ // so content freshness cannot be verified at all (how brainstorm rotted to v1.0).
322
+ findings.push({
323
+ rule: 'translation-hash-missing',
324
+ severity: 'info',
325
+ line: fm.source.line,
326
+ file: skillMdPath,
327
+ message: 'Cannot verify content freshness: canonical has no version field and locale has no source_hash. Stamp source_hash (sha256[:12] of canonical) to enable silent-drift detection.'
328
+ });
329
+ }
257
330
  }
258
331
 
259
332
  return findings;
260
333
  }
261
334
 
262
335
  /**
263
- * Batch-lint a UDS project tree. Scans canonical `skills/` and `core/`,
264
- * plus `locales/{locale}/skills/` and `locales/{locale}/core/`.
336
+ * Batch-lint a UDS project tree. Scans canonical `skills/` (SKILL.md + guide.md)
337
+ * and `core/`, plus `locales/{locale}/skills/` (SKILL.md + guide.md) and
338
+ * `locales/{locale}/core/`. Canonical guide.md uses the guide ruleset
339
+ * (CJK discoverability keywords allowed in description; XSPEC-248).
265
340
  *
266
341
  * @param {object} opts
267
342
  * @param {string} opts.projectPath - Project (or UDS) root path
@@ -276,7 +351,7 @@ export function lintAll({ projectPath, locales }) {
276
351
  const coreDir = join(projectPath, 'core');
277
352
  const localesDir = join(projectPath, 'locales');
278
353
 
279
- // --- Canonical skills ---
354
+ // --- Canonical skills (SKILL.md + guide.md) ---
280
355
  if (existsSync(skillsDir)) {
281
356
  for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
282
357
  if (!entry.isDirectory()) continue;
@@ -284,6 +359,11 @@ export function lintAll({ projectPath, locales }) {
284
359
  if (existsSync(skillFile)) {
285
360
  findings.push(...lintCanonical(skillFile));
286
361
  }
362
+ // XSPEC-248: guide.md uses the guide ruleset (CJK description allowed).
363
+ const guideFile = join(skillsDir, entry.name, 'guide.md');
364
+ if (existsSync(guideFile)) {
365
+ findings.push(...lintCanonical(guideFile, { isGuide: true }));
366
+ }
287
367
  }
288
368
  }
289
369
 
@@ -313,6 +393,11 @@ export function lintAll({ projectPath, locales }) {
313
393
  if (existsSync(skillFile)) {
314
394
  findings.push(...lintLocale(skillFile, locale));
315
395
  }
396
+ // XSPEC-248: locale guide.md reuses the locale ruleset (freshness checks).
397
+ const guideFile = join(localeSkillsDir, entry.name, 'guide.md');
398
+ if (existsSync(guideFile)) {
399
+ findings.push(...lintLocale(guideFile, locale));
400
+ }
316
401
  }
317
402
  }
318
403
  if (existsSync(localeCoreDir)) {
@@ -334,5 +419,6 @@ export function partitionFindings(findings) {
334
419
  return {
335
420
  errors: findings.filter(f => f.severity === 'error'),
336
421
  warnings: findings.filter(f => f.severity === 'warn'),
422
+ infos: findings.filter(f => f.severity === 'info'),
337
423
  };
338
424
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "version": "5.15.1",
3
+ "version": "5.16.0",
4
4
  "lastUpdated": "2026-05-13",
5
5
  "description": "Standards registry for universal-dev-standards with integrated skills and AI-optimized formats",
6
6
  "formats": {
@@ -58,14 +58,14 @@
58
58
  "standards": {
59
59
  "name": "universal-dev-standards",
60
60
  "url": "https://github.com/AsiaOstrich/universal-dev-standards",
61
- "version": "5.15.1"
61
+ "version": "5.16.0"
62
62
  },
63
63
  "skills": {
64
64
  "name": "universal-dev-standards",
65
65
  "url": "https://github.com/AsiaOstrich/universal-dev-standards",
66
66
  "localPath": "skills",
67
67
  "rawUrl": "https://raw.githubusercontent.com/AsiaOstrich/universal-dev-standards/main/skills",
68
- "version": "5.15.1",
68
+ "version": "5.16.0",
69
69
  "note": "Skills are now included in the main repository under skills/"
70
70
  }
71
71
  },
@@ -2350,7 +2350,7 @@
2350
2350
  "id": "license-compliance",
2351
2351
  "name": "License Compliance Standards",
2352
2352
  "nameZh": "授權合規標準",
2353
- "version": "5.15.1",
2353
+ "version": "5.16.0",
2354
2354
  "source": {
2355
2355
  "human": "core/license-compliance.md",
2356
2356
  "ai": "ai/standards/license-compliance.ai.yaml"