universal-dev-standards 5.14.0 → 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 (134) hide show
  1. package/bin/uds.js +2 -0
  2. package/bundled/ai/standards/acceptance-criteria-traceability.ai.yaml +31 -0
  3. package/bundled/ai/standards/ai-instruction-standards.ai.yaml +190 -3
  4. package/bundled/ai/standards/forward-derivation-standards.ai.yaml +23 -0
  5. package/bundled/ai/standards/knowledge-graph-memory.ai.yaml +83 -0
  6. package/bundled/core/acceptance-criteria-traceability.md +46 -0
  7. package/bundled/core/ai-instruction-standards.md +136 -11
  8. package/bundled/core/forward-derivation-standards.md +19 -0
  9. package/bundled/core/knowledge-graph-memory.md +119 -0
  10. package/bundled/locales/COVERAGE.md +226 -0
  11. package/bundled/locales/zh-CN/CHANGELOG.md +42 -3
  12. package/bundled/locales/zh-CN/README.md +1 -1
  13. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  14. package/bundled/locales/zh-CN/core/acceptance-criteria-traceability.md +46 -0
  15. package/bundled/locales/zh-CN/core/ai-instruction-standards.md +111 -5
  16. package/bundled/locales/zh-CN/core/forward-derivation-standards.md +19 -0
  17. package/bundled/locales/zh-CN/skills/ac-coverage/SKILL.md +194 -0
  18. package/bundled/locales/zh-CN/skills/adr-assistant/SKILL.md +135 -40
  19. package/bundled/locales/zh-CN/skills/brainstorm-assistant/SKILL.md +217 -63
  20. package/bundled/locales/zh-CN/skills/brainstorm-assistant/guide.md +599 -0
  21. package/bundled/locales/zh-CN/skills/commands/brainstorm.md +92 -25
  22. package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +78 -16
  23. package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +85 -26
  24. package/bundled/locales/zh-CN/skills/deploy-assistant/SKILL.md +189 -0
  25. package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +110 -0
  26. package/bundled/locales/zh-CN/skills/dev-methodology/guide.md +255 -0
  27. package/bundled/locales/zh-CN/skills/dev-workflow-guide/SKILL.md +70 -11
  28. package/bundled/locales/zh-CN/skills/journey-test-assistant/SKILL.md +209 -0
  29. package/bundled/locales/zh-CN/skills/knowledge-graph/SKILL.md +58 -0
  30. package/bundled/locales/zh-CN/skills/knowledge-graph/guide.md +74 -0
  31. package/bundled/locales/zh-CN/skills/migration-assistant/SKILL.md +125 -8
  32. package/bundled/locales/zh-CN/skills/observability-assistant/guide.md +188 -0
  33. package/bundled/locales/zh-CN/skills/orchestrate/SKILL.md +173 -0
  34. package/bundled/locales/zh-CN/skills/plan/SKILL.md +240 -0
  35. package/bundled/locales/zh-CN/skills/push/SKILL.md +242 -0
  36. package/bundled/locales/zh-CN/skills/retrospective-assistant/SKILL.md +104 -36
  37. package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +88 -32
  38. package/bundled/locales/zh-CN/skills/runbook-assistant/guide.md +216 -0
  39. package/bundled/locales/zh-CN/skills/skill-builder/SKILL.md +149 -0
  40. package/bundled/locales/zh-CN/skills/slo-assistant/guide.md +188 -0
  41. package/bundled/locales/zh-CN/skills/spec-derivation/SKILL.md +86 -0
  42. package/bundled/locales/zh-CN/skills/spec-derivation/guide.md +476 -0
  43. package/bundled/locales/zh-CN/skills/spec-driven-dev/SKILL.md +155 -81
  44. package/bundled/locales/zh-CN/skills/sweep/SKILL.md +151 -0
  45. package/bundled/locales/zh-CN/skills/testing-guide/SKILL.md +207 -110
  46. package/bundled/locales/zh-TW/CHANGELOG.md +46 -3
  47. package/bundled/locales/zh-TW/README.md +1 -1
  48. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  49. package/bundled/locales/zh-TW/core/acceptance-criteria-traceability.md +46 -0
  50. package/bundled/locales/zh-TW/core/ai-instruction-standards.md +130 -5
  51. package/bundled/locales/zh-TW/core/browser-compatibility-standards.md +222 -5
  52. package/bundled/locales/zh-TW/core/contract-testing-standards.md +184 -5
  53. package/bundled/locales/zh-TW/core/cross-flow-regression.md +192 -5
  54. package/bundled/locales/zh-TW/core/forward-derivation-standards.md +19 -0
  55. package/bundled/locales/zh-TW/core/knowledge-graph-memory.md +127 -0
  56. package/bundled/locales/zh-TW/core/release-readiness-gate.md +186 -5
  57. package/bundled/locales/zh-TW/core/self-review-protocol.md +9 -1
  58. package/bundled/locales/zh-TW/skills/ac-coverage/SKILL.md +192 -0
  59. package/bundled/locales/zh-TW/skills/adr-assistant/SKILL.md +21 -42
  60. package/bundled/locales/zh-TW/skills/ai-collaboration-standards/SKILL.md +5 -1
  61. package/bundled/locales/zh-TW/skills/brainstorm-assistant/SKILL.md +212 -59
  62. package/bundled/locales/zh-TW/skills/brainstorm-assistant/guide.md +266 -579
  63. package/bundled/locales/zh-TW/skills/commands/brainstorm.md +91 -26
  64. package/bundled/locales/zh-TW/skills/commit-standards/SKILL.md +77 -15
  65. package/bundled/locales/zh-TW/skills/contract-test-assistant/SKILL.md +75 -16
  66. package/bundled/locales/zh-TW/skills/deploy-assistant/SKILL.md +187 -0
  67. package/bundled/locales/zh-TW/skills/dev-methodology/SKILL.md +108 -0
  68. package/bundled/locales/zh-TW/skills/dev-methodology/guide.md +255 -0
  69. package/bundled/locales/zh-TW/skills/dev-workflow-guide/SKILL.md +125 -64
  70. package/bundled/locales/zh-TW/skills/journey-test-assistant/SKILL.md +222 -0
  71. package/bundled/locales/zh-TW/skills/knowledge-graph/SKILL.md +56 -0
  72. package/bundled/locales/zh-TW/skills/knowledge-graph/guide.md +74 -0
  73. package/bundled/locales/zh-TW/skills/migration-assistant/SKILL.md +128 -11
  74. package/bundled/locales/zh-TW/skills/observability-assistant/guide.md +188 -0
  75. package/bundled/locales/zh-TW/skills/orchestrate/SKILL.md +173 -0
  76. package/bundled/locales/zh-TW/skills/plan/SKILL.md +240 -0
  77. package/bundled/locales/zh-TW/skills/project-structure-guide/SKILL.md +5 -1
  78. package/bundled/locales/zh-TW/skills/push/SKILL.md +242 -0
  79. package/bundled/locales/zh-TW/skills/retrospective-assistant/SKILL.md +94 -28
  80. package/bundled/locales/zh-TW/skills/reverse-engineer/SKILL.md +84 -28
  81. package/bundled/locales/zh-TW/skills/runbook-assistant/guide.md +216 -0
  82. package/bundled/locales/zh-TW/skills/skill-builder/SKILL.md +165 -0
  83. package/bundled/locales/zh-TW/skills/slo-assistant/guide.md +188 -0
  84. package/bundled/locales/zh-TW/skills/spec-derivation/SKILL.md +83 -0
  85. package/bundled/locales/zh-TW/skills/spec-derivation/guide.md +476 -0
  86. package/bundled/locales/zh-TW/skills/spec-driven-dev/SKILL.md +148 -77
  87. package/bundled/locales/zh-TW/skills/sweep/SKILL.md +149 -0
  88. package/bundled/locales/zh-TW/skills/testing-guide/SKILL.md +141 -44
  89. package/bundled/skills/adr-assistant/SKILL.md +1 -1
  90. package/bundled/skills/ai-collaboration-standards/SKILL.md +1 -1
  91. package/bundled/skills/ai-friendly-architecture/SKILL.md +1 -1
  92. package/bundled/skills/ai-instruction-standards/SKILL.md +1 -1
  93. package/bundled/skills/api-design-assistant/SKILL.md +1 -1
  94. package/bundled/skills/audit-assistant/SKILL.md +1 -1
  95. package/bundled/skills/brainstorm-assistant/SKILL.md +142 -106
  96. package/bundled/skills/brainstorm-assistant/guide.md +256 -661
  97. package/bundled/skills/ci-cd-assistant/SKILL.md +1 -1
  98. package/bundled/skills/commands/brainstorm.md +51 -30
  99. package/bundled/skills/contract-test-assistant/SKILL.md +1 -1
  100. package/bundled/skills/database-assistant/SKILL.md +1 -1
  101. package/bundled/skills/deploy-assistant/SKILL.md +1 -1
  102. package/bundled/skills/documentation-guide/SKILL.md +1 -1
  103. package/bundled/skills/error-code-guide/SKILL.md +1 -1
  104. package/bundled/skills/git-workflow-guide/SKILL.md +1 -1
  105. package/bundled/skills/incident-response-assistant/SKILL.md +1 -1
  106. package/bundled/skills/journey-test-assistant/SKILL.md +1 -1
  107. package/bundled/skills/knowledge-graph/SKILL.md +58 -0
  108. package/bundled/skills/knowledge-graph/guide.md +69 -0
  109. package/bundled/skills/logging-guide/SKILL.md +1 -1
  110. package/bundled/skills/observability-assistant/SKILL.md +1 -1
  111. package/bundled/skills/orchestrate/SKILL.md +1 -1
  112. package/bundled/skills/plan/SKILL.md +1 -1
  113. package/bundled/skills/pr-automation-assistant/SKILL.md +1 -1
  114. package/bundled/skills/project-structure-guide/SKILL.md +1 -1
  115. package/bundled/skills/push/SKILL.md +1 -1
  116. package/bundled/skills/retrospective-assistant/SKILL.md +1 -1
  117. package/bundled/skills/reverse-engineer/SKILL.md +1 -1
  118. package/bundled/skills/runbook-assistant/SKILL.md +1 -1
  119. package/bundled/skills/security-assistant/SKILL.md +1 -1
  120. package/bundled/skills/security-scan-assistant/SKILL.md +1 -1
  121. package/bundled/skills/slo-assistant/SKILL.md +1 -1
  122. package/bundled/skills/sweep/SKILL.md +1 -1
  123. package/bundled/skills/testing-guide/SKILL.md +1 -1
  124. package/package.json +2 -2
  125. package/src/commands/check.js +80 -0
  126. package/src/commands/init.js +8 -1
  127. package/src/commands/update.js +49 -14
  128. package/src/i18n/messages.js +32 -5
  129. package/src/installers/skills-installer.js +49 -0
  130. package/src/lint/i18n.js +424 -0
  131. package/src/utils/config-manager.js +39 -0
  132. package/src/utils/skills-installer.js +39 -7
  133. package/standards-registry.json +16 -4
  134. package/bundled/locales/zh-TW/docs/SKILL-FALLBACK-GUIDE.md +0 -407
@@ -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);