universal-dev-standards 6.1.0 → 6.2.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.
- package/bundled/locales/zh-CN/CHANGELOG.md +46 -4
- package/bundled/locales/zh-CN/README.md +75 -33
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/behavior-snapshot.md +2 -2
- package/bundled/locales/zh-CN/core/data-migration-testing.md +2 -2
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +4 -4
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +9 -3
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +10 -11
- package/bundled/locales/zh-CN/docs/USER-MANUAL.md +40 -21
- package/bundled/locales/zh-CN/integrations/gemini-cli/README.md +12 -0
- package/bundled/locales/zh-CN/skills/ac-coverage/SKILL.md +11 -5
- package/bundled/locales/zh-CN/skills/adr-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/commands/brainstorm.md +2 -2
- package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/deploy-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/dev-workflow-guide/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/journey-test-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/knowledge-graph/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/knowledge-graph/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/migration-assistant/SKILL.md +188 -2
- package/bundled/locales/zh-CN/skills/observability-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/orchestrate/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/plan/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/push/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/retrospective-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/runbook-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/skill-builder/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/slo-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/spec-derivation/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/spec-driven-dev/SKILL.md +40 -2
- package/bundled/locales/zh-CN/skills/sweep/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/testing-guide/SKILL.md +2 -2
- package/bundled/locales/zh-TW/CHANGELOG.md +47 -4
- package/bundled/locales/zh-TW/README.md +75 -33
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/audit-trail.md +2 -2
- package/bundled/locales/zh-TW/core/behavior-snapshot.md +2 -2
- package/bundled/locales/zh-TW/core/browser-compatibility-standards.md +18 -7
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +4 -4
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +9 -3
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +10 -11
- package/bundled/locales/zh-TW/docs/USER-MANUAL.md +40 -21
- package/bundled/locales/zh-TW/integrations/gemini-cli/README.md +12 -0
- package/bundled/skills/commands/journey-test.md +80 -6
- package/bundled/skills/commands/skill-builder.md +75 -6
- package/package.json +3 -3
- package/src/commands/check.js +77 -20
- package/src/commands/config.js +9 -0
- package/src/commands/init.js +15 -1
- package/src/commands/release.js +1 -1
- package/src/commands/update.js +82 -31
- package/src/config/ai-agent-paths.js +47 -7
- package/src/core/constants.js +61 -1
- package/src/core/manifest.js +56 -0
- package/src/flow/flow-parser.js +1 -1
- package/src/flow/gate-loader.js +1 -1
- package/src/i18n/messages.js +12 -6
- package/src/installers/standards-installer.js +10 -20
- package/src/reconciler/actual-state-scanner.js +118 -43
- package/src/reconciler/desired-state-calculator.js +157 -43
- package/src/reconciler/diff-engine.js +7 -2
- package/src/reconciler/manifest-migrator.js +5 -2
- package/src/reconciler/plan-executor.js +53 -14
- package/src/uninstallers/integration-uninstaller.js +7 -1
- package/src/utils/config-loader.js +1 -1
- package/src/utils/config-manager.js +1 -1
- package/src/utils/github.js +5 -1
- package/src/utils/hasher.js +7 -4
- package/src/utils/integration-generator.js +121 -78
- package/src/utils/registry.js +39 -0
- package/src/utils/skills-installer.js +49 -34
- package/src/utils/skills-source.js +51 -0
- package/src/utils/standard-fixer.js +1 -1
- package/src/utils/standard-validator.js +1 -1
- package/standards-registry.json +21 -11
|
@@ -3,6 +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
7
|
import { getAgentConfig, getAgentTier } from '../config/ai-agent-paths.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -107,24 +108,13 @@ const STANDARD_TASK_MAPPING = {
|
|
|
107
108
|
/**
|
|
108
109
|
* Standard descriptions for index generation
|
|
109
110
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
'versioning.md': '語意化版本',
|
|
118
|
-
'changelog-standards.md': '變更日誌標準',
|
|
119
|
-
'code-review-checklist.md': '程式碼審查清單',
|
|
120
|
-
'spec-driven-development.md': '規格驅動開發',
|
|
121
|
-
'test-completeness-dimensions.md': '測試完整性維度',
|
|
122
|
-
'git-workflow.ai.yaml': 'Git 工作流程',
|
|
123
|
-
'developer-memory.ai.yaml': '開發者持久記憶',
|
|
124
|
-
'project-context-memory.ai.yaml': '專案情境記憶',
|
|
125
|
-
'zh-tw.md': '繁體中文本地化',
|
|
126
|
-
'workflow-enforcement.ai.yaml': '工作流程強制執行'
|
|
127
|
-
};
|
|
111
|
+
// STANDARD_DESCRIPTIONS 已移除(XSPEC-358 R1 裁決為方案 A:索引區塊不再列名稱清單)。
|
|
112
|
+
// 移除前的實況值得記錄,因為它是一個**綠燈測試釘住生產環境不成立假設**的實例:
|
|
113
|
+
// 該表有 16 筆真實中文描述,鍵**帶副檔名**('anti-hallucination.md')。
|
|
114
|
+
// init 路徑傳入 copyStandard 的 sourcePath → basename **含副檔名** → 查表命中。
|
|
115
|
+
// update 路徑傳入 manifest.standards → **不含副檔名**(71/78 筆)→ 查表全 miss。
|
|
116
|
+
// 於是同一個區塊在 `uds init` 後有描述、在 `uds update` 後全是 `名稱 - 名稱`。
|
|
117
|
+
// 單元測試餵的是完整檔名,所以它一直是綠的;dev-platform 實測 78/78 皆 fallback。
|
|
128
118
|
|
|
129
119
|
/**
|
|
130
120
|
* Commit type templates for different output_language options
|
|
@@ -2908,6 +2898,11 @@ export function generateComplianceInstructions(installedStandards, mode, format,
|
|
|
2908
2898
|
const sections = [];
|
|
2909
2899
|
|
|
2910
2900
|
if (format === 'markdown') {
|
|
2901
|
+
// 三個清單全空時不得留下一個空標題(XSPEC-358 §1)——
|
|
2902
|
+
// 一個沒有內容的章節,讀起來像「這裡本來該有東西而它不見了」。
|
|
2903
|
+
if (mustFollow.length === 0 && shouldFollow.length === 0) {
|
|
2904
|
+
return '';
|
|
2905
|
+
}
|
|
2911
2906
|
sections.push('## Standards Compliance Instructions');
|
|
2912
2907
|
sections.push('');
|
|
2913
2908
|
|
|
@@ -2936,6 +2931,9 @@ export function generateComplianceInstructions(installedStandards, mode, format,
|
|
|
2936
2931
|
}
|
|
2937
2932
|
} else {
|
|
2938
2933
|
// Plaintext format
|
|
2934
|
+
if (mustFollow.length === 0 && shouldFollow.length === 0) {
|
|
2935
|
+
return '';
|
|
2936
|
+
}
|
|
2939
2937
|
sections.push('## Standards Compliance Instructions');
|
|
2940
2938
|
sections.push('');
|
|
2941
2939
|
|
|
@@ -2975,71 +2973,108 @@ export function generateStandardsIndex(installedStandards, format, language = 'z
|
|
|
2975
2973
|
return '';
|
|
2976
2974
|
}
|
|
2977
2975
|
|
|
2978
|
-
const
|
|
2979
|
-
const
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2976
|
+
const total = installedStandards.length;
|
|
2977
|
+
const optionCount = installedStandards.filter(
|
|
2978
|
+
(p) => p.includes('/options/') || p.includes('\\options\\')
|
|
2979
|
+
).length;
|
|
2980
|
+
const coreCount = total - optionCount;
|
|
2981
|
+
|
|
2982
|
+
const heading = '## Installed Standards Index';
|
|
2983
|
+
const md = format === 'markdown';
|
|
2984
|
+
const body = language === 'en'
|
|
2985
|
+
? [
|
|
2986
|
+
`This project has adopted **${total}** UDS standards (${coreCount} core, ${optionCount} options), installed in \`.standards/\`.`,
|
|
2987
|
+
'',
|
|
2988
|
+
'The authoritative list is the `standards` field of `.standards/manifest.json`. Read a standard\'s own file to see what it requires — and check its first line, which is where a deprecated standard says so.'
|
|
2989
|
+
]
|
|
2990
|
+
: [
|
|
2991
|
+
`本專案採用 UDS 標準共 **${total}** 條(core ${coreCount}、options ${optionCount}),安裝於 \`.standards/\`。`,
|
|
2992
|
+
'',
|
|
2993
|
+
'權威清單為 `.standards/manifest.json` 的 `standards` 欄位。要知道某標準要求什麼,讀它自己的檔案——並看第一行,已棄用的標準會在那裡寫明。'
|
|
2994
|
+
];
|
|
2995
2995
|
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
sections.push(language === 'en'
|
|
3000
|
-
? 'This project has adopted UDS standards. All standards are in `.standards/`:'
|
|
3001
|
-
: '本專案採用 UDS 標準。所有規範位於 `.standards/`:');
|
|
3002
|
-
sections.push('');
|
|
2996
|
+
const plainBody = body.map((l) => l.replace(/`/g, ''));
|
|
2997
|
+
return [heading, '', ...(md ? body : plainBody), ''].join('\n');
|
|
2998
|
+
}
|
|
3003
2999
|
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3000
|
+
/**
|
|
3001
|
+
* Build the generation config for one AI tool from the manifest.
|
|
3002
|
+
*
|
|
3003
|
+
* Both the normal `uds update` path and the reconciler regenerate integration
|
|
3004
|
+
* blocks, and they used to derive this config independently. The reconciler's
|
|
3005
|
+
* copy omitted `categories` entirely, defaulted `outputLanguage` to English, and
|
|
3006
|
+
* looked up `manifest.integrationConfigs` by **tool key** while the manifest
|
|
3007
|
+
* stores it by **file name** — so the lookup always missed. The result was a
|
|
3008
|
+
* strictly poorer block: reconciling machine-setup silently dropped its
|
|
3009
|
+
* "提交訊息語言" and "Standards Compliance Instructions" sections, which the
|
|
3010
|
+
* normal update path had always written. Two generators, one contract, one of
|
|
3011
|
+
* them quietly lossy. (XSPEC-343 R2)
|
|
3012
|
+
*
|
|
3013
|
+
* `integrationConfigs` is deliberately NOT merged in: its `installedStandards`
|
|
3014
|
+
* is a snapshot frozen at install time, and honouring it would reintroduce
|
|
3015
|
+
* exactly the staleness this whole XSPEC is about.
|
|
3016
|
+
*
|
|
3017
|
+
* @param {Object} manifest - Project manifest
|
|
3018
|
+
* @param {string} tool - Tool key (e.g. 'claude-code')
|
|
3019
|
+
* @returns {Object} Config for generateIntegrationContent / writeIntegrationFile
|
|
3020
|
+
*/
|
|
3021
|
+
function withSelectedOptions(manifest) {
|
|
3022
|
+
const standards = manifest.standards || [];
|
|
3023
|
+
const seen = new Set(standards.map((p) => basename(p)));
|
|
3024
|
+
const extra = resolveSelectedOptionSources(manifest.options, manifest.format || 'ai')
|
|
3025
|
+
.filter((p) => !seen.has(basename(p)));
|
|
3026
|
+
return [...standards, ...extra];
|
|
3027
|
+
}
|
|
3011
3028
|
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
}
|
|
3019
|
-
} else {
|
|
3020
|
-
// Plaintext format
|
|
3021
|
-
sections.push('## Installed Standards Index');
|
|
3022
|
-
sections.push('');
|
|
3023
|
-
sections.push(language === 'en'
|
|
3024
|
-
? 'UDS standards installed in .standards/:'
|
|
3025
|
-
: 'UDS 標準已安裝於 .standards/:');
|
|
3026
|
-
sections.push('');
|
|
3029
|
+
export function buildToolIntegrationConfig(manifest, tool) {
|
|
3030
|
+
const selected = manifest.options?.output_language || manifest.options?.commit_language || 'english';
|
|
3031
|
+
const language = selected === 'bilingual'
|
|
3032
|
+
? 'bilingual'
|
|
3033
|
+
: selected === 'traditional-chinese' ? 'zh-tw' : 'en';
|
|
3034
|
+
const resolved = resolveContentModeForTool(tool, manifest.contentMode || 'auto');
|
|
3027
3035
|
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3036
|
+
return {
|
|
3037
|
+
tool,
|
|
3038
|
+
categories: ['anti-hallucination', 'commit-standards', 'code-review'],
|
|
3039
|
+
language,
|
|
3040
|
+
// Pass manifest entries UNCHANGED. Every consumer here documents this
|
|
3041
|
+
// parameter as "standard file paths" and basenames it for display, while
|
|
3042
|
+
// classifying options by `path.includes('/options/')`. Reducing to basenames
|
|
3043
|
+
// first destroys that signal: options silently count as core, so the index
|
|
3044
|
+
// block reported "(70 core, 0 options)" and the minimal block listed option
|
|
3045
|
+
// files under the wrong heading with a path that does not exist on disk.
|
|
3046
|
+
installedStandards: withSelectedOptions(manifest),
|
|
3047
|
+
contentMode: resolved.contentMode,
|
|
3048
|
+
level: resolved.level,
|
|
3049
|
+
outputLanguage: selected,
|
|
3050
|
+
methodology: manifest.methodology
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3031
3053
|
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
sections.push('');
|
|
3040
|
-
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Heading of the index-mode standards block. Not localised — both the English and
|
|
3056
|
+
* the Chinese body sit under this exact string, so it is a stable marker.
|
|
3057
|
+
*/
|
|
3058
|
+
export const STANDARDS_INDEX_HEADING = '## Installed Standards Index';
|
|
3041
3059
|
|
|
3042
|
-
|
|
3060
|
+
/**
|
|
3061
|
+
* Read the standards count declared by an index-mode block.
|
|
3062
|
+
*
|
|
3063
|
+
* XSPEC-358 R1 replaced the enumerated list with a count plus a pointer to the
|
|
3064
|
+
* manifest. Two checks in `uds check` still asserted the old contract by grepping
|
|
3065
|
+
* the integration file for every standard name, so after the change they reported
|
|
3066
|
+
* `5/70` and `0/7` and told the user to run `uds update` — which regenerates the
|
|
3067
|
+
* same non-enumerating block. An unsatisfiable loop. Checks must assert what the
|
|
3068
|
+
* block claims now, not what it used to list. (XSPEC-343 R2 / XSPEC-358 R1)
|
|
3069
|
+
*
|
|
3070
|
+
* @param {string} content - Full integration file content
|
|
3071
|
+
* @returns {number|null} Declared count, or null if this is not an index block
|
|
3072
|
+
*/
|
|
3073
|
+
export function parseStandardsIndexCount(content) {
|
|
3074
|
+
const at = content.indexOf(STANDARDS_INDEX_HEADING);
|
|
3075
|
+
if (at === -1) return null;
|
|
3076
|
+
const match = content.slice(at).match(/\*\*(\d+)\*\*/);
|
|
3077
|
+
return match ? Number(match[1]) : null;
|
|
3043
3078
|
}
|
|
3044
3079
|
|
|
3045
3080
|
/**
|
|
@@ -3053,7 +3088,15 @@ export function wrapWithMarkers(content, format) {
|
|
|
3053
3088
|
const warning = format === 'plaintext'
|
|
3054
3089
|
? '# WARNING: This block is managed by UDS (universal-dev-standards). DO NOT manually edit. Use \'npx uds install\' or \'npx uds update\' to modify.'
|
|
3055
3090
|
: '<!-- WARNING: This block is managed by UDS (universal-dev-standards). DO NOT manually edit. Use \'npx uds install\' or \'npx uds update\' to modify. -->';
|
|
3056
|
-
|
|
3091
|
+
// 冪等:warning 位於 markers **內部**,而 extractMarkedContent 取出的內容也含它,
|
|
3092
|
+
// 於是重新包裝會疊出第二份(dev-platform CLAUDE.md 實測 178/179 兩行完全相同)。
|
|
3093
|
+
// 這裡先剝掉內容開頭既有的 warning,不論上游哪條路徑造成都能修掉。
|
|
3094
|
+
// ⚠️ 未隔離出上游那條路徑——這是防禦性修法,如實記錄其範圍。
|
|
3095
|
+
const stripped = content.replace(
|
|
3096
|
+
/^(?:<!--\s*WARNING: This block is managed by UDS[\s\S]*?-->|#\s*WARNING: This block is managed by UDS[^\n]*)\n?/,
|
|
3097
|
+
''
|
|
3098
|
+
);
|
|
3099
|
+
return `${markers.start}\n${warning}\n${stripped}\n${markers.end}`;
|
|
3057
3100
|
}
|
|
3058
3101
|
|
|
3059
3102
|
/**
|
package/src/utils/registry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
|
+
import { MANIFEST_OPTION_BINDINGS } from '../core/constants.js';
|
|
4
5
|
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
@@ -205,3 +206,41 @@ export function isMultiSelectOption(standard, categoryKey) {
|
|
|
205
206
|
}
|
|
206
207
|
return standard.options[categoryKey].multiSelect === true;
|
|
207
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Resolve the option source paths a manifest's `options` selection installs.
|
|
212
|
+
*
|
|
213
|
+
* `manifest.options` is the authoritative record of what the project chose;
|
|
214
|
+
* `manifest.standards` records option files inconsistently — most repos list
|
|
215
|
+
* them, machine-setup lists none — so anything counting options from
|
|
216
|
+
* `manifest.standards` alone reported "0 options" for a project that has seven
|
|
217
|
+
* of them on disk. (XSPEC-343 R2)
|
|
218
|
+
*
|
|
219
|
+
* @param {Object} manifestOptions - `manifest.options`
|
|
220
|
+
* @param {string} [format='ai'] - Source format
|
|
221
|
+
* @returns {string[]} Registry-relative source paths, e.g. ai/options/testing/unit-testing.ai.yaml
|
|
222
|
+
*/
|
|
223
|
+
export function resolveSelectedOptionSources(manifestOptions, format = 'ai') {
|
|
224
|
+
if (!manifestOptions) return [];
|
|
225
|
+
const all = getAllStandards();
|
|
226
|
+
const paths = [];
|
|
227
|
+
|
|
228
|
+
for (const binding of MANIFEST_OPTION_BINDINGS) {
|
|
229
|
+
const key = binding.manifestKeys.find((k) => manifestOptions[k] != null);
|
|
230
|
+
if (!key) continue;
|
|
231
|
+
|
|
232
|
+
const standard = all.find((s) => s.id === binding.standardId);
|
|
233
|
+
if (!standard) continue;
|
|
234
|
+
|
|
235
|
+
const selection = manifestOptions[key];
|
|
236
|
+
for (const optionId of Array.isArray(selection) ? selection : [selection]) {
|
|
237
|
+
if (typeof optionId !== 'string') continue;
|
|
238
|
+
const option = findOption(standard, binding.categoryKey, optionId);
|
|
239
|
+
if (!option) continue;
|
|
240
|
+
const source = getOptionSource(option, format);
|
|
241
|
+
if (source) paths.push(source);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return paths;
|
|
246
|
+
}
|
|
@@ -15,10 +15,12 @@ import {
|
|
|
15
15
|
getSkillsDirForAgent,
|
|
16
16
|
getCommandsDirForAgent,
|
|
17
17
|
getSkillsSupportedAgents,
|
|
18
|
-
getCommandsSupportedAgents
|
|
18
|
+
getCommandsSupportedAgents,
|
|
19
|
+
getCommandFileExtension
|
|
19
20
|
} from '../config/ai-agent-paths.js';
|
|
20
21
|
import { computeDirectoryHashes, computeFileHash } from './hasher.js';
|
|
21
22
|
import { isLocalizedLocale } from './locale.js';
|
|
23
|
+
import { getSkillsSourceDir } from './skills-source.js';
|
|
22
24
|
|
|
23
25
|
// Get the CLI package root directory
|
|
24
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -26,19 +28,8 @@ const __dirname = dirname(__filename);
|
|
|
26
28
|
const CLI_ROOT = join(__dirname, '..', '..');
|
|
27
29
|
const BUNDLED_DIR = join(CLI_ROOT, 'bundled');
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* Prioritizes bundled directory (npm install), falls back to development path.
|
|
32
|
-
* @returns {string} Path to skills source directory
|
|
33
|
-
*/
|
|
34
|
-
function getSkillsSourceDir() {
|
|
35
|
-
const bundledPath = join(BUNDLED_DIR, 'skills');
|
|
36
|
-
if (existsSync(bundledPath)) {
|
|
37
|
-
return bundledPath;
|
|
38
|
-
}
|
|
39
|
-
// Development environment fallback
|
|
40
|
-
return join(CLI_ROOT, '..', 'skills');
|
|
41
|
-
}
|
|
31
|
+
// getSkillsSourceDir now lives in skills-source.js so github.js resolves the same path.
|
|
32
|
+
// Its previous private copy here was the half that worked; the copy in github.js was not.
|
|
42
33
|
|
|
43
34
|
/**
|
|
44
35
|
* Get the localized Skills source directory for a given locale.
|
|
@@ -108,23 +99,56 @@ export function getAvailableSkillNames() {
|
|
|
108
99
|
return [];
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
const NON_SKILL_ITEMS = [
|
|
112
|
-
'README.md', 'CONTRIBUTING.template.md',
|
|
113
|
-
'commands', '.manifest.json', '.DS_Store'
|
|
114
|
-
];
|
|
115
|
-
|
|
116
102
|
try {
|
|
117
103
|
return readdirSync(SKILLS_LOCAL_DIR)
|
|
118
104
|
.filter(item => {
|
|
119
|
-
if (NON_SKILL_ITEMS.includes(item)) return false;
|
|
120
105
|
const itemPath = join(SKILLS_LOCAL_DIR, item);
|
|
121
|
-
|
|
106
|
+
if (!statSync(itemPath).isDirectory()) return false;
|
|
107
|
+
// A skill is a directory containing SKILL.md. Defining it positively rather than
|
|
108
|
+
// by a deny-list matters: `skills/` also holds agents/, workflows/, ai/, tools/
|
|
109
|
+
// and _shared/, which belong to other installers. The old deny-list named only
|
|
110
|
+
// some of them, so the rest were treated as skills whose SKILL.md "failed to
|
|
111
|
+
// install" — 5 phantom failures that failed the whole install transaction.
|
|
112
|
+
// A new sibling directory now costs nothing; under a deny-list it would break
|
|
113
|
+
// installation until someone remembered to add it.
|
|
114
|
+
return existsSync(join(itemPath, 'SKILL.md'));
|
|
122
115
|
});
|
|
123
116
|
} catch {
|
|
124
117
|
return [];
|
|
125
118
|
}
|
|
126
119
|
}
|
|
127
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Every directory name in the skills source tree, including the ones that are not
|
|
123
|
+
* skills (`_shared`, `agents`, `ai`, `commands`, `tools`, `workflows`).
|
|
124
|
+
*
|
|
125
|
+
* Used as a provenance test: if a directory in an adopter's skills folder has a
|
|
126
|
+
* counterpart here, UDS put it there. If it does not, UDS did not — it is either
|
|
127
|
+
* an adopter's own skill or an artefact of a UDS version too old to reason about,
|
|
128
|
+
* and deleting it is not this tool's call to make.
|
|
129
|
+
*
|
|
130
|
+
* The distinction matters because an older CLI copied the non-skill siblings in
|
|
131
|
+
* by mistake, so a strict "is it a skill we ship" test would leave those behind
|
|
132
|
+
* while a naive "is it in the skills folder" test deletes hand-written skills.
|
|
133
|
+
* dev-platform has fourteen of those. (XSPEC-343 R2)
|
|
134
|
+
*
|
|
135
|
+
* @returns {Set<string>} Directory names present in the skills source tree
|
|
136
|
+
*/
|
|
137
|
+
export function getSkillsSourceEntryNames() {
|
|
138
|
+
if (!existsSync(SKILLS_LOCAL_DIR)) {
|
|
139
|
+
return new Set();
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return new Set(
|
|
143
|
+
readdirSync(SKILLS_LOCAL_DIR, { withFileTypes: true })
|
|
144
|
+
.filter((e) => e.isDirectory())
|
|
145
|
+
.map((e) => e.name)
|
|
146
|
+
);
|
|
147
|
+
} catch {
|
|
148
|
+
return new Set();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
128
152
|
/**
|
|
129
153
|
* Get list of available command names from local directory
|
|
130
154
|
* @returns {string[]} Array of command names (without .md extension)
|
|
@@ -550,19 +574,10 @@ export async function installCommandsForAgent(agent, level = 'project', commandN
|
|
|
550
574
|
return results;
|
|
551
575
|
}
|
|
552
576
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
*/
|
|
558
|
-
function getCommandFileExtension(agent) {
|
|
559
|
-
// Gemini CLI uses TOML format
|
|
560
|
-
if (agent === 'gemini-cli') {
|
|
561
|
-
return '.toml';
|
|
562
|
-
}
|
|
563
|
-
// Most agents use markdown
|
|
564
|
-
return '.md';
|
|
565
|
-
}
|
|
577
|
+
// getCommandFileExtension moved to config/ai-agent-paths.js — the reconciler's
|
|
578
|
+
// scanner needs the same mapping, and a second private copy is how the writer
|
|
579
|
+
// and the reader drift apart. It now reads `commandFormat` from the agent config
|
|
580
|
+
// instead of hard-coding one agent id. (XSPEC-343 R2)
|
|
566
581
|
|
|
567
582
|
/**
|
|
568
583
|
* Install a single command to target directory
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills source directory resolution — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Two modules need to answer "where do the skill files live on this machine?", and they
|
|
5
|
+
* used to answer it separately. `github.js` had its own hardcoded copy pointing at
|
|
6
|
+
* `<cli>/../skills/claude-code`, a layout that no longer exists:
|
|
7
|
+
*
|
|
8
|
+
* - in a checkout, skills live at `<repo>/skills/<name>/`, with no `claude-code/` level
|
|
9
|
+
* - in an npm install, they are bundled at `<package>/bundled/skills/<name>/`
|
|
10
|
+
*
|
|
11
|
+
* So `hasLocalSkills()` was permanently false, every install silently fell through to the
|
|
12
|
+
* remote-download path, and that path then failed on files most skills do not have
|
|
13
|
+
* (only 5 of 61 ship a README.md), which failed the install transaction.
|
|
14
|
+
*
|
|
15
|
+
* Keeping the resolution in one module is the point: the bug was not the wrong path,
|
|
16
|
+
* it was the second copy of the path.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync } from 'fs';
|
|
20
|
+
import { dirname, join } from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
/** CLI package root — `cli/` in a checkout, the package root in an npm install. */
|
|
26
|
+
export const CLI_ROOT = join(__dirname, '..', '..');
|
|
27
|
+
|
|
28
|
+
/** Bundled assets directory, present in npm installs. */
|
|
29
|
+
export const BUNDLED_DIR = join(CLI_ROOT, 'bundled');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the skills source directory.
|
|
33
|
+
* Prefers the bundled copy (npm install), falls back to the repo layout (development).
|
|
34
|
+
* @returns {string} Absolute path to the skills source directory
|
|
35
|
+
*/
|
|
36
|
+
export function getSkillsSourceDir() {
|
|
37
|
+
const bundledPath = join(BUNDLED_DIR, 'skills');
|
|
38
|
+
if (existsSync(bundledPath)) {
|
|
39
|
+
return bundledPath;
|
|
40
|
+
}
|
|
41
|
+
// Development environment fallback: <repo>/skills
|
|
42
|
+
return join(CLI_ROOT, '..', 'skills');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether skill files are available locally, making a remote download unnecessary.
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
export function hasLocalSkillsSource() {
|
|
50
|
+
return existsSync(getSkillsSourceDir());
|
|
51
|
+
}
|
package/standards-registry.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"lastUpdated": "2026-05-13",
|
|
5
5
|
"description": "Standards registry for universal-dev-standards with integrated skills and AI-optimized formats",
|
|
6
6
|
"formats": {
|
|
@@ -58,18 +58,20 @@
|
|
|
58
58
|
"standards": {
|
|
59
59
|
"name": "universal-dev-standards",
|
|
60
60
|
"url": "https://github.com/AsiaOstrich/universal-dev-standards",
|
|
61
|
-
"version": "6.
|
|
61
|
+
"version": "6.2.0"
|
|
62
62
|
},
|
|
63
63
|
"skills": {
|
|
64
64
|
"name": "universal-dev-standards",
|
|
65
65
|
"url": "https://github.com/AsiaOstrich/universal-dev-standards",
|
|
66
66
|
"localPath": "skills",
|
|
67
67
|
"rawUrl": "https://raw.githubusercontent.com/AsiaOstrich/universal-dev-standards/main/skills",
|
|
68
|
-
"version": "6.
|
|
68
|
+
"version": "6.2.0",
|
|
69
69
|
"note": "Skills are now included in the main repository under skills/"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
"supportedAITools": {
|
|
73
|
+
"_note": "INFORMATIONAL MIRROR. No code reads this block -- the CLI's actual install behaviour is driven by cli/src/config/ai-agent-paths.js, and integrations/REGISTRY.json is the SSOT for tier and deprecation. Kept in sync by scripts/check-integration-liveness.ts so the three cannot drift apart silently again (they did, for 5.5 months, on antigravity.supportsSkills).",
|
|
74
|
+
"_noteZh": "資訊性鏡像。沒有任何程式讀取此區塊——CLI 實際的安裝行為由 cli/src/config/ai-agent-paths.js 驅動,tier 與停用狀態的 SSOT 是 integrations/REGISTRY.json。由 scripts/check-integration-liveness.ts 維持同步,避免三者再次無聲漂移(antigravity.supportsSkills 曾漂移 5.5 個月)。",
|
|
73
75
|
"claude-code": {
|
|
74
76
|
"name": "Claude Code",
|
|
75
77
|
"skillsPath": "skills",
|
|
@@ -197,8 +199,8 @@
|
|
|
197
199
|
"skillsPath": "skills",
|
|
198
200
|
"nativePaths": {
|
|
199
201
|
"skills": {
|
|
200
|
-
"project": ".
|
|
201
|
-
"user": "~/.
|
|
202
|
+
"project": ".agents/skills/",
|
|
203
|
+
"user": "~/.agents/skills/"
|
|
202
204
|
},
|
|
203
205
|
"agents": {
|
|
204
206
|
"project": ".codex/agents/",
|
|
@@ -210,7 +212,8 @@
|
|
|
210
212
|
"supportsCommands": false,
|
|
211
213
|
"supportsTask": false,
|
|
212
214
|
"supportsAgents": true,
|
|
213
|
-
"status": "partial"
|
|
215
|
+
"status": "partial",
|
|
216
|
+
"skillsPathNote": "Verified 2026-07-23 against developers.openai.com/codex/skills. Codex reads .agents/skills (plural). The previous '.codex/skills/' was a directory UDS invented and Codex never reads."
|
|
214
217
|
},
|
|
215
218
|
"copilot": {
|
|
216
219
|
"name": "GitHub Copilot",
|
|
@@ -256,6 +259,10 @@
|
|
|
256
259
|
},
|
|
257
260
|
"gemini-cli": {
|
|
258
261
|
"name": "Gemini CLI",
|
|
262
|
+
"deprecated": true,
|
|
263
|
+
"deprecationNote": "Google sunset Gemini CLI on 2026-06-18; succeeded by Antigravity CLI. The integrations/gemini-cli/ and .gemini/ trees are frozen -- see .gemini/DEPRECATED.md.",
|
|
264
|
+
"deprecationNoteZh": "Google 已於 2026-06-18 終止 Gemini CLI,由 Antigravity CLI 接手。integrations/gemini-cli/ 與 .gemini/ 兩棵樹已凍結——見 .gemini/DEPRECATED.md。",
|
|
265
|
+
"supersededBy": "antigravity",
|
|
259
266
|
"skillsPath": "skills",
|
|
260
267
|
"nativePaths": {
|
|
261
268
|
"skills": {
|
|
@@ -284,12 +291,15 @@
|
|
|
284
291
|
"antigravity": {
|
|
285
292
|
"name": "Google Antigravity",
|
|
286
293
|
"skillsPath": null,
|
|
294
|
+
"skillsPathStatus": "unverified",
|
|
295
|
+
"skillsPathNote": "Antigravity supports skills, but UDS has not verified where it reads them from. Candidates: '~/.gemini/antigravity-cli/plugins/<name>/skills/' (official plugin docs) and '.agent/skills/' (UDS's own 2026-02 spec, written in the Gemini CLI era). skillsPath stays null until one is confirmed against a real CLI -- an unverified path fails silently. Tracked as XSPEC-355 OQ6.",
|
|
296
|
+
"skillsPathNoteZh": "Antigravity 支援 skills,但 UDS 尚未驗證它實際從何處讀取。候選路徑:'~/.gemini/antigravity-cli/plugins/<name>/skills/'(官方 plugin 文件)與 '.agent/skills/'(UDS 自己 2026-02 的 spec,寫於 Gemini CLI 時代)。在對實際 CLI 確認之前 skillsPath 維持 null——路徑填錯會是靜默失敗。追蹤於 XSPEC-355 OQ6。",
|
|
287
297
|
"nativePaths": {
|
|
288
298
|
"config": {
|
|
289
299
|
"project": "INSTRUCTIONS.md"
|
|
290
300
|
}
|
|
291
301
|
},
|
|
292
|
-
"supportsSkills":
|
|
302
|
+
"supportsSkills": true,
|
|
293
303
|
"supportsCommands": false,
|
|
294
304
|
"supportsTask": false,
|
|
295
305
|
"supportsAgents": false,
|
|
@@ -2226,7 +2236,7 @@
|
|
|
2226
2236
|
"id": "license-compliance",
|
|
2227
2237
|
"name": "License Compliance Standards",
|
|
2228
2238
|
"nameZh": "授權合規標準",
|
|
2229
|
-
"version": "6.
|
|
2239
|
+
"version": "6.2.0",
|
|
2230
2240
|
"source": {
|
|
2231
2241
|
"human": "core/license-compliance.md",
|
|
2232
2242
|
"ai": "ai/standards/license-compliance.ai.yaml"
|
|
@@ -2238,7 +2248,7 @@
|
|
|
2238
2248
|
"id": "verification-oracle",
|
|
2239
2249
|
"name": "Verification Oracle Standards",
|
|
2240
2250
|
"nameZh": "驗證 Oracle 標準",
|
|
2241
|
-
"version": "6.
|
|
2251
|
+
"version": "6.2.0",
|
|
2242
2252
|
"source": {
|
|
2243
2253
|
"human": "core/verification-oracle.md",
|
|
2244
2254
|
"ai": "ai/standards/verification-oracle.ai.yaml"
|
|
@@ -2250,7 +2260,7 @@
|
|
|
2250
2260
|
"id": "model-provenance",
|
|
2251
2261
|
"name": "Model Provenance Policy Standards",
|
|
2252
2262
|
"nameZh": "模型來源政策標準",
|
|
2253
|
-
"version": "6.
|
|
2263
|
+
"version": "6.2.0",
|
|
2254
2264
|
"source": {
|
|
2255
2265
|
"human": "core/model-provenance.md",
|
|
2256
2266
|
"ai": "ai/standards/model-provenance.ai.yaml"
|
|
@@ -2262,7 +2272,7 @@
|
|
|
2262
2272
|
"id": "resource-cost-boundary",
|
|
2263
2273
|
"name": "Resource / Cost Boundary Declaration Standards",
|
|
2264
2274
|
"nameZh": "資源/成本邊界宣告標準",
|
|
2265
|
-
"version": "6.
|
|
2275
|
+
"version": "6.2.0",
|
|
2266
2276
|
"source": {
|
|
2267
2277
|
"human": "core/resource-cost-boundary.md",
|
|
2268
2278
|
"ai": "ai/standards/resource-cost-boundary.ai.yaml"
|