universal-dev-standards 5.7.1 → 5.7.2

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  source: ../../CHANGELOG.md
3
- source_version: 5.7.1
4
- translation_version: 5.7.1
3
+ source_version: 5.7.2
4
+ translation_version: 5.7.2
5
5
  last_synced: 2026-05-08
6
6
  status: current
7
7
  ---
@@ -14,7 +14,7 @@ status: current
14
14
 
15
15
  > **语言**: [English](../../README.md) | [繁體中文](../zh-TW/README.md) | 简体中文
16
16
 
17
- **版本**: 5.7.1 | **发布日期**: 2026-04-13 | **授权**: [双重授权](../../LICENSE) (CC BY 4.0 + MIT)
17
+ **版本**: 5.7.2 | **发布日期**: 2026-04-13 | **授权**: [双重授权](../../LICENSE) (CC BY 4.0 + MIT)
18
18
 
19
19
  语言无关、框架无关的软件项目文档标准。通过 AI 原生工作流,确保不同技术栈之间的一致性、质量和可维护性。
20
20
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  source: ../../CHANGELOG.md
3
- source_version: 5.7.1
4
- translation_version: 5.7.1
3
+ source_version: 5.7.2
4
+ translation_version: 5.7.2
5
5
  last_synced: 2026-05-08
6
6
  status: current
7
7
  ---
@@ -14,7 +14,7 @@ status: current
14
14
 
15
15
  > **語言**: [English](../../README.md) | 繁體中文 | [简体中文](../zh-CN/README.md)
16
16
 
17
- **版本**: 5.7.1 | **發布日期**: 2026-04-13 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
17
+ **版本**: 5.7.2 | **發布日期**: 2026-04-13 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
18
18
 
19
19
  語言無關、框架無關的軟體專案文件標準。透過 AI 原生工作流,確保不同技術堆疊之間的一致性、品質和可維護性。
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-dev-standards",
3
- "version": "5.7.1",
3
+ "version": "5.7.2",
4
4
  "description": "CLI tool for adopting Universal Development Standards",
5
5
  "keywords": [
6
6
  "documentation",
@@ -36,7 +36,7 @@ import { checkForUpdates } from '../utils/npm-registry.js';
36
36
  import { writeUpdateCache } from '../utils/update-checker.js';
37
37
  import { StandardValidator } from '../utils/standard-validator.js';
38
38
  import { WorkflowGate } from '../utils/workflow-gate.js';
39
- import { t, getLanguage, setLanguage, isLanguageExplicitlySet } from '../i18n/messages.js';
39
+ import { t, setLanguage, isLanguageExplicitlySet } from '../i18n/messages.js';
40
40
  import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
41
41
 
42
42
  /**
@@ -112,13 +112,42 @@ function performFileIntegrityCheck(projectPath, manifest, msg) {
112
112
  console.log(chalk.gray(` ${msg.checkingExistence}`));
113
113
  console.log();
114
114
 
115
- // Check standards
115
+ // Check standards — support both legacy path format and current ID format
116
+ const allStdsLegacy = getAllStandards();
117
+ const legacyFormat = manifest.format || 'ai';
116
118
  for (const std of manifest.standards) {
117
- const filePath = join(projectPath, '.standards', std.split('/').pop());
119
+ // Option file paths (ai/options/...) use a subdirectory; handle separately
120
+ if (std.includes('/options/') || std.startsWith('options/')) {
121
+ const fileName = std.split('/').pop();
122
+ const filePath = join(projectPath, '.standards', 'options', fileName);
123
+ if (existsSync(filePath)) {
124
+ fileStatus.noHash.push(`.standards/options/${fileName}`);
125
+ } else {
126
+ fileStatus.missing.push(`.standards/options/${fileName}`);
127
+ }
128
+ continue;
129
+ }
130
+ // ID format (no '/' or '.'): resolve via registry to get actual filename
131
+ let fileName;
132
+ if (!std.includes('/') && !std.includes('.')) {
133
+ const entry = allStdsLegacy.find(s => s.id === std);
134
+ if (entry) {
135
+ const src = entry.source;
136
+ const sourcePath = typeof src === 'string'
137
+ ? src
138
+ : (src?.[legacyFormat] || src?.ai || src?.human || std);
139
+ fileName = basename(sourcePath);
140
+ } else {
141
+ fileName = std;
142
+ }
143
+ } else {
144
+ fileName = std.split('/').pop();
145
+ }
146
+ const filePath = join(projectPath, '.standards', fileName);
118
147
  if (existsSync(filePath)) {
119
- fileStatus.noHash.push(`.standards/${std.split('/').pop()}`);
148
+ fileStatus.noHash.push(`.standards/${fileName}`);
120
149
  } else {
121
- fileStatus.missing.push(`.standards/${std.split('/').pop()}`);
150
+ fileStatus.missing.push(`.standards/${fileName}`);
122
151
  }
123
152
  }
124
153
 
@@ -496,7 +525,7 @@ async function showSingleFileDiff(projectPath, manifest, relativePath, msg) {
496
525
  const fullPath = join(projectPath, relativePath);
497
526
 
498
527
  // Get current content
499
- let currentContent = '';
528
+ let currentContent;
500
529
  try {
501
530
  currentContent = readFileSync(fullPath, 'utf-8');
502
531
  } catch {
@@ -739,10 +768,23 @@ async function migrateToHashBasedTracking(projectPath, manifest) {
739
768
  const now = new Date().toISOString();
740
769
  let count = 0;
741
770
 
742
- // Process standards
771
+ // Process standards — support both legacy path format and current ID format
772
+ const format = manifest.format || 'ai';
773
+ const allStds = getAllStandards();
743
774
  for (const std of manifest.standards) {
744
- const fileName = basename(std);
745
- const relativePath = (std.includes('options/')
775
+ // Resolve path: ID format → look up source path from registry
776
+ let resolvedPath = std;
777
+ if (!std.includes('/') && !std.includes('.')) {
778
+ const entry = allStds.find(s => s.id === std);
779
+ if (entry) {
780
+ const src = entry.source;
781
+ resolvedPath = typeof src === 'string'
782
+ ? src
783
+ : (src?.[format] || src?.ai || src?.human || std);
784
+ }
785
+ }
786
+ const fileName = basename(resolvedPath);
787
+ const relativePath = (resolvedPath.includes('options/')
746
788
  ? join('.standards', 'options', fileName)
747
789
  : join('.standards', fileName)).replace(/\\/g, '/');
748
790
  const fullPath = join(projectPath, relativePath);
@@ -8,8 +8,7 @@ import { msg, t as getMessages, setLanguage, isLanguageExplicitlySet } from '../
8
8
  import {
9
9
  getOptionSource,
10
10
  findOption,
11
- getAllStandards,
12
- getStandardSource
11
+ getAllStandards
13
12
  } from '../utils/registry.js';
14
13
  import {
15
14
  copyStandard,
@@ -641,7 +640,7 @@ export async function runProjectConfiguration(options) {
641
640
  try {
642
641
  unlinkSync(filePath);
643
642
  console.log(chalk.gray(` ${msgObj.removed}: ${getToolFilePath(tool)}`));
644
- } catch (err) {
643
+ } catch {
645
644
  console.log(chalk.yellow(` ${msgObj.couldNotRemove}: ${getToolFilePath(tool)}`));
646
645
  }
647
646
  }
@@ -15,11 +15,11 @@
15
15
  */
16
16
 
17
17
  import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
18
- import { join, basename } from 'path';
18
+ import { join } from 'path';
19
19
  import yaml from 'js-yaml';
20
20
  import { parseFlow } from '../flow/flow-parser.js';
21
21
  import { listFlows, validateFlowById, diffFlows } from '../flow/flow-commands.js';
22
- import { exportBundle, importBundle, validateBundle } from '../flow/flow-bundler.js';
22
+ import { exportBundle, importBundle } from '../flow/flow-bundler.js';
23
23
 
24
24
  /**
25
25
  * 從互動式回答建立 Flow 物件。
@@ -105,7 +105,7 @@ export function loadAllFlows(projectPath) {
105
105
  /**
106
106
  * CLI action: uds flow create
107
107
  */
108
- export async function flowCreateCommand(options) {
108
+ export async function flowCreateCommand(_options) {
109
109
  const inquirer = await import('inquirer');
110
110
  const projectPath = process.cwd();
111
111
 
@@ -65,7 +65,7 @@ export async function initCommand(options) {
65
65
  console.log();
66
66
 
67
67
  // Configuration object
68
- let config = {};
68
+ let config;
69
69
 
70
70
  if (!options.yes) {
71
71
  // Interactive Mode
@@ -218,7 +218,7 @@ async function setupHuskyHook(projectPath) {
218
218
  console.log(chalk.gray(' Initializing husky...'));
219
219
  execSync('npx husky init', { stdio: 'ignore', cwd: projectPath });
220
220
  }
221
- } catch (e) {
221
+ } catch {
222
222
  // Ignore, might already be init
223
223
  }
224
224
 
@@ -249,7 +249,7 @@ async function setupHuskyHook(projectPath) {
249
249
  writeFileSync(preCommitPath, content + `\n# UDS Standard Check\n${udsCmd}\n`, 'utf-8');
250
250
  try {
251
251
  execSync(`chmod +x ${preCommitPath}`);
252
- } catch (e) {
252
+ } catch {
253
253
  // Ignore chmod failures on systems that don't support it
254
254
  }
255
255
  console.log(chalk.green(' ✓ Adding uds check to pre-commit hook'));
@@ -204,10 +204,23 @@ function checkNewStandards(manifest) {
204
204
  // Include all reference and skill standards
205
205
  const eligibleStandards = registryStandards.filter(s => s.category === 'reference' || s.category === 'skill');
206
206
 
207
- // Get installed standard basenames for comparison
208
- const installedBasenames = new Set(
209
- (manifest.standards || []).map(s => basename(s))
210
- );
207
+ // Get installed standard basenames for comparison.
208
+ // Handles both legacy path format ("ai/standards/foo.ai.yaml" → basename "foo.ai.yaml")
209
+ // and v3.4.0 ID format ("foo" resolve to source path, then get basename).
210
+ const installedBasenames = new Set();
211
+ for (const s of (manifest.standards || [])) {
212
+ if (s.includes('/options/') || s.startsWith('options/')) continue; // Skip option paths
213
+ if (!s.includes('/') && !s.includes('.')) {
214
+ // ID format: resolve to actual source filename via registry
215
+ const entry = registryStandards.find(r => r.id === s);
216
+ if (entry) {
217
+ const sourcePath = getStandardSource(entry, format);
218
+ if (sourcePath) installedBasenames.add(basename(sourcePath));
219
+ }
220
+ } else {
221
+ installedBasenames.add(basename(s));
222
+ }
223
+ }
211
224
 
212
225
  // Find standards in registry that are not yet installed
213
226
  const newStandards = [];
@@ -394,9 +407,20 @@ export async function updateCommand(options) {
394
407
 
395
408
  // List files to update
396
409
  console.log(chalk.gray(msg.filesToUpdate));
410
+ const displayFormat = manifest.format || 'ai';
411
+ const allStdsDisplay = getAllStandards();
397
412
  for (const std of manifest.standards) {
398
- const fileName = std.split('/').pop();
399
- const displayPath = getStandardTargetDir(std) + '/' + fileName;
413
+ // ID format: resolve to source path for display
414
+ let resolvedStd = std;
415
+ if (!std.includes('/') && !std.includes('.')) {
416
+ const entry = allStdsDisplay.find(r => r.id === std);
417
+ if (entry) {
418
+ const src = getStandardSource(entry, displayFormat);
419
+ if (src) resolvedStd = src;
420
+ }
421
+ }
422
+ const fileName = resolvedStd.split('/').pop();
423
+ const displayPath = getStandardTargetDir(resolvedStd) + '/' + fileName;
400
424
  console.log(chalk.gray(` ${displayPath}`));
401
425
  }
402
426
  for (const ext of manifest.extensions) {
@@ -445,8 +469,19 @@ export async function updateCommand(options) {
445
469
  };
446
470
 
447
471
  // Update standards
472
+ const updateFormat = manifest.format || 'ai';
473
+ const allStdsUpdate = getAllStandards();
448
474
  for (const std of manifest.standards) {
449
- const result = await copyStandard(std, getStandardTargetDir(std), projectPath);
475
+ // ID format: resolve to source path for copying
476
+ let sourcePath = std;
477
+ if (!std.includes('/') && !std.includes('.')) {
478
+ const entry = allStdsUpdate.find(r => r.id === std);
479
+ if (entry) {
480
+ const src = getStandardSource(entry, updateFormat);
481
+ if (src) sourcePath = src;
482
+ }
483
+ }
484
+ const result = await copyStandard(sourcePath, getStandardTargetDir(sourcePath), projectPath);
450
485
  if (result.success) {
451
486
  results.updated.push(std);
452
487
  } else {
@@ -602,10 +637,21 @@ export async function updateCommand(options) {
602
637
  manifest.fileHashes = {};
603
638
  }
604
639
 
605
- // Update hashes for standards
640
+ // Update hashes for standards (handles both ID format and legacy path format)
641
+ const hashFormat = manifest.format || 'ai';
642
+ const allStdsHash = getAllStandards();
606
643
  for (const std of manifest.standards) {
607
- const fileName = basename(std);
608
- const relativePath = (std.includes('options/')
644
+ // Resolve ID-format to source path
645
+ let resolvedPath = std;
646
+ if (!std.includes('/') && !std.includes('.')) {
647
+ const entry = allStdsHash.find(r => r.id === std);
648
+ if (entry) {
649
+ const src = getStandardSource(entry, hashFormat);
650
+ if (src) resolvedPath = src;
651
+ }
652
+ }
653
+ const fileName = basename(resolvedPath);
654
+ const relativePath = (resolvedPath.includes('options/')
609
655
  ? join('.standards', 'options', fileName)
610
656
  : join('.standards', fileName)).replace(/\\/g, '/');
611
657
  const fullPath = join(projectPath, relativePath);
@@ -705,9 +751,20 @@ export async function updateCommand(options) {
705
751
 
706
752
  // Post-update integrity check: detect and restore missing files
707
753
  const allTrackedFiles = [];
754
+ const trackFormat = manifest.format || 'ai';
755
+ const allStdsTrack = getAllStandards();
708
756
  for (const std of manifest.standards) {
709
- const fileName = basename(std);
710
- const relativePath = std.includes('options/')
757
+ // Resolve ID-format to source path for computing relativePath
758
+ let resolvedStd = std;
759
+ if (!std.includes('/') && !std.includes('.')) {
760
+ const entry = allStdsTrack.find(r => r.id === std);
761
+ if (entry) {
762
+ const src = getStandardSource(entry, trackFormat);
763
+ if (src) resolvedStd = src;
764
+ }
765
+ }
766
+ const fileName = basename(resolvedStd);
767
+ const relativePath = resolvedStd.includes('options/')
711
768
  ? join('.standards', 'options', fileName)
712
769
  : join('.standards', fileName);
713
770
  allTrackedFiles.push(relativePath);
@@ -1,20 +1,21 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
- import { join, dirname } from 'path';
2
+ import { join, dirname, basename } from 'path';
3
+ import { getAllStandards } from '../utils/registry.js';
3
4
 
4
5
  /**
5
- * UDS Manifest Schema v3.3.0
6
+ * UDS Manifest Schema v3.4.0
6
7
  * Central configuration and state tracking for UDS installations
7
8
  */
8
9
 
9
10
  /**
10
11
  * Supported manifest schema versions
11
12
  */
12
- export const SUPPORTED_SCHEMA_VERSIONS = ['3.0.0', '3.1.0', '3.2.0', '3.3.0'];
13
+ export const SUPPORTED_SCHEMA_VERSIONS = ['3.0.0', '3.1.0', '3.2.0', '3.3.0', '3.4.0'];
13
14
 
14
15
  /**
15
16
  * Current manifest schema version
16
17
  */
17
- export const CURRENT_SCHEMA_VERSION = '3.3.0';
18
+ export const CURRENT_SCHEMA_VERSION = '3.4.0';
18
19
 
19
20
  /**
20
21
  * Default manifest values
@@ -294,6 +295,11 @@ export function migrateManifest(manifest) {
294
295
  migrated = migrateToV330(migrated);
295
296
  }
296
297
 
298
+ if (currentVersion < '3.4.0') {
299
+ // Migrate to 3.4.0 - convert standards array from path format to ID format
300
+ migrated = migrateToV340(migrated);
301
+ }
302
+
297
303
  // Update schema version
298
304
  migrated.version = CURRENT_SCHEMA_VERSION;
299
305
 
@@ -367,6 +373,83 @@ function migrateToV330(manifest) {
367
373
  };
368
374
  }
369
375
 
376
+ /**
377
+ * Migration to version 3.4.0
378
+ * Converts standards array from legacy path format ("ai/standards/foo.ai.yaml",
379
+ * "core/foo.md") to registry ID format ("foo"). Deduplicates entries that
380
+ * refer to the same standard via different format paths (ai vs human).
381
+ * @param {Object} manifest - V3.3.x manifest
382
+ * @returns {Object} V3.4.0 compatible manifest
383
+ */
384
+ function migrateToV340(manifest) {
385
+ return {
386
+ ...manifest,
387
+ version: '3.4.0',
388
+ standards: migrateStandardsPathsToIds(manifest.standards || [])
389
+ };
390
+ }
391
+
392
+ /**
393
+ * Convert a standards array from legacy path format to registry ID format.
394
+ * Entries that already look like IDs (no "/" or ".") are kept as-is.
395
+ * Path entries are matched against the registry's source.ai / source.human
396
+ * fields (exact path match or basename match), then replaced with the
397
+ * registry ID. Entries that match no registry standard are dropped.
398
+ *
399
+ * @param {string[]} standards - Standards array (may be IDs or legacy paths)
400
+ * @returns {string[]} Sorted, deduplicated array of registry IDs
401
+ */
402
+ export function migrateStandardsPathsToIds(standards) {
403
+ if (!standards || standards.length === 0) return [];
404
+
405
+ const hasPathFormat = standards.some(
406
+ s => typeof s === 'string' && (s.includes('/') || s.includes('.'))
407
+ );
408
+ if (!hasPathFormat) return standards;
409
+
410
+ const allStandards = getAllStandards();
411
+ const ids = new Set();
412
+ // Option file paths (ai/options/...) have no short-ID equivalent; collect separately
413
+ const optionPaths = [];
414
+
415
+ for (const entry of standards) {
416
+ if (typeof entry !== 'string') continue;
417
+
418
+ // Already an ID (no path separators or extensions)
419
+ if (!entry.includes('/') && !entry.includes('.')) {
420
+ ids.add(entry);
421
+ continue;
422
+ }
423
+
424
+ // Option file paths (e.g. ai/options/commit-message/english.ai.yaml):
425
+ // no registry ID equivalent — preserve as-is so update/check can manage them
426
+ if (entry.includes('/options/') || entry.includes('options/')) {
427
+ optionPaths.push(entry);
428
+ continue;
429
+ }
430
+
431
+ // Base-standard path format — find matching registry entry
432
+ let matched = false;
433
+ for (const s of allStandards) {
434
+ const src = s.source;
435
+ const paths = typeof src === 'string'
436
+ ? [src]
437
+ : Object.values(src || {}).filter(p => typeof p === 'string');
438
+
439
+ if (paths.some(p => p === entry || basename(p) === basename(entry))) {
440
+ ids.add(s.id);
441
+ matched = true;
442
+ break;
443
+ }
444
+ }
445
+ // Unmatched non-option paths are silently dropped (standard removed from registry)
446
+ void matched;
447
+ }
448
+
449
+ // Return: sorted IDs first, then option paths (preserving order)
450
+ return [...ids].sort().concat(optionPaths);
451
+ }
452
+
370
453
  /**
371
454
  * Ensure all required fields exist on a manifest that already has the current
372
455
  * schema version. Older CLIs may have stamped the version number without
@@ -398,9 +481,15 @@ function ensureRequiredFields(manifest) {
398
481
  }
399
482
  }
400
483
 
484
+ // Safety net: convert any remaining path-format standards to IDs.
485
+ // Runs even at current schema version in case a manifest was written with
486
+ // the wrong format by an older CLI or a partial migration.
487
+ const standards = migrateStandardsPathsToIds(manifest.standards || []);
488
+
401
489
  return {
402
490
  ...manifest,
403
491
  options,
492
+ standards,
404
493
  contentMode: manifest.contentMode || 'index',
405
494
  fileHashes: normalizedHashes,
406
495
  skillHashes: manifest.skillHashes || {},
@@ -82,8 +82,25 @@ function calculateStandards(state, manifest) {
82
82
  const allStandards = getAllStandards();
83
83
 
84
84
  for (const standardId of (manifest.standards || [])) {
85
- // Find registry entry for this standard
86
- const registryEntry = allStandards.find(s => s.id === standardId);
85
+ // Skip option file paths — they are handled by calculateOptions, not this function.
86
+ // Option paths look like "ai/options/commit-message/english.ai.yaml".
87
+ if (standardId.includes('/options/') || standardId.startsWith('options/')) continue;
88
+
89
+ // Primary lookup: match by registry ID
90
+ let registryEntry = allStandards.find(s => s.id === standardId);
91
+
92
+ // Fallback: legacy path-format manifest entry (e.g. "ai/standards/foo.ai.yaml").
93
+ // Handles manifests that have not yet been migrated to v3.4.0 ID format.
94
+ if (!registryEntry && (standardId.includes('/') || standardId.includes('.'))) {
95
+ registryEntry = allStandards.find(s => {
96
+ const src = s.source;
97
+ const paths = typeof src === 'string'
98
+ ? [src]
99
+ : Object.values(src || {}).filter(p => typeof p === 'string');
100
+ return paths.some(p => p === standardId || basename(p) === basename(standardId));
101
+ });
102
+ }
103
+
87
104
  if (!registryEntry) continue;
88
105
 
89
106
  const source = getStandardSource(registryEntry, format);
@@ -14,6 +14,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs';
14
14
  import { join } from 'path';
15
15
  import { compareFileHash, hasFileHashes, computeFileHash } from './hasher.js';
16
16
  import { SUPPORTED_AI_TOOLS } from '../core/constants.js';
17
+ import { getAllStandards, getStandardSource } from './registry.js';
17
18
 
18
19
  /**
19
20
  * Run friction detection on a UDS installation
@@ -113,9 +114,20 @@ function detectUnusedStandards(projectPath, manifest) {
113
114
 
114
115
  const allConfigText = configContents.join('\n');
115
116
 
116
- // Check each standard file
117
+ // Check each standard file (handles both ID format and legacy path format)
118
+ const allStdsFriction = getAllStandards();
119
+ const frictionFormat = manifest.format || 'ai';
117
120
  const standardFiles = (manifest.standards || []).map(s => {
118
- return s.includes('/') ? s.split('/').pop() : s;
121
+ if (!s.includes('/') && !s.includes('.')) {
122
+ // ID format: resolve to actual filename
123
+ const entry = allStdsFriction.find(r => r.id === s);
124
+ if (entry) {
125
+ const src = getStandardSource(entry, frictionFormat);
126
+ if (src) return src.split('/').pop();
127
+ }
128
+ return s;
129
+ }
130
+ return s.split('/').pop();
119
131
  });
120
132
 
121
133
  for (const fileName of standardFiles) {
@@ -16,6 +16,7 @@ import { join } from 'path';
16
16
  import { readManifest } from './copier.js';
17
17
  import { compareFileHash, hasFileHashes } from './hasher.js';
18
18
  import { SUPPORTED_AI_TOOLS } from '../core/constants.js';
19
+ import { getAllStandards, getStandardSource } from './registry.js';
19
20
 
20
21
  /**
21
22
  * Run Layer 1 health check on a UDS installation
@@ -64,8 +65,22 @@ export function runHealthCheck(projectPath) {
64
65
 
65
66
  // Check 4: Each standard file in manifest exists
66
67
  if (manifest.standards && Array.isArray(manifest.standards)) {
68
+ const allStds = getAllStandards();
69
+ const stdFormat = manifest.format || 'ai';
67
70
  for (const standardId of manifest.standards) {
68
- const fileName = standardId.includes('/') ? standardId.split('/').pop() : standardId;
71
+ // Resolve ID-format to actual filename via registry
72
+ let fileName;
73
+ if (!standardId.includes('/') && !standardId.includes('.')) {
74
+ const entry = allStds.find(s => s.id === standardId);
75
+ if (entry) {
76
+ const src = getStandardSource(entry, stdFormat);
77
+ fileName = src ? src.split('/').pop() : standardId;
78
+ } else {
79
+ fileName = standardId;
80
+ }
81
+ } else {
82
+ fileName = standardId.split('/').pop();
83
+ }
69
84
  const filePath = join(standardsDir, fileName);
70
85
  if (!existsSync(filePath)) {
71
86
  issues.push({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "version": "5.7.1",
3
+ "version": "5.7.2",
4
4
  "lastUpdated": "2026-04-16",
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.7.1"
61
+ "version": "5.7.2"
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.7.1",
68
+ "version": "5.7.2",
69
69
  "note": "Skills are now included in the main repository under skills/"
70
70
  }
71
71
  },