universal-dev-standards 5.14.0 → 5.15.1

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 (71) hide show
  1. package/bin/uds.js +2 -0
  2. package/bundled/ai/standards/ai-instruction-standards.ai.yaml +190 -3
  3. package/bundled/ai/standards/knowledge-graph-memory.ai.yaml +83 -0
  4. package/bundled/core/ai-instruction-standards.md +136 -11
  5. package/bundled/core/knowledge-graph-memory.md +119 -0
  6. package/bundled/locales/COVERAGE.md +226 -0
  7. package/bundled/locales/zh-CN/CHANGELOG.md +32 -3
  8. package/bundled/locales/zh-CN/README.md +1 -1
  9. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  10. package/bundled/locales/zh-CN/core/ai-instruction-standards.md +111 -5
  11. package/bundled/locales/zh-TW/CHANGELOG.md +36 -3
  12. package/bundled/locales/zh-TW/README.md +1 -1
  13. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  14. package/bundled/locales/zh-TW/core/ai-instruction-standards.md +130 -5
  15. package/bundled/locales/zh-TW/core/knowledge-graph-memory.md +127 -0
  16. package/bundled/locales/zh-TW/core/self-review-protocol.md +9 -1
  17. package/bundled/locales/zh-TW/skills/ac-coverage/SKILL.md +192 -0
  18. package/bundled/locales/zh-TW/skills/ai-collaboration-standards/SKILL.md +5 -1
  19. package/bundled/locales/zh-TW/skills/deploy-assistant/SKILL.md +187 -0
  20. package/bundled/locales/zh-TW/skills/dev-methodology/SKILL.md +108 -0
  21. package/bundled/locales/zh-TW/skills/journey-test-assistant/SKILL.md +222 -0
  22. package/bundled/locales/zh-TW/skills/knowledge-graph/SKILL.md +56 -0
  23. package/bundled/locales/zh-TW/skills/orchestrate/SKILL.md +172 -0
  24. package/bundled/locales/zh-TW/skills/plan/SKILL.md +239 -0
  25. package/bundled/locales/zh-TW/skills/project-structure-guide/SKILL.md +5 -1
  26. package/bundled/locales/zh-TW/skills/push/SKILL.md +241 -0
  27. package/bundled/locales/zh-TW/skills/skill-builder/SKILL.md +165 -0
  28. package/bundled/locales/zh-TW/skills/spec-derivation/SKILL.md +83 -0
  29. package/bundled/locales/zh-TW/skills/sweep/SKILL.md +149 -0
  30. package/bundled/skills/adr-assistant/SKILL.md +1 -1
  31. package/bundled/skills/ai-collaboration-standards/SKILL.md +1 -1
  32. package/bundled/skills/ai-friendly-architecture/SKILL.md +1 -1
  33. package/bundled/skills/ai-instruction-standards/SKILL.md +1 -1
  34. package/bundled/skills/api-design-assistant/SKILL.md +1 -1
  35. package/bundled/skills/audit-assistant/SKILL.md +1 -1
  36. package/bundled/skills/ci-cd-assistant/SKILL.md +1 -1
  37. package/bundled/skills/contract-test-assistant/SKILL.md +1 -1
  38. package/bundled/skills/database-assistant/SKILL.md +1 -1
  39. package/bundled/skills/deploy-assistant/SKILL.md +1 -1
  40. package/bundled/skills/documentation-guide/SKILL.md +1 -1
  41. package/bundled/skills/error-code-guide/SKILL.md +1 -1
  42. package/bundled/skills/git-workflow-guide/SKILL.md +1 -1
  43. package/bundled/skills/incident-response-assistant/SKILL.md +1 -1
  44. package/bundled/skills/journey-test-assistant/SKILL.md +1 -1
  45. package/bundled/skills/knowledge-graph/SKILL.md +58 -0
  46. package/bundled/skills/knowledge-graph/guide.md +69 -0
  47. package/bundled/skills/logging-guide/SKILL.md +1 -1
  48. package/bundled/skills/observability-assistant/SKILL.md +1 -1
  49. package/bundled/skills/orchestrate/SKILL.md +1 -1
  50. package/bundled/skills/plan/SKILL.md +1 -1
  51. package/bundled/skills/pr-automation-assistant/SKILL.md +1 -1
  52. package/bundled/skills/project-structure-guide/SKILL.md +1 -1
  53. package/bundled/skills/push/SKILL.md +1 -1
  54. package/bundled/skills/retrospective-assistant/SKILL.md +1 -1
  55. package/bundled/skills/reverse-engineer/SKILL.md +1 -1
  56. package/bundled/skills/runbook-assistant/SKILL.md +1 -1
  57. package/bundled/skills/security-assistant/SKILL.md +1 -1
  58. package/bundled/skills/security-scan-assistant/SKILL.md +1 -1
  59. package/bundled/skills/slo-assistant/SKILL.md +1 -1
  60. package/bundled/skills/sweep/SKILL.md +1 -1
  61. package/bundled/skills/testing-guide/SKILL.md +1 -1
  62. package/package.json +1 -1
  63. package/src/commands/check.js +71 -0
  64. package/src/commands/init.js +8 -1
  65. package/src/commands/update.js +49 -14
  66. package/src/i18n/messages.js +32 -5
  67. package/src/installers/skills-installer.js +49 -0
  68. package/src/lint/i18n.js +338 -0
  69. package/src/utils/config-manager.js +39 -0
  70. package/src/utils/skills-installer.js +39 -7
  71. package/standards-registry.json +16 -4
@@ -0,0 +1,69 @@
1
+ # Knowledge Graph — Detailed Guide | 詳細指南
2
+
3
+ Companion to [SKILL.md](SKILL.md). Worked examples of both operating modes and the engine API.
4
+
5
+ [SKILL.md](SKILL.md) 的配套說明。兩種運作模式的範例與引擎 API。
6
+
7
+ ---
8
+
9
+ ## 1. Service Mode (graph engine) | 服務模式
10
+
11
+ When a CodeSage-compatible engine is reachable, a single query returns the full chain.
12
+
13
+ ### Impact analysis | 影響分析
14
+
15
+ ```bash
16
+ curl -s -X POST "$CODESAGE_URL/graph/impact-analysis" \
17
+ -H 'content-type: application/json' \
18
+ -d '{"nodeId":"XSPEC-205","maxHops":3}'
19
+ # => { "nodeId": "XSPEC-205",
20
+ # "decisions": [ {"id":"DEC-062","title":"...","via":"direct"},
21
+ # {"id":"DEC-069","title":"...","via":"supersedes"} ] }
22
+ ```
23
+
24
+ `via: "direct"` means the decision IMPACTS the spec directly; `via: "supersedes"` means it reaches the spec through a SUPERSEDES chain (≤ `maxHops`).
25
+
26
+ ### Confidence feedback (SAGE) | 信心回饋
27
+
28
+ ```bash
29
+ curl -s -X POST "$CODESAGE_URL/graph/ingest" \
30
+ -H 'content-type: application/json' \
31
+ -d '{"type":"test_fail","functionId":"src/a.ts#execute"}'
32
+ ```
33
+
34
+ Lowers the node's confidence; reads then surface higher-confidence nodes first.
35
+
36
+ ---
37
+
38
+ ## 2. Degraded Mode (Markdown only) | 降級模式
39
+
40
+ No engine required. Reconstruct the chain by reading files:
41
+
42
+ 1. Read the target document (e.g. `XSPEC-205`).
43
+ 2. Collect ids from its front-matter (`impacts`, `impacted_by`, `supersedes`, `related`) and inline `[[ref]]` links.
44
+ 3. For each id, read that document and repeat up to the desired hop depth.
45
+ 4. Classify each id by prefix (`XSPEC-*`/`SPEC-*` → Spec; `DEC-*`/`ADR-*` → Decision) and report the edges.
46
+
47
+ ```bash
48
+ # find the documents
49
+ grep -rl "id: XSPEC-205" --include='*.md' .
50
+ # discover outbound references
51
+ grep -nE "(impacts|impacted_by|supersedes|related):|\[\[(XSPEC|DEC|ADR)-" path/to/XSPEC-205.md
52
+ ```
53
+
54
+ Degraded mode is always correct but bounded by how many files you read; cross-domain code links (function → spec) are usually only available in service mode.
55
+
56
+ ---
57
+
58
+ ## 3. Equivalence | 等價性
59
+
60
+ Both modes produce the **same answer shape** (a list of connected Specs/Decisions with edge types). Service mode is faster and more complete (it can include code→spec→decision hops); degraded mode is the universal fallback. Always tell the user which mode produced the answer.
61
+
62
+ 兩種模式產生**相同形狀的答案**。服務模式更快更完整;降級模式為通用後備。務必告知使用者答案來自哪種模式。
63
+
64
+ ---
65
+
66
+ ## Reference | 參考
67
+
68
+ - [core/knowledge-graph-memory.md](../../core/knowledge-graph-memory.md)
69
+ - [CodeSage](https://github.com/AsiaOstrich/CodeSage)
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Implement structured logging with proper log levels and sensitive data handling.
6
6
  Use when: adding logging, debugging, setting up observability.
7
- Keywords: logging, log level, structured logging, observability, 日誌, 記錄, 結構化日誌.
7
+ Keywords: logging, log level, structured logging, observability.
8
8
  ---
9
9
 
10
10
  # Logging Guide
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide observability setup, metrics design, and alerting configuration.
6
6
  Use when: new service instrumentation, SLO definition, alert design, maturity assessment.
7
- Keywords: observability, metrics, traces, golden signals, alerting, SLO, 可觀測性, 告警.
7
+ Keywords: observability, metrics, traces, golden signals, alerting, SLO.
8
8
  allowed-tools: Read, Write, Grep, Glob
9
9
  argument-hint: "[service name or observability topic | 服務名稱或可觀測性主題]"
10
10
  ---
@@ -5,7 +5,7 @@ scope: universal
5
5
  description: |
6
6
  Orchestrate multi-task execution plans using Claude's native Agent tool (DAG-based, no external engine).
7
7
  Use when: executing a plan.json file with parallel/sequential task dependencies.
8
- Keywords: orchestrate, plan, execute, DAG, task plan, 編排, 執行計畫, 並行.
8
+ Keywords: orchestrate, plan, execute, DAG, task plan.
9
9
  argument-hint: "<plan.json> [--dry-run]"
10
10
  ---
11
11
 
@@ -5,7 +5,7 @@ scope: universal
5
5
  description: |
6
6
  Generate plan.json from Spec documents, OpenSpec changes, or free-text requirements.
7
7
  Use when: converting specifications into executable task plans for /orchestrate.
8
- Keywords: plan, spec, task plan, 計畫, 規格, 任務, plan.json, DAG.
8
+ Keywords: plan, spec, task plan, plan.json, DAG.
9
9
  argument-hint: "[spec-file.md | openspec-dir/ | \"requirement text\" | (interactive)]"
10
10
  ---
11
11
 
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide pull request creation, review automation, and merge strategies.
6
6
  Use when: creating PRs, automating reviews, configuring merge policies.
7
- Keywords: pull request, PR, merge, review, GitHub, GitLab, 合併請求, 審查.
7
+ Keywords: pull request, PR, merge, review, GitHub, GitLab.
8
8
  allowed-tools: Read, Grep, Glob, Bash(git:*, gh:*)
9
9
  argument-hint: "[branch name or PR number | 分支名稱或 PR 編號]"
10
10
  ---
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide for organizing project directories following language-specific best practices.
6
6
  Use when: creating projects, reorganizing structure, adding modules, setting up builds, deciding file placement.
7
- Keywords: project, structure, directory, layout, gitignore, scaffold, 專案結構, 目錄, file placement, utils, helpers, shared, where to put, 檔案歸檔.
7
+ Keywords: project, structure, directory, layout, gitignore, scaffold, file placement, utils, helpers, shared, where to put.
8
8
  ---
9
9
 
10
10
  # Project Structure Guide
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  AI-assisted safety layer for git push operations with quality gates and collaboration guardrails.
6
6
  Use when: pushing commits, force pushing, pushing to protected branches, pushing feature branches.
7
- Keywords: git push, force push, protected branch, quality gate, push receipt, PR automation, 推送, 保護分支, 品質閘門.
7
+ Keywords: git push, force push, protected branch, quality gate, push receipt, PR automation.
8
8
  allowed-tools: Read, Bash(git:*), Bash(npm:*), Bash(pnpm:*), Bash(yarn:*), Bash(bun:*)
9
9
  argument-hint: "[--force] [--target <branch>] [--skip-gates] [--no-pr]"
10
10
  ---
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  [UDS] Guide structured team retrospectives for Sprint and Release cycles.
6
6
  Use when: sprint end, release post-mortem, iteration review, process improvement.
7
- Keywords: retrospective, retro, sprint review, lessons learned, action items, 回顧, 檢討, 持續改善.
7
+ Keywords: retrospective, retro, sprint review, lessons learned, action items.
8
8
  allowed-tools: Read, Write, Glob, Grep
9
9
  argument-hint: "[sprint | release | actions | --technique starfish]"
10
10
  ---
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reverse
3
3
  scope: partial
4
- description: "[UDS] System archeology reverse engineer code across Logic, Data, and Runtime dimensions"
4
+ description: "[UDS] System archeology - reverse engineer code across Logic, Data, and Runtime dimensions"
5
5
  allowed-tools: Read, Grep, Glob, Bash(pg_dump:*), Bash(mysql:*), Bash(sqlite3:*), Bash(npm run:*), Bash(cat:*), Bash(docker:*)
6
6
  argument-hint: "[spec|data|runtime|bdd|tdd] <input>"
7
7
  disable-model-invocation: true
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide runbook creation, maintenance, and drill exercises.
6
6
  Use when: writing runbooks, planning drills, auditing runbook coverage, post-incident runbook updates.
7
- Keywords: runbook, operations, drill, on-call, procedure, 操作手冊, 演練.
7
+ Keywords: runbook, operations, drill, on-call, procedure.
8
8
  allowed-tools: Read, Write, Grep, Glob
9
9
  argument-hint: "[create|drill|coverage] [alert name or type | 告警名稱或類型]"
10
10
  ---
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide security review and vulnerability assessment following OWASP standards.
6
6
  Use when: security audit, vulnerability check, secure coding review, threat modeling.
7
- Keywords: security, OWASP, vulnerability, authentication, authorization, 安全, 漏洞, 認證.
7
+ Keywords: security, OWASP, vulnerability, authentication, authorization.
8
8
  allowed-tools: Read, Grep, Glob
9
9
  argument-hint: "[module or file to audit | 要審計的模組或檔案]"
10
10
  ---
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide automated security scanning, dependency auditing, and secret detection.
6
6
  Use when: dependency audit, CVE scanning, secret detection, license compliance.
7
- Keywords: scan, audit, CVE, dependency, secret, SBOM, vulnerability, 掃描, 漏洞.
7
+ Keywords: scan, audit, CVE, dependency, secret, SBOM, vulnerability.
8
8
  allowed-tools: Read, Grep, Glob, Bash(npm:audit, npx:*)
9
9
  argument-hint: "[scan type or target | 掃描類型或目標]"
10
10
  ---
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Guide SLI selection, SLO setting, and Error Budget management.
6
6
  Use when: defining service quality targets, setting up SLO-based alerting, Error Budget policy.
7
- Keywords: SLI, SLO, SLA, Error Budget, burn rate, service level, 服務等級.
7
+ Keywords: SLI, SLO, SLA, Error Budget, burn rate, service level.
8
8
  allowed-tools: Read, Write, Grep, Glob
9
9
  argument-hint: "[create|review|budget] [service name | 服務名稱]"
10
10
  ---
@@ -4,7 +4,7 @@ scope: universal
4
4
  description: |
5
5
  Scan codebase for debug artifacts and code quality issues; optionally auto-fix safe patterns.
6
6
  Use when: before committing, during PR review, or periodic codebase cleanup.
7
- Keywords: sweep, debug cleanup, console.log, debugger, TODO, ts-any, code quality, 掃描, 清理.
7
+ Keywords: sweep, debug cleanup, console.log, debugger, TODO, ts-any, code quality.
8
8
  allowed-tools: Read, Grep, Glob, Bash(find:*), Edit, Write
9
9
  argument-hint: "[--fix] [--report] [--path <dir>] [--exclude <pattern>]"
10
10
  ---
@@ -5,7 +5,7 @@ description: |
5
5
  Testing pyramid and test writing standards for UT/IT/ST/E2E.
6
6
  Supports ISTQB and Industry Pyramid frameworks.
7
7
  Use when: writing tests, discussing test coverage, test strategy, or test naming.
8
- Keywords: test, unit, integration, e2e, coverage, mock, ISTQB, SIT, 測試, 單元, 整合, 端對端.
8
+ Keywords: test, unit, integration, e2e, coverage, mock, ISTQB, SIT.
9
9
  ---
10
10
 
11
11
  # Testing Guide
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-dev-standards",
3
- "version": "5.14.0",
3
+ "version": "5.15.1",
4
4
  "description": "CLI tool for adopting Universal Development Standards",
5
5
  "keywords": [
6
6
  "documentation",
@@ -38,6 +38,7 @@ import { StandardValidator } from '../utils/standard-validator.js';
38
38
  import { WorkflowGate } from '../utils/workflow-gate.js';
39
39
  import { t, setLanguage, isLanguageExplicitlySet } from '../i18n/messages.js';
40
40
  import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
41
+ import { lintAll as lintI18nAll, partitionFindings as partitionI18nFindings } from '../lint/i18n.js';
41
42
 
42
43
  /**
43
44
  * Display the summary of file integrity status
@@ -243,6 +244,15 @@ function displayAdoptionStatus(manifest, msg, common, repoInfo) {
243
244
  export async function checkCommand(options = {}) {
244
245
  const projectPath = process.cwd();
245
246
 
247
+ // Handle --i18n option early — i18n lint is meant to be runnable both
248
+ // inside UDS source (lint canonical + locale variants) and inside
249
+ // adopter projects (lint installed locale variants). It does not require
250
+ // adoption-drift checks, so we short-circuit before guardAgainstSelfAdoption.
251
+ if (options.i18n) {
252
+ await runI18nLint(projectPath, options);
253
+ return;
254
+ }
255
+
246
256
  // Refuse to run inside the UDS source repo itself.
247
257
  // See DEC-044 / XSPEC-071 — adoption-drift check makes no sense for the
248
258
  // source repo. `--force` bypasses (e.g. private forks of UDS).
@@ -1837,3 +1847,64 @@ function displayWorkflowStatus(projectPath) {
1837
1847
  // Silently skip if workflow gate not available
1838
1848
  }
1839
1849
  }
1850
+
1851
+ // ============================================================
1852
+ // i18n Lint (XSPEC-239 — P1-CLI-5)
1853
+ // ============================================================
1854
+
1855
+ /**
1856
+ * Run i18n lint across canonical + locale variants and print findings.
1857
+ * Exits 1 if any error-level findings are detected.
1858
+ *
1859
+ * @param {string} projectPath
1860
+ * @param {object} options - CLI options (currently honors options.json)
1861
+ */
1862
+ async function runI18nLint(projectPath, options = {}) {
1863
+ const findings = lintI18nAll({ projectPath });
1864
+ const { errors, warnings } = partitionI18nFindings(findings);
1865
+
1866
+ if (options.json) {
1867
+ console.log(JSON.stringify({
1868
+ summary: {
1869
+ errors: errors.length,
1870
+ warnings: warnings.length,
1871
+ },
1872
+ findings,
1873
+ }, null, 2));
1874
+ if (errors.length > 0) process.exitCode = 1;
1875
+ return;
1876
+ }
1877
+
1878
+ console.log();
1879
+ console.log(chalk.bold('UDS i18n Lint (XSPEC-239)'));
1880
+ console.log(chalk.gray('─'.repeat(50)));
1881
+ console.log();
1882
+
1883
+ if (findings.length === 0) {
1884
+ console.log(chalk.green(' ✓ No i18n violations found.'));
1885
+ console.log();
1886
+ return;
1887
+ }
1888
+
1889
+ // Display errors first
1890
+ for (const f of errors) {
1891
+ console.log(chalk.red(` ✗ [${f.rule}]`));
1892
+ console.log(chalk.red(` ${f.file}:${f.line}`));
1893
+ console.log(chalk.gray(` ${f.message}`));
1894
+ console.log();
1895
+ }
1896
+ for (const f of warnings) {
1897
+ console.log(chalk.yellow(` ⚠ [${f.rule}]`));
1898
+ console.log(chalk.yellow(` ${f.file}:${f.line}`));
1899
+ console.log(chalk.gray(` ${f.message}`));
1900
+ console.log();
1901
+ }
1902
+
1903
+ console.log(chalk.gray('─'.repeat(50)));
1904
+ console.log(` ${chalk.red('Errors:')} ${errors.length} ${chalk.yellow('Warnings:')} ${warnings.length}`);
1905
+ console.log();
1906
+
1907
+ if (errors.length > 0) {
1908
+ process.exitCode = 1;
1909
+ }
1910
+ }
@@ -23,6 +23,7 @@ import {
23
23
  import { displayLanguageToLocale } from '../utils/locale.js';
24
24
  import { generateReleaseConfig, RELEASE_MODE_LABELS } from '../utils/release-config.js';
25
25
  import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
26
+ import { readInstallYaml } from '../utils/config-manager.js';
26
27
 
27
28
  /**
28
29
  * Init command - initialize standards in current project
@@ -308,7 +309,13 @@ echo "Pre-commit checks passed"
308
309
  * Build configuration for non-interactive mode
309
310
  */
310
311
  function buildNonInteractiveConfig(options, detected, projectPath) {
311
- const displayLanguage = options.locale || detectLanguage(null);
312
+ // Locale resolution order (XSPEC-239 §Req-3):
313
+ // CLI --locale > .uds/install.yaml locale: > UDS_LOCALE env > LANG > 'en'
314
+ // detectLanguage() handles UDS_LOCALE + LANG fallback internally (P1-CLI-3).
315
+ const installYaml = readInstallYaml(projectPath);
316
+ const displayLanguage = options.locale
317
+ || installYaml.locale
318
+ || detectLanguage(null);
312
319
 
313
320
  // Determine AI tools
314
321
  const detectedAiTools = Object.keys(detected.aiTools).filter(k => detected.aiTools[k]);
@@ -30,6 +30,7 @@ import {
30
30
  cleanupLegacyCommands
31
31
  } from '../utils/skills-installer.js';
32
32
  import { displayLanguageToLocale, isLocalizedLocale, detectLocaleFromStandards } from '../utils/locale.js';
33
+ import { readInstallYaml } from '../utils/config-manager.js';
33
34
  import {
34
35
  getAgentDisplayName,
35
36
  getAgentConfig,
@@ -237,17 +238,49 @@ function checkNewStandards(manifest) {
237
238
  }
238
239
 
239
240
  /**
240
- * Resolve locale from manifest with fallback detection.
241
- * If manifest has display_language, use it. Otherwise detect from .standards/ files.
241
+ * Resolve locale for skill / command installation.
242
+ *
243
+ * Resolution order (XSPEC-239 §Req-3 / P1-CLI-2 + P1-CLI-3):
244
+ * 1. CLI `--locale` (passed via `options.locale`)
245
+ * 2. `.uds/install.yaml` `locale:`
246
+ * 3. `UDS_LOCALE` env var
247
+ * 4. manifest `options.display_language` (existing behaviour)
248
+ * 5. `.standards/` file-based detection (existing behaviour)
249
+ * 6. `'en'`
250
+ *
242
251
  * @param {Object} manifest - Project manifest
243
252
  * @param {string} projectPath - Project root path
253
+ * @param {Object} [options] - CLI options object (optional, for `--locale`)
244
254
  * @returns {string} Locale directory name (e.g., 'zh-TW', 'en')
245
255
  */
246
- function resolveLocale(manifest, projectPath) {
256
+ function resolveLocale(manifest, projectPath, options) {
257
+ // 1. CLI flag wins
258
+ const cliLocale = options?.locale && displayLanguageToLocale(options.locale);
259
+ if (cliLocale && isLocalizedLocale(cliLocale)) return cliLocale;
260
+ if (cliLocale === 'en') return 'en';
261
+
262
+ // 2. .uds/install.yaml
263
+ const installYaml = readInstallYaml(projectPath);
264
+ if (installYaml.locale) {
265
+ const fromYaml = displayLanguageToLocale(installYaml.locale);
266
+ if (isLocalizedLocale(fromYaml)) return fromYaml;
267
+ if (fromYaml === 'en') return 'en';
268
+ }
269
+
270
+ // 3. UDS_LOCALE env
271
+ if (process.env.UDS_LOCALE) {
272
+ const fromEnv = displayLanguageToLocale(process.env.UDS_LOCALE);
273
+ if (isLocalizedLocale(fromEnv)) return fromEnv;
274
+ if (fromEnv === 'en') return 'en';
275
+ }
276
+
277
+ // 4. Manifest
247
278
  const fromManifest = displayLanguageToLocale(manifest.options?.display_language);
248
279
  if (isLocalizedLocale(fromManifest)) {
249
280
  return fromManifest;
250
281
  }
282
+
283
+ // 5. .standards/ detection → 6. 'en'
251
284
  return detectLocaleFromStandards(projectPath) || 'en';
252
285
  }
253
286
 
@@ -309,13 +342,13 @@ export async function updateCommand(options) {
309
342
 
310
343
  // Handle --skills option
311
344
  if (options.skills) {
312
- await updateSkillsOnly(projectPath, manifest);
345
+ await updateSkillsOnly(projectPath, manifest, options);
313
346
  return;
314
347
  }
315
348
 
316
349
  // Handle --commands option
317
350
  if (options.commands) {
318
- await updateCommandsOnly(projectPath, manifest);
351
+ await updateCommandsOnly(projectPath, manifest, options);
319
352
  return;
320
353
  }
321
354
 
@@ -892,7 +925,7 @@ export async function updateCommand(options) {
892
925
  // Install Skills if user agreed
893
926
  if (installSkills.length > 0) {
894
927
  const skillSpinner = ora(msg.installingNewSkills || 'Installing Skills...').start();
895
- const skillsLocale = resolveLocale(manifest, projectPath);
928
+ const skillsLocale = resolveLocale(manifest, projectPath, options);
896
929
  const skillResult = await installSkillsToMultipleAgents(installSkills, null, projectPath, skillsLocale);
897
930
 
898
931
  // Update manifest
@@ -933,7 +966,7 @@ export async function updateCommand(options) {
933
966
  // Update outdated Skills if user agreed
934
967
  if (updateSkills.length > 0) {
935
968
  const updateSpinner = ora(msg.updatingSkills || 'Updating Skills...').start();
936
- const updateLocale = resolveLocale(manifest, projectPath);
969
+ const updateLocale = resolveLocale(manifest, projectPath, options);
937
970
  const updateResult = await installSkillsToMultipleAgents(updateSkills, null, projectPath, updateLocale);
938
971
 
939
972
  // Update manifest version
@@ -969,7 +1002,7 @@ export async function updateCommand(options) {
969
1002
  // Install Commands if user agreed
970
1003
  if (installCommands.length > 0) {
971
1004
  const cmdSpinner = ora(msg.installingNewCommands || 'Installing commands...').start();
972
- const cmdLocale = resolveLocale(manifest, projectPath);
1005
+ const cmdLocale = resolveLocale(manifest, projectPath, options);
973
1006
  const cmdResult = await installCommandsToMultipleAgents(installCommands, null, projectPath, cmdLocale);
974
1007
 
975
1008
  // Update manifest
@@ -999,7 +1032,7 @@ export async function updateCommand(options) {
999
1032
  // Update outdated Commands if user agreed
1000
1033
  if (updateCommands.length > 0) {
1001
1034
  const updateCmdSpinner = ora(msg.updatingCommands || 'Updating Commands...').start();
1002
- const updateCmdLocale = resolveLocale(manifest, projectPath);
1035
+ const updateCmdLocale = resolveLocale(manifest, projectPath, options);
1003
1036
  const updateCmdResult = await installCommandsToMultipleAgents(updateCommands, null, projectPath, updateCmdLocale);
1004
1037
 
1005
1038
  // Update manifest version
@@ -1069,7 +1102,7 @@ export async function updateCommand(options) {
1069
1102
  }
1070
1103
  } else {
1071
1104
  // --yes mode: auto-install/update Skills and Commands (treat -y as confirming all prompts)
1072
- const skillsLocale = resolveLocale(manifest, projectPath);
1105
+ const skillsLocale = resolveLocale(manifest, projectPath, options);
1073
1106
  let hasChanges = false;
1074
1107
 
1075
1108
  if (missingSkills.length > 0) {
@@ -1551,8 +1584,9 @@ async function syncIntegrationReferences(projectPath, manifest) {
1551
1584
  * Update Skills for all AI agents (--skills option)
1552
1585
  * @param {string} projectPath - Project path
1553
1586
  * @param {Object} manifest - Manifest object
1587
+ * @param {Object} [options] - CLI options (forwarded for locale resolution)
1554
1588
  */
1555
- async function updateSkillsOnly(projectPath, manifest) {
1589
+ async function updateSkillsOnly(projectPath, manifest, options) {
1556
1590
  const msg = t().commands.update;
1557
1591
  const repoInfo = getRepositoryInfo();
1558
1592
  const latestVersion = repoInfo.skills.version;
@@ -1621,7 +1655,7 @@ async function updateSkillsOnly(projectPath, manifest) {
1621
1655
 
1622
1656
  const spinner = ora(msg.installingSkills || 'Installing Skills...').start();
1623
1657
 
1624
- const skillsLocaleForUpdate = resolveLocale(manifest, projectPath);
1658
+ const skillsLocaleForUpdate = resolveLocale(manifest, projectPath, options);
1625
1659
  const result = await installSkillsToMultipleAgents(
1626
1660
  fileBasedInstallations,
1627
1661
  null, // Install all skills
@@ -1665,8 +1699,9 @@ async function updateSkillsOnly(projectPath, manifest) {
1665
1699
  * Update slash commands for all AI agents (--commands option)
1666
1700
  * @param {string} projectPath - Project path
1667
1701
  * @param {Object} manifest - Manifest object
1702
+ * @param {Object} [options] - CLI options (forwarded for locale resolution)
1668
1703
  */
1669
- async function updateCommandsOnly(projectPath, manifest) {
1704
+ async function updateCommandsOnly(projectPath, manifest, options) {
1670
1705
  const msg = t().commands.update;
1671
1706
 
1672
1707
  console.log(chalk.cyan(msg.updatingCommandsOnly || 'Updating slash commands for all AI Agents...'));
@@ -1715,7 +1750,7 @@ async function updateCommandsOnly(projectPath, manifest) {
1715
1750
 
1716
1751
  const spinner = ora(msg.installingCommands || 'Installing commands...').start();
1717
1752
 
1718
- const commandsLocale = resolveLocale(manifest, projectPath);
1753
+ const commandsLocale = resolveLocale(manifest, projectPath, options);
1719
1754
  const result = await installCommandsToMultipleAgents(
1720
1755
  commandsInstallations,
1721
1756
  null, // Install all commands
@@ -1003,6 +1003,10 @@ export const messages = {
1003
1003
  installingSkills: 'Installing Claude Code Skills...',
1004
1004
  installedSkills: 'Installed {count} Skills to {locations}',
1005
1005
  installedSkillsWithErrors: 'Installed {count} Skills with {errors} errors',
1006
+ // P1-CLI-1: locale fallback summary (printed after install when some
1007
+ // skills lack a localized variant and fell back to English source)
1008
+ localeFallbackTitle: 'Locale fallback: {count} skill(s) fell back to English because no {locale} variant exists:',
1009
+ localeFallbackHint: 'See locales/COVERAGE.md for full coverage status.',
1006
1010
  // Success
1007
1011
  initializedSuccess: '✓ Standards initialized successfully!',
1008
1012
  filesCopied: '{count} files copied to project',
@@ -2264,6 +2268,9 @@ export const messages = {
2264
2268
  installingSkills: '安裝 Claude Code Skills 中...',
2265
2269
  installedSkills: '已安裝 {count} 個 Skills 到 {locations}',
2266
2270
  installedSkillsWithErrors: '已安裝 {count} 個 Skills,有 {errors} 個錯誤',
2271
+ // P1-CLI-1:locale fallback 摘要(安裝結束後顯示,列出沒有對應語系變體而退回英文來源的 skill)
2272
+ localeFallbackTitle: 'Locale fallback:{count} 個 skill 因為沒有 {locale} 變體而退回英文版本:',
2273
+ localeFallbackHint: '完整覆蓋率請參閱 locales/COVERAGE.md。',
2267
2274
  // Success
2268
2275
  initializedSuccess: '✓ 標準初始化成功!',
2269
2276
  filesCopied: '已複製 {count} 個檔案到專案',
@@ -3270,6 +3277,9 @@ export const messages = {
3270
3277
  installingSkills: '正在安装 Claude Code Skills...',
3271
3278
  installedSkills: '已安装 {count} 个 Skills 到 {locations}',
3272
3279
  installedSkillsWithErrors: '已安装 {count} 个 Skills,有 {errors} 个错误',
3280
+ // P1-CLI-1:locale fallback 摘要(安装结束后显示,列出没有对应语种变体而退回英文来源的 skill)
3281
+ localeFallbackTitle: 'Locale fallback:{count} 个 skill 因为没有 {locale} 变体而退回英文版本:',
3282
+ localeFallbackHint: '完整覆盖率请参阅 locales/COVERAGE.md。',
3273
3283
  // Success
3274
3284
  initializedSuccess: '✓ 标准初始化成功!',
3275
3285
  filesCopied: '已复制 {count} 个文件到项目',
@@ -3792,12 +3802,19 @@ export function msg(path) {
3792
3802
  }
3793
3803
 
3794
3804
  /**
3795
- * Detect language from environment or locale setting
3805
+ * Detect language from environment or locale setting.
3806
+ *
3807
+ * Resolution order (XSPEC-239 §Req-3 / P1-CLI-3):
3808
+ * 1. explicit `locale` arg (from CLI `--locale`)
3809
+ * 2. `UDS_LOCALE` env var (UDS-specific override; takes precedence over LANG)
3810
+ * 3. POSIX locale env vars (`LANG` / `LC_ALL` / `LC_MESSAGES`)
3811
+ * 4. `'en'`
3812
+ *
3796
3813
  * @param {string|null} locale - Locale setting from CLI options
3797
- * @returns {string} Detected language code
3814
+ * @returns {string} Detected language code ('zh-tw' | 'zh-cn' | 'en')
3798
3815
  */
3799
3816
  export function detectLanguage(locale) {
3800
- // If locale is explicitly set, use it
3817
+ // 1. If locale is explicitly set, use it
3801
3818
  if (locale === 'zh-tw') {
3802
3819
  return 'zh-tw';
3803
3820
  }
@@ -3805,7 +3822,17 @@ export function detectLanguage(locale) {
3805
3822
  return 'zh-cn';
3806
3823
  }
3807
3824
 
3808
- // Check environment variables
3825
+ // 2. UDS-specific env var (P1-CLI-3): preferred over generic POSIX LANG so
3826
+ // adopters can opt into a specific UDS locale in CI without touching LANG.
3827
+ if (process.env.UDS_LOCALE) {
3828
+ const v = process.env.UDS_LOCALE.toLowerCase();
3829
+ if (v === 'zh-tw' || v === 'zh_tw') return 'zh-tw';
3830
+ if (v === 'zh-cn' || v === 'zh_cn') return 'zh-cn';
3831
+ if (v === 'en') return 'en';
3832
+ // Unknown UDS_LOCALE values fall through to LANG detection below
3833
+ }
3834
+
3835
+ // 3. Check POSIX environment variables
3809
3836
  const envLang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || '';
3810
3837
  if (envLang.toLowerCase().includes('zh_tw') || envLang.toLowerCase().includes('zh-tw')) {
3811
3838
  return 'zh-tw';
@@ -3814,6 +3841,6 @@ export function detectLanguage(locale) {
3814
3841
  return 'zh-cn';
3815
3842
  }
3816
3843
 
3817
- // Default to English
3844
+ // 4. Default to English
3818
3845
  return 'en';
3819
3846
  }
@@ -34,6 +34,46 @@ import {
34
34
  } from '../config/ai-agent-paths.js';
35
35
  import { getRepositoryInfo } from '../utils/registry.js';
36
36
 
37
+ /**
38
+ * Print a single WARN block summarizing skills that fell back to the English
39
+ * source because no localized variant exists for the requested locale.
40
+ *
41
+ * Adopter-facing behaviour (XSPEC-239 §Req-3 / P1-CLI-1):
42
+ * - One yellow block at the end of install, listing all fallen-back skills.
43
+ * - Hint pointer to `locales/COVERAGE.md` for the full coverage matrix.
44
+ *
45
+ * The `messages` bundle is the same flat command-scope bundle that `installSkills`
46
+ * receives (e.g. `t().commands.init`). Falls back to English literals if keys
47
+ * are absent so older translation bundles keep working.
48
+ *
49
+ * @param {string[]} fallenBack - Skill names that fell back to English
50
+ * @param {string} [locale] - Requested locale (e.g. 'zh-TW'); used in the message
51
+ * @param {Object} [messages] - Command-scope i18n bundle (optional)
52
+ * @returns {void}
53
+ */
54
+ export function printLocaleFallbackWarning(fallenBack, locale, messages) {
55
+ if (!fallenBack || fallenBack.length === 0) return;
56
+
57
+ const count = fallenBack.length;
58
+ const localeLabel = locale || 'requested locale';
59
+
60
+ const titleTemplate = messages?.localeFallbackTitle
61
+ || 'Locale fallback: {count} skill(s) fell back to English because no {locale} variant exists:';
62
+ const hint = messages?.localeFallbackHint
63
+ || 'See locales/COVERAGE.md for full coverage status.';
64
+
65
+ const title = titleTemplate
66
+ .replace('{count}', String(count))
67
+ .replace('{locale}', localeLabel);
68
+
69
+ console.log();
70
+ console.log(chalk.yellow(`⚠ ${title}`));
71
+ for (const name of fallenBack) {
72
+ console.log(chalk.yellow(` - ${name}`));
73
+ }
74
+ console.log(chalk.gray(` ${hint}`));
75
+ }
76
+
37
77
  /**
38
78
  * Get all skill files mapping (skill name -> file paths)
39
79
  * Used for remote download fallback
@@ -120,6 +160,15 @@ export async function installSkills(skillsConfig, projectPath, messages, results
120
160
  .replace('{count}', installResult.totalInstalled)
121
161
  .replace('{errors}', installResult.totalErrors));
122
162
  }
163
+
164
+ // P1-CLI-1: Emit a single locale-fallback WARN after the install loop when
165
+ // adopters requested a localized variant but some skills only ship in English.
166
+ // The aggregated list is built in installSkillsToMultipleAgents (deduped).
167
+ if (Array.isArray(installResult.localeFallbacks) && installResult.localeFallbacks.length > 0) {
168
+ // `messages` is a flat command-scope bundle (e.g. t().commands.init);
169
+ // the WARN helper looks for localeFallbackTitle/Hint keys directly on it.
170
+ printLocaleFallbackWarning(installResult.localeFallbacks, skillsConfig.locale, messages);
171
+ }
123
172
  } else if (skillsConfig.needsInstall && skillsConfig.updateTargets?.length > 0) {
124
173
  // Legacy fallback for backward compatibility (remote download)
125
174
  await installSkillsLegacy(skillsConfig, projectPath, messages, results);