universal-dev-standards 6.3.6 → 6.3.8

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.
@@ -3,7 +3,7 @@ import { dirname, join, basename } from 'path';
3
3
  import { getLanguageRules } from '../prompts/integrations.js';
4
4
  import { computeIntegrationBlockHash } from './hasher.js';
5
5
  import { UDS_MARKERS, SUPPORTED_AI_TOOLS, LEGACY_TOOL_MAPPINGS } from '../core/constants.js';
6
- import { resolveSelectedOptionSources } from './registry.js';
6
+ import { resolveSelectedOptionSources, resolveStandardFilename } from './registry.js';
7
7
  import { getAgentConfig, getAgentTier } from '../config/ai-agent-paths.js';
8
8
 
9
9
  /**
@@ -101,8 +101,14 @@ const STANDARD_TASK_MAPPING = {
101
101
  'test-completeness-dimensions.md': { task: 'Test coverage', when: 'When evaluating tests', priority: 'SHOULD' },
102
102
  'git-workflow.ai.yaml': { task: 'Git workflow', when: 'Branch/merge decisions', priority: 'SHOULD' },
103
103
  'developer-memory.ai.yaml': { task: 'Developer memory', when: 'Always (protocol)', priority: 'SHOULD' },
104
- 'project-context-memory.ai.yaml': { task: 'Project context', when: 'Planning & Coding', priority: 'MUST' },
105
- 'workflow-enforcement.ai.yaml': { task: 'Workflow gates', when: 'Before any workflow phase', priority: 'MUST' }
104
+ 'project-context-memory.ai.yaml': { task: 'Project context', when: 'Planning & Coding', priority: 'MUST' }
105
+ // 'workflow-enforcement.ai.yaml' was removed in 6.0.0 (MIGRATION-v6 §2) along
106
+ // with seven other machine-readable standards whose runtime moved to the
107
+ // adoption layer. Its entry here pointed a task mapping at
108
+ // `.standards/workflow-enforcement.ai.yaml`, a file that has not shipped
109
+ // since. The human-readable core/workflow-enforcement.md was deliberately
110
+ // kept upstream, but adopters receive .standards/, not core/, so it is not a
111
+ // substitute path.
106
112
  };
107
113
 
108
114
  /**
@@ -2399,18 +2405,39 @@ All responses should be in **Traditional Chinese (繁體中文)**, with technica
2399
2405
  * @param {string} language - Language: 'en', 'zh-tw', or 'bilingual'
2400
2406
  * @returns {string} Generated minimal reference
2401
2407
  */
2402
- export function generateMinimalStandardsReference(installedStandards, format, language = 'zh-tw') {
2408
+ export function generateMinimalStandardsReference(
2409
+ installedStandards,
2410
+ format,
2411
+ language = 'zh-tw',
2412
+ standardsFormat = 'ai',
2413
+ ) {
2403
2414
  if (!installedStandards || installedStandards.length === 0) {
2404
2415
  return '';
2405
2416
  }
2406
2417
 
2407
2418
  const coreStandards = [];
2408
2419
  const optionStandards = [];
2409
-
2410
- // Separate core standards from options
2420
+ const unresolved = [];
2421
+
2422
+ // Separate core standards from options.
2423
+ //
2424
+ // `basename(entry)` used to serve for both, and it is only right for one:
2425
+ // option entries are paths, core entries are registry IDs, and an ID is not
2426
+ // a filename. That produced `.standards/error-code-standards` for a file
2427
+ // installed as `error-codes.ai.yaml` — seven dead paths out of seventy in a
2428
+ // real adopter's AGENTS.md, directly under a line telling the agent it must
2429
+ // read them.
2411
2430
  for (const standardPath of installedStandards) {
2412
- const filename = basename(standardPath);
2413
2431
  const isOption = standardPath.includes('/options/') || standardPath.includes('\\options\\');
2432
+ const filename = resolveStandardFilename(standardPath, standardsFormat);
2433
+
2434
+ // An entry we cannot resolve is not written out as if it were a path. A
2435
+ // listed path that does not exist is worse than an omission: the reader
2436
+ // cannot tell it apart from the ones that do, so it discredits the list.
2437
+ if (!filename) {
2438
+ unresolved.push(standardPath);
2439
+ continue;
2440
+ }
2414
2441
 
2415
2442
  if (isOption) {
2416
2443
  optionStandards.push({ filename, path: standardPath });
@@ -2446,6 +2473,15 @@ export function generateMinimalStandardsReference(installedStandards, format, la
2446
2473
  }
2447
2474
  sections.push('');
2448
2475
  }
2476
+
2477
+ // Said out loud rather than dropped quietly. A shorter list and a complete
2478
+ // one read the same, and only the manifest knows which this is.
2479
+ if (unresolved.length > 0) {
2480
+ sections.push(language === 'en'
2481
+ ? `> ${unresolved.length} manifest entr${unresolved.length === 1 ? 'y' : 'ies'} could not be matched to an installed file and ${unresolved.length === 1 ? 'is' : 'are'} not listed above: ${unresolved.join(', ')}. Run \`uds update\` — if that does not clear it, the manifest and the registry disagree.`
2482
+ : `> 有 ${unresolved.length} 筆 manifest 項目對不到已安裝的檔案,未列於上方:${unresolved.join('、')}。請執行 \`uds update\`;若仍未消失,代表 manifest 與 registry 不一致。`);
2483
+ sections.push('');
2484
+ }
2449
2485
  } else {
2450
2486
  // Plaintext format
2451
2487
  sections.push(language === 'en'
@@ -2468,6 +2504,13 @@ export function generateMinimalStandardsReference(installedStandards, format, la
2468
2504
  sections.push(`- .standards/options/${std.filename}`);
2469
2505
  }
2470
2506
  }
2507
+
2508
+ if (unresolved.length > 0) {
2509
+ sections.push('');
2510
+ sections.push(language === 'en'
2511
+ ? `NOTE: ${unresolved.length} manifest entries could not be matched to an installed file and are not listed: ${unresolved.join(', ')}`
2512
+ : `注意:有 ${unresolved.length} 筆 manifest 項目對不到已安裝的檔案,未列出:${unresolved.join('、')}`);
2513
+ }
2471
2514
  sections.push('');
2472
2515
  }
2473
2516
 
@@ -2562,7 +2605,7 @@ function generateWorkflowGateContent(language) {
2562
2605
  sections.push('| BDD | AUTOMATION | `.feature` file exists | → FORMULATION |');
2563
2606
  sections.push('| Commit | feat/fix | Check active specs | → Suggest `Refs: SPEC-XXX` |');
2564
2607
  sections.push('');
2565
- sections.push('Reference: `.standards/workflow-enforcement.ai.yaml`');
2608
+
2566
2609
  } else if (language === 'zh-cn') {
2567
2610
  sections.push('## 工作流程强制闸门');
2568
2611
  sections.push('');
@@ -2585,7 +2628,7 @@ function generateWorkflowGateContent(language) {
2585
2628
  sections.push('| BDD | AUTOMATION | `.feature` 文件存在 | → FORMULATION |');
2586
2629
  sections.push('| Commit | feat/fix | 检查活跃 spec | → 建议 `Refs: SPEC-XXX` |');
2587
2630
  sections.push('');
2588
- sections.push('参考: `.standards/workflow-enforcement.ai.yaml`');
2631
+
2589
2632
  } else {
2590
2633
  sections.push('## Workflow Enforcement Gates');
2591
2634
  sections.push('');
@@ -2608,7 +2651,7 @@ function generateWorkflowGateContent(language) {
2608
2651
  sections.push('| BDD | AUTOMATION | `.feature` file exists | → FORMULATION |');
2609
2652
  sections.push('| Commit | feat/fix | Check active specs | → Suggest `Refs: SPEC-XXX` |');
2610
2653
  sections.push('');
2611
- sections.push('Reference: `.standards/workflow-enforcement.ai.yaml`');
2654
+
2612
2655
  }
2613
2656
 
2614
2657
  return sections.join('\n');
@@ -2631,6 +2674,10 @@ export function generateIntegrationContent(config) {
2631
2674
  // New fields for enhanced standards compliance
2632
2675
  installedStandards = [],
2633
2676
  contentMode = 'minimal',
2677
+ // Content format of the installed standards ('ai' -> *.ai.yaml,
2678
+ // 'human' -> *.md). Needed to turn a manifest's registry IDs into the
2679
+ // filenames actually on disk; without it the block prints IDs as paths.
2680
+ standardsFormat = 'ai',
2634
2681
  // Output language option for dynamic commit standards generation
2635
2682
  outputLanguage = 'english'
2636
2683
  } = config;
@@ -2691,7 +2738,8 @@ export function generateIntegrationContent(config) {
2691
2738
  standardsContent += generateMinimalStandardsReference(
2692
2739
  installedStandards,
2693
2740
  format,
2694
- language
2741
+ language,
2742
+ standardsFormat
2695
2743
  );
2696
2744
  } else {
2697
2745
  // Index/Full mode: detailed compliance instructions + index
@@ -2699,7 +2747,8 @@ export function generateIntegrationContent(config) {
2699
2747
  installedStandards,
2700
2748
  contentMode,
2701
2749
  format,
2702
- language
2750
+ language,
2751
+ standardsFormat
2703
2752
  );
2704
2753
 
2705
2754
  const standardsIndex = generateStandardsIndex(
@@ -2718,8 +2767,20 @@ export function generateIntegrationContent(config) {
2718
2767
  sections.push('\n---\n');
2719
2768
  }
2720
2769
 
2721
- // Add workflow enforcement gates if workflow-enforcement standard is installed
2722
- if (installedStandards.some(s => basename(s) === 'workflow-enforcement.ai.yaml')) {
2770
+ // Add workflow enforcement gates if the project still declares the standard.
2771
+ //
2772
+ // Matched against both forms because a manifest can hold either: the legacy
2773
+ // path (pre-3.4.0) or the ID. vibeops and dev-platform both still carry
2774
+ // `workflow-enforcement` as an ID today, two majors after 6.0.0 removed the
2775
+ // file — checking only the filename silently stopped honouring their
2776
+ // declaration. The section's content is self-contained; what it no longer
2777
+ // does is point at `.standards/workflow-enforcement.ai.yaml`, which has not
2778
+ // existed since 6.0.0 and was being written into adopters' instruction
2779
+ // files regardless (asiaostrich-telemetry-client/CLAUDE.md:145).
2780
+ if (installedStandards.some(s => {
2781
+ const name = basename(s);
2782
+ return name === 'workflow-enforcement.ai.yaml' || name === 'workflow-enforcement';
2783
+ })) {
2723
2784
  const workflowGateContent = generateWorkflowGateContent(language);
2724
2785
  sections.push(workflowGateContent);
2725
2786
  sections.push('\n---\n');
@@ -2868,7 +2929,13 @@ export function integrationFileExists(tool, projectPath) {
2868
2929
  * @param {string} language - Language: 'en', 'zh-tw', or 'bilingual'
2869
2930
  * @returns {string} Generated compliance instructions
2870
2931
  */
2871
- export function generateComplianceInstructions(installedStandards, mode, format, language = 'zh-tw') {
2932
+ export function generateComplianceInstructions(
2933
+ installedStandards,
2934
+ mode,
2935
+ format,
2936
+ language = 'zh-tw',
2937
+ standardsFormat = 'ai',
2938
+ ) {
2872
2939
  if (mode === 'minimal' || !installedStandards || installedStandards.length === 0) {
2873
2940
  return '';
2874
2941
  }
@@ -2876,9 +2943,16 @@ export function generateComplianceInstructions(installedStandards, mode, format,
2876
2943
  const mustFollow = [];
2877
2944
  const shouldFollow = [];
2878
2945
 
2879
- // Categorize standards by priority
2946
+ // Categorize standards by priority.
2947
+ //
2948
+ // Keyed by installed filename, so a registry ID has to be resolved first —
2949
+ // `basename()` on an ID returns the ID, which matches nothing in the table,
2950
+ // and the standard silently gets no task mapping. No error, no shorter
2951
+ // output anyone would notice: just a standard the agent is never told when
2952
+ // to apply.
2880
2953
  for (const standardPath of installedStandards) {
2881
- const filename = basename(standardPath);
2954
+ const filename = resolveStandardFilename(standardPath, standardsFormat);
2955
+ if (!filename) continue;
2882
2956
  const mapping = STANDARD_TASK_MAPPING[filename];
2883
2957
  if (mapping) {
2884
2958
  const entry = {
@@ -3155,7 +3229,6 @@ export function getToolFilePath(tool) {
3155
3229
  }
3156
3230
 
3157
3231
 
3158
-
3159
3232
  /**
3160
3233
  * Get default commands based on ecosystem
3161
3234
  * @param {'node'|'python'|'go'|'java_maven'|'java_gradle'|'rust'|'ruby'} ecosystem
@@ -3327,15 +3400,18 @@ export function generateAgentsMdSummary(config = {}) {
3327
3400
  lines.push('All standards are in `.standards/`. Installed standards:');
3328
3401
  lines.push('');
3329
3402
 
3330
- const keyStandards = installedStandards
3331
- .filter(s => {
3332
- const name = basename(s);
3333
- return name.endsWith('.ai.yaml');
3334
- });
3335
-
3336
- for (const std of keyStandards) {
3337
- const name = basename(std).replace('.ai.yaml', '');
3338
- lines.push(`- \`.standards/${basename(std)}\` — ${name}`);
3403
+ // Resolved before filtering, not after. The filter asks for an `.ai.yaml`
3404
+ // suffix and a manifest's core entries are registry IDs, which carry no
3405
+ // suffix — so every core standard failed the test and the block listed
3406
+ // only the option files. On one adopter that meant seven lines standing in
3407
+ // for seventy, under a heading reading "Installed Standards", with nothing
3408
+ // anywhere reporting the sixty-three that had been dropped.
3409
+ const resolved = installedStandards
3410
+ .map((s) => resolveStandardFilename(s, 'ai'))
3411
+ .filter((name) => name?.endsWith('.ai.yaml'));
3412
+
3413
+ for (const filename of resolved) {
3414
+ lines.push(`- \`.standards/${filename}\` — ${filename.replace('.ai.yaml', '')}`);
3339
3415
  }
3340
3416
  } else {
3341
3417
  lines.push('No standards installed yet. Run `npx uds init` to install.');
@@ -244,3 +244,48 @@ export function resolveSelectedOptionSources(manifestOptions, format = 'ai') {
244
244
 
245
245
  return paths;
246
246
  }
247
+
248
+ /**
249
+ * The filename a manifest entry has on disk under `.standards/`.
250
+ *
251
+ * The `standards` array in a manifest is deliberately mixed: core standards are
252
+ * stored as registry IDs (v3.4.0 onwards), option files as their upstream
253
+ * source path, because options have no registry ID. Anything that turns those
254
+ * entries into paths has to handle both, and three places in
255
+ * integration-generator.js did not — each failing differently and silently:
256
+ *
257
+ * - minimal mode printed `.standards/<id>`, and an ID is not a filename.
258
+ * `error-code-standards` installs as `error-codes.ai.yaml`, `ai-agreement`
259
+ * as `ai-agreement-standards.ai.yaml`. Seven of seventy paths in a real
260
+ * adopter's AGENTS.md pointed at nothing, in a block whose first line tells
261
+ * the agent it must read them.
262
+ * - the index block filtered entries by an `.ai.yaml` suffix, which no ID
263
+ * has, so every core standard was dropped. That is why the same adopter's
264
+ * previous block listed seven options and none of its sixty-three core
265
+ * standards.
266
+ * - the task-mapping lookup is keyed by filename, so an ID missed every
267
+ * time and the standard quietly got no mapping.
268
+ *
269
+ * Returns null when the entry cannot be resolved — a caller must not fall back
270
+ * to printing the raw entry, which is the bug above.
271
+ *
272
+ * @param {string} entry - A manifest `standards` entry: registry ID or path
273
+ * @param {string} format - Content format: 'ai' or 'human'
274
+ * @returns {string|null} Basename as installed, or null if unresolvable
275
+ */
276
+ export function resolveStandardFilename(entry, format = 'ai') {
277
+ if (typeof entry !== 'string' || entry.length === 0) return null;
278
+
279
+ // A path (option files, and legacy pre-3.4.0 manifests) already names itself.
280
+ if (entry.includes('/') || entry.includes('.')) {
281
+ const parts = entry.split(/[/\\]/);
282
+ return parts[parts.length - 1] || null;
283
+ }
284
+
285
+ const found = getAllStandards().find((s) => s.id === entry);
286
+ if (!found) return null;
287
+ const source = getStandardSource(found, format);
288
+ if (!source) return null;
289
+ const parts = source.split(/[/\\]/);
290
+ return parts[parts.length - 1] || null;
291
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "version": "6.3.6",
3
+ "version": "6.3.8",
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": "6.3.6"
61
+ "version": "6.3.8"
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": "6.3.6",
68
+ "version": "6.3.8",
69
69
  "note": "Skills are now included in the main repository under skills/"
70
70
  }
71
71
  },
@@ -2236,7 +2236,7 @@
2236
2236
  "id": "license-compliance",
2237
2237
  "name": "License Compliance Standards",
2238
2238
  "nameZh": "授權合規標準",
2239
- "version": "6.3.6",
2239
+ "version": "6.3.8",
2240
2240
  "source": {
2241
2241
  "human": "core/license-compliance.md",
2242
2242
  "ai": "ai/standards/license-compliance.ai.yaml"
@@ -2248,7 +2248,7 @@
2248
2248
  "id": "verification-oracle",
2249
2249
  "name": "Verification Oracle Standards",
2250
2250
  "nameZh": "驗證 Oracle 標準",
2251
- "version": "6.3.6",
2251
+ "version": "6.3.8",
2252
2252
  "source": {
2253
2253
  "human": "core/verification-oracle.md",
2254
2254
  "ai": "ai/standards/verification-oracle.ai.yaml"
@@ -2260,7 +2260,7 @@
2260
2260
  "id": "model-provenance",
2261
2261
  "name": "Model Provenance Policy Standards",
2262
2262
  "nameZh": "模型來源政策標準",
2263
- "version": "6.3.6",
2263
+ "version": "6.3.8",
2264
2264
  "source": {
2265
2265
  "human": "core/model-provenance.md",
2266
2266
  "ai": "ai/standards/model-provenance.ai.yaml"
@@ -2272,7 +2272,7 @@
2272
2272
  "id": "resource-cost-boundary",
2273
2273
  "name": "Resource / Cost Boundary Declaration Standards",
2274
2274
  "nameZh": "資源/成本邊界宣告標準",
2275
- "version": "6.3.6",
2275
+ "version": "6.3.8",
2276
2276
  "source": {
2277
2277
  "human": "core/resource-cost-boundary.md",
2278
2278
  "ai": "ai/standards/resource-cost-boundary.ai.yaml"
@@ -1,117 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "title": "UDS Standard Definition (v2)",
4
- "description": "Schema for Universal Development Standards .ai.yaml files, supporting Dual-Layer Architecture.",
5
- "type": "object",
6
- "required": ["standard"],
7
- "properties": {
8
- "standard": {
9
- "type": "object",
10
- "description": "The Imagination Layer: Human-readable and AI-promptable guidelines.",
11
- "required": ["id", "name", "description", "guidelines"],
12
- "properties": {
13
- "id": {
14
- "type": "string",
15
- "description": "Unique identifier for the standard (e.g., STR-001)."
16
- },
17
- "name": {
18
- "type": "string",
19
- "description": "Display name of the standard."
20
- },
21
- "description": {
22
- "type": "string",
23
- "description": "Brief description for AI context."
24
- },
25
- "guidelines": {
26
- "type": "array",
27
- "items": {
28
- "type": "string"
29
- },
30
- "description": "List of semantic guidelines."
31
- }
32
- }
33
- },
34
- "physical_spec": {
35
- "type": "object",
36
- "description": "The Physical Layer: Machine-readable constraints and validators.",
37
- "required": ["type"],
38
- "properties": {
39
- "type": {
40
- "type": "string",
41
- "enum": ["filesystem_schema", "json_schema", "regex_validator", "custom_script", "script_file"],
42
- "description": "Type of the physical constraint."
43
- },
44
- "schema": {
45
- "type": "object",
46
- "description": "The actual schema definition (e.g., directory structure, JSON schema)."
47
- },
48
- "validator": {
49
- "type": "object",
50
- "description": "Configuration for the validation command.",
51
- "properties": {
52
- "command": {
53
- "type": "string",
54
- "description": "Shell command to run validation (use placeholders like {target})."
55
- },
56
- "script_path": {
57
- "type": "string",
58
- "description": "Path to a JS/TS script file that performs validation."
59
- },
60
- "rule": {
61
- "type": "string",
62
- "description": "Validation rule identifier."
63
- }
64
- }
65
- },
66
- "simulator": {
67
- "type": "object",
68
- "description": "Configuration for the simulation command (predictive validation).",
69
- "properties": {
70
- "command": {
71
- "type": "string",
72
- "description": "Command to run simulation. Use {input} placeholder."
73
- },
74
- "script_path": {
75
- "type": "string",
76
- "description": "Path to a JS/TS script file that performs simulation."
77
- },
78
- "type": {
79
- "type": "string",
80
- "enum": ["command", "script", "script_file"],
81
- "default": "command"
82
- }
83
- },
84
- "required": ["command"]
85
- },
86
- "fixer": {
87
- "type": "object",
88
- "description": "Configuration for auto-fixing violations.",
89
- "properties": {
90
- "type": {
91
- "type": "string",
92
- "enum": ["command", "copy_template", "script_file"],
93
- "description": "Type of fix action"
94
- },
95
- "command": {
96
- "type": "string",
97
- "description": "Command to run for fixing (for type=command)"
98
- },
99
- "script_path": {
100
- "type": "string",
101
- "description": "Path to a JS/TS script file that performs fixing (for type=script_file)."
102
- },
103
- "template_file": {
104
- "type": "string",
105
- "description": "Path to template file (relative to .standards/templates or absolute)"
106
- },
107
- "destination": {
108
- "type": "string",
109
- "description": "Destination path (relative to project root)"
110
- }
111
- },
112
- "required": ["type"]
113
- }
114
- }
115
- }
116
- }
117
- }