universal-dev-standards 6.14.0-beta.3 → 6.14.0-beta.4
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/ai/standards/ai-response-navigation.ai.yaml +43 -3
- package/bundled/ai/standards/checkin-standards.ai.yaml +25 -6
- package/bundled/ai/standards/pipeline-security-gates.ai.yaml +5 -1
- package/bundled/core/ai-response-navigation.md +128 -12
- package/bundled/extensions/frameworks/fat-free-patterns.md +937 -0
- package/bundled/extensions/languages/csharp-style.md +464 -0
- package/bundled/extensions/languages/php/fat-free-patterns.md +915 -0
- package/bundled/extensions/languages/php/php-style.md +693 -0
- package/bundled/extensions/languages/php-style.md +700 -0
- package/bundled/extensions/locales/zh-cn.md +717 -0
- package/bundled/extensions/locales/zh-tw.md +717 -0
- package/bundled/locales/COVERAGE.md +5 -4
- package/bundled/locales/zh-CN/CHANGELOG.md +27 -3
- package/bundled/locales/zh-CN/README.md +2 -2
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/ai-response-navigation.md +110 -12
- package/bundled/locales/zh-CN/skills/README.md +1 -0
- package/bundled/locales/zh-CN/skills/comprehension-ladder/SKILL.md +289 -0
- package/bundled/locales/zh-CN/skills/comprehension-ladder/eval-cases.md +261 -0
- package/bundled/locales/zh-TW/CHANGELOG.md +27 -3
- package/bundled/locales/zh-TW/README.md +2 -2
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/ai-response-navigation.md +110 -12
- package/bundled/locales/zh-TW/skills/README.md +1 -0
- package/bundled/locales/zh-TW/skills/comprehension-ladder/SKILL.md +289 -0
- package/bundled/locales/zh-TW/skills/comprehension-ladder/eval-cases.md +261 -0
- package/bundled/skills/README.md +1 -0
- package/bundled/skills/comprehension-ladder/SKILL.md +283 -0
- package/bundled/skills/comprehension-ladder/eval-cases.md +255 -0
- package/package.json +2 -2
- package/src/commands/init.js +29 -9
- package/src/commands/update.js +3 -2
- package/src/installers/standards-installer.js +16 -23
- package/src/reconciler/plan-executor.js +10 -11
- package/src/uninstallers/hook-uninstaller.js +5 -3
- package/src/utils/copier.js +57 -0
- package/src/utils/git-hooks.js +8 -4
- package/src/utils/locale.js +19 -0
- package/standards-registry.json +21 -7
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getOptionSource,
|
|
8
8
|
findOption
|
|
9
9
|
} from '../utils/registry.js';
|
|
10
|
-
import { copyStandard } from '../utils/copier.js';
|
|
10
|
+
import { copyStandard, copyExtension } from '../utils/copier.js';
|
|
11
11
|
import { t } from '../i18n/messages.js';
|
|
12
12
|
import { computeFileHash } from '../utils/hasher.js';
|
|
13
13
|
import { MANIFEST_OPTION_BINDINGS } from '../core/constants.js';
|
|
@@ -115,36 +115,29 @@ export async function installStandards(config, projectPath) {
|
|
|
115
115
|
if (config.languages.length > 0 || config.frameworks.length > 0 || localeExtension) {
|
|
116
116
|
const extSpinner = createSpinner(msg.copyingExtensions).start();
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
// One helper, one copy call. Extension files come from the installed package only:
|
|
119
|
+
// a declared extension that is missing from it is an error naming that file, never a
|
|
120
|
+
// download and never a silent skip (XSPEC-452 R2).
|
|
121
|
+
const installExtension = async (sourcePath) => {
|
|
122
|
+
const result = await copyExtension(sourcePath, '.standards', projectPath);
|
|
123
|
+
if (result.success) {
|
|
124
|
+
results.extensions.push(sourcePath);
|
|
125
|
+
} else {
|
|
126
|
+
results.errors.push(`${sourcePath}: ${result.error}`);
|
|
126
127
|
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
for (const lang of config.languages) {
|
|
131
|
+
if (EXTENSION_MAPPINGS[lang]) await installExtension(EXTENSION_MAPPINGS[lang]);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
for (const fw of config.frameworks) {
|
|
130
|
-
if (EXTENSION_MAPPINGS[fw])
|
|
131
|
-
const result = await copyStandard(EXTENSION_MAPPINGS[fw], '.standards', projectPath);
|
|
132
|
-
if (result.success) {
|
|
133
|
-
results.extensions.push(EXTENSION_MAPPINGS[fw]);
|
|
134
|
-
} else {
|
|
135
|
-
results.errors.push(`${EXTENSION_MAPPINGS[fw]}: ${result.error}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
135
|
+
if (EXTENSION_MAPPINGS[fw]) await installExtension(EXTENSION_MAPPINGS[fw]);
|
|
138
136
|
}
|
|
139
137
|
|
|
140
138
|
// Auto-install locale extension based on display language
|
|
141
139
|
if (localeExtension && EXTENSION_MAPPINGS[localeExtension]) {
|
|
142
|
-
|
|
143
|
-
if (result.success) {
|
|
144
|
-
results.extensions.push(EXTENSION_MAPPINGS[localeExtension]);
|
|
145
|
-
} else {
|
|
146
|
-
results.errors.push(`${EXTENSION_MAPPINGS[localeExtension]}: ${result.error}`);
|
|
147
|
-
}
|
|
140
|
+
await installExtension(EXTENSION_MAPPINGS[localeExtension]);
|
|
148
141
|
}
|
|
149
142
|
|
|
150
143
|
extSpinner.succeed(msg.copiedExtensions.replace('{count}', results.extensions.length));
|
|
@@ -249,18 +249,17 @@ async function executeCreateOrUpdate(projectPath, action, manifest) {
|
|
|
249
249
|
let targetDir = '.standards';
|
|
250
250
|
|
|
251
251
|
if (metadata?.extensionSource) {
|
|
252
|
-
// An entry from `manifest.extensions`.
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
// source checkout, and this function answered "No source path available"
|
|
257
|
-
// for a file it was perfectly able to fetch.
|
|
252
|
+
// An entry from `manifest.extensions`. It is resolved from the installed
|
|
253
|
+
// package (`bundled/extensions/`, shipped since XSPEC-452) — never from GitHub.
|
|
254
|
+
// `copyStandard` hands any `extensions/` path to `copyExtension`, which fails
|
|
255
|
+
// by name when the package lacks the file, instead of downloading it.
|
|
258
256
|
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
257
|
+
// History: before XSPEC-452 the published package had no `extensions/`, so
|
|
258
|
+
// `sourcePath` was null for every adopter who did not install from a source
|
|
259
|
+
// checkout, and this function answered "No source path available" for a file it
|
|
260
|
+
// could fetch from GitHub. The legacy update path (`update.js`, "Update
|
|
261
|
+
// extensions") was the one that worked — only because it fell back to a
|
|
262
|
+
// download. (XSPEC-343)
|
|
264
263
|
sourceStr = metadata.extensionSource;
|
|
265
264
|
} else if (metadata?.registryEntry) {
|
|
266
265
|
const source = metadata.registryEntry.source;
|
|
@@ -426,11 +426,13 @@ export function uninstallHook(projectPath, options = {}) {
|
|
|
426
426
|
if (!dryRun) unlinkSync(nativeHookPath);
|
|
427
427
|
result.removed.push(`.git/hooks/pre-commit (UDS native hook, file removed — ${proof.why})`);
|
|
428
428
|
result.deletedPaths.push('.git/hooks/pre-commit');
|
|
429
|
-
} else if (!NATIVE_UDS_LINE.test(content)) {
|
|
429
|
+
} else if (!NATIVE_UDS_LINE.test(content) && !stripUdsHookBlock(content).removed) {
|
|
430
430
|
result.skipped.push('.git/hooks/pre-commit (no UDS lines found)');
|
|
431
431
|
} else {
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
// The block the native hook carries is removed whole, by its markers — its
|
|
433
|
+
// inner lines are not individually matchable (same as the husky path above).
|
|
434
|
+
const filtered = stripUdsHookBlock(content).content.split('\n')
|
|
435
|
+
.filter(line => !NATIVE_UDS_LINE.test(line));
|
|
434
436
|
if (nothingButShebang(filtered)) {
|
|
435
437
|
if (!dryRun) unlinkSync(nativeHookPath);
|
|
436
438
|
result.removed.push('.git/hooks/pre-commit (UDS native hook, file removed)');
|
package/src/utils/copier.js
CHANGED
|
@@ -26,15 +26,72 @@ function getSourcePath(sourcePath) {
|
|
|
26
26
|
return PathResolver.getStandardSource(sourcePath);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Is this source path one of the add-on files under `extensions/`?
|
|
31
|
+
* @param {string} sourcePath - Relative path from repo root
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export function isExtensionPath(sourcePath) {
|
|
35
|
+
return typeof sourcePath === 'string' && sourcePath.replace(/\\/g, '/').startsWith('extensions/');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Copy an extension file (language style guide, framework pattern, locale pack)
|
|
40
|
+
* into the target project — from the installed package ONLY.
|
|
41
|
+
*
|
|
42
|
+
* 🔴 There is deliberately no download fallback here (XSPEC-452 R2). `extensions/`
|
|
43
|
+
* used to be missing from the npm package, so the copy below fell through to
|
|
44
|
+
* `raw.githubusercontent.com/.../main`: offline installs failed, an install pulled
|
|
45
|
+
* whatever `main` held that day instead of the version the adopter had installed,
|
|
46
|
+
* and a file that did not exist anywhere (`extensions/locales/zh-cn.md`, XSPEC-451)
|
|
47
|
+
* hid behind that fallback until someone ran the locale. A declared extension that
|
|
48
|
+
* is not in the package is a packaging defect, so it fails — by name.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} sourcePath - Relative path from repo root (e.g., 'extensions/locales/zh-tw.md')
|
|
51
|
+
* @param {string} targetDir - Target directory (usually '.standards')
|
|
52
|
+
* @param {string} projectPath - Project root path
|
|
53
|
+
* @returns {Promise<Object>} Result with success status and copied path
|
|
54
|
+
*/
|
|
55
|
+
export async function copyExtension(sourcePath, targetDir, projectPath) {
|
|
56
|
+
try {
|
|
57
|
+
const source = getSourcePath(sourcePath);
|
|
58
|
+
if (!source) {
|
|
59
|
+
return failure(
|
|
60
|
+
`Extension file not found in the installed package: ${sourcePath} (extension files are never downloaded; reinstall or upgrade universal-dev-standards)`,
|
|
61
|
+
ERROR_CODES.FILE_NOT_FOUND,
|
|
62
|
+
{ sourcePath, targetDir }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const targetFolder = join(projectPath, targetDir);
|
|
66
|
+
const targetFile = join(targetFolder, basename(sourcePath));
|
|
67
|
+
if (!existsSync(targetFolder)) {
|
|
68
|
+
mkdirSync(targetFolder, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
copyFileSync(source, targetFile);
|
|
71
|
+
return success(targetFile, { source: 'local', sourcePath, targetFile });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return failure(
|
|
74
|
+
error.message,
|
|
75
|
+
ERROR_CODES.FILE_COPY_FAILED,
|
|
76
|
+
{ sourcePath, targetDir, projectPath }
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
29
81
|
/**
|
|
30
82
|
* Copy a standard file to target project
|
|
31
83
|
* Falls back to downloading from GitHub if local file not found
|
|
84
|
+
* (except for `extensions/` files, which are package-only — see copyExtension)
|
|
32
85
|
* @param {string} sourcePath - Relative path from repo root (e.g., 'core/anti-hallucination.md')
|
|
33
86
|
* @param {string} targetDir - Target directory (usually '.standards')
|
|
34
87
|
* @param {string} projectPath - Project root path
|
|
35
88
|
* @returns {Promise<Object>} Result with success status and copied path
|
|
36
89
|
*/
|
|
37
90
|
export async function copyStandard(sourcePath, targetDir, projectPath) {
|
|
91
|
+
// Extensions never take the GitHub fallback below, whichever caller got here (XSPEC-452 R2).
|
|
92
|
+
if (isExtensionPath(sourcePath)) {
|
|
93
|
+
return copyExtension(sourcePath, targetDir, projectPath);
|
|
94
|
+
}
|
|
38
95
|
try {
|
|
39
96
|
const targetFolder = join(projectPath, targetDir);
|
|
40
97
|
const targetFile = join(targetFolder, basename(sourcePath));
|
package/src/utils/git-hooks.js
CHANGED
|
@@ -189,11 +189,15 @@ export function hookRunsUdsCheck(content) {
|
|
|
189
189
|
* - Not installed: say what is missing and how to fix it, exit non-zero. It
|
|
190
190
|
* neither skips the check nor downloads anything.
|
|
191
191
|
*
|
|
192
|
-
* @param {{args?: string}} [opts]
|
|
193
|
-
*
|
|
192
|
+
* @param {{args?: string, hookFile?: string}} [opts]
|
|
193
|
+
* args — extra arguments to keep on the check command
|
|
194
|
+
* (e.g. `--standard checkin-standards`, which older UDS versions wrote).
|
|
195
|
+
* hookFile — the file the block is written into, named in the "blocked" message so
|
|
196
|
+
* it points at the file the adopter actually has (default `.husky/pre-commit`;
|
|
197
|
+
* the native hook passes `.git/hooks/pre-commit`).
|
|
194
198
|
* @returns {string} the block, LF line endings, ending with a newline
|
|
195
199
|
*/
|
|
196
|
-
export function buildPreCommitBlock({ args = '' } = {}) {
|
|
200
|
+
export function buildPreCommitBlock({ args = '', hookFile = '.husky/pre-commit' } = {}) {
|
|
197
201
|
const cmd = args ? `${UDS_BIN_NAME} check ${args}` : `${UDS_BIN_NAME} check`;
|
|
198
202
|
return [
|
|
199
203
|
UDS_HOOK_MARKER,
|
|
@@ -208,7 +212,7 @@ export function buildPreCommitBlock({ args = '' } = {}) {
|
|
|
208
212
|
' fi',
|
|
209
213
|
` echo "[UDS] Pre-commit check cannot run: the UDS CLI (${UDS_BIN_NAME}) is not installed." >&2`,
|
|
210
214
|
` echo "[UDS] Install it: npm install --save-dev ${UDS_BIN_NAME} (or: npm install -g ${UDS_BIN_NAME})" >&2`,
|
|
211
|
-
|
|
215
|
+
` echo "[UDS] This commit is blocked until it is installed, or until you remove this block from ${hookFile}." >&2`,
|
|
212
216
|
' exit 1',
|
|
213
217
|
') || exit $?',
|
|
214
218
|
UDS_HOOK_END_MARKER,
|
package/src/utils/locale.js
CHANGED
|
@@ -32,6 +32,24 @@ export function displayLanguageToLocale(displayLanguage) {
|
|
|
32
32
|
return LOCALE_MAP[displayLanguage.toLowerCase()] || 'en';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Normalise a requested display language and say whether it is supported.
|
|
37
|
+
*
|
|
38
|
+
* `uds init --locale zh-CN` used to install English and exit 0: the value was compared case-sensitively
|
|
39
|
+
* further down, so the upper-case spelling silently missed every Chinese branch. An unsupported value such
|
|
40
|
+
* as `fr` did the same — English, exit 0, nothing said. The caller still falls back to English for an
|
|
41
|
+
* unsupported value, but now it can say so.
|
|
42
|
+
* @param {string|undefined|null} requested - what the user, install.yaml or the environment asked for
|
|
43
|
+
* @returns {{ displayLanguage: string|undefined|null, supported: boolean }}
|
|
44
|
+
*/
|
|
45
|
+
export function normalizeRequestedLocale(requested) {
|
|
46
|
+
if (typeof requested !== 'string' || requested.trim() === '') {
|
|
47
|
+
return { displayLanguage: requested, supported: true };
|
|
48
|
+
}
|
|
49
|
+
const displayLanguage = requested.trim().toLowerCase();
|
|
50
|
+
return { displayLanguage, supported: Object.prototype.hasOwnProperty.call(LOCALE_MAP, displayLanguage) };
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
/**
|
|
36
54
|
* Check if a locale requires localized skill installation.
|
|
37
55
|
* English locale uses the default source, no localization needed.
|
|
@@ -67,6 +85,7 @@ export function detectLocaleFromStandards(projectPath) {
|
|
|
67
85
|
|
|
68
86
|
export default {
|
|
69
87
|
displayLanguageToLocale,
|
|
88
|
+
normalizeRequestedLocale,
|
|
70
89
|
isLocalizedLocale,
|
|
71
90
|
detectLocaleFromStandards,
|
|
72
91
|
LOCALE_MAP
|
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.14.0-beta.
|
|
3
|
+
"version": "6.14.0-beta.4",
|
|
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.14.0-beta.
|
|
61
|
+
"version": "6.14.0-beta.4"
|
|
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.14.0-beta.
|
|
68
|
+
"version": "6.14.0-beta.4",
|
|
69
69
|
"note": "Skills are now included in the main repository under skills/"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
@@ -382,6 +382,10 @@
|
|
|
382
382
|
"skills/brainstorm-assistant/SKILL.md",
|
|
383
383
|
"skills/brainstorm-assistant/guide.md"
|
|
384
384
|
],
|
|
385
|
+
"comprehension-ladder": [
|
|
386
|
+
"skills/comprehension-ladder/SKILL.md",
|
|
387
|
+
"skills/comprehension-ladder/eval-cases.md"
|
|
388
|
+
],
|
|
385
389
|
"bdd-assistant": [
|
|
386
390
|
"skills/bdd-assistant/SKILL.md",
|
|
387
391
|
"skills/bdd-assistant/guide.md",
|
|
@@ -1536,6 +1540,16 @@
|
|
|
1536
1540
|
"description": "Traditional Chinese (Taiwan) localization for all standards",
|
|
1537
1541
|
"applicability": "Traditional Chinese speaking teams"
|
|
1538
1542
|
},
|
|
1543
|
+
{
|
|
1544
|
+
"id": "zh-cn-locale",
|
|
1545
|
+
"name": "Simplified Chinese Locale",
|
|
1546
|
+
"nameZh": "简体中文本地化",
|
|
1547
|
+
"source": "extensions/locales/zh-cn.md",
|
|
1548
|
+
"category": "extension",
|
|
1549
|
+
"skillName": null,
|
|
1550
|
+
"description": "Simplified Chinese (Mainland China) localization for all standards",
|
|
1551
|
+
"applicability": "Simplified Chinese speaking teams"
|
|
1552
|
+
},
|
|
1539
1553
|
{
|
|
1540
1554
|
"id": "copilot-integration",
|
|
1541
1555
|
"name": "GitHub Copilot Instructions",
|
|
@@ -2295,7 +2309,7 @@
|
|
|
2295
2309
|
"id": "license-compliance",
|
|
2296
2310
|
"name": "License Compliance Standards",
|
|
2297
2311
|
"nameZh": "授權合規標準",
|
|
2298
|
-
"version": "6.14.0-beta.
|
|
2312
|
+
"version": "6.14.0-beta.4",
|
|
2299
2313
|
"source": {
|
|
2300
2314
|
"human": "core/license-compliance.md",
|
|
2301
2315
|
"ai": "ai/standards/license-compliance.ai.yaml"
|
|
@@ -2307,7 +2321,7 @@
|
|
|
2307
2321
|
"id": "verification-oracle",
|
|
2308
2322
|
"name": "Verification Oracle Standards",
|
|
2309
2323
|
"nameZh": "驗證 Oracle 標準",
|
|
2310
|
-
"version": "6.14.0-beta.
|
|
2324
|
+
"version": "6.14.0-beta.4",
|
|
2311
2325
|
"source": {
|
|
2312
2326
|
"human": "core/verification-oracle.md",
|
|
2313
2327
|
"ai": "ai/standards/verification-oracle.ai.yaml"
|
|
@@ -2319,7 +2333,7 @@
|
|
|
2319
2333
|
"id": "model-provenance",
|
|
2320
2334
|
"name": "Model Provenance Policy Standards",
|
|
2321
2335
|
"nameZh": "模型來源政策標準",
|
|
2322
|
-
"version": "6.14.0-beta.
|
|
2336
|
+
"version": "6.14.0-beta.4",
|
|
2323
2337
|
"source": {
|
|
2324
2338
|
"human": "core/model-provenance.md",
|
|
2325
2339
|
"ai": "ai/standards/model-provenance.ai.yaml"
|
|
@@ -2331,7 +2345,7 @@
|
|
|
2331
2345
|
"id": "resource-cost-boundary",
|
|
2332
2346
|
"name": "Resource / Cost Boundary Declaration Standards",
|
|
2333
2347
|
"nameZh": "資源/成本邊界宣告標準",
|
|
2334
|
-
"version": "6.14.0-beta.
|
|
2348
|
+
"version": "6.14.0-beta.4",
|
|
2335
2349
|
"source": {
|
|
2336
2350
|
"human": "core/resource-cost-boundary.md",
|
|
2337
2351
|
"ai": "ai/standards/resource-cost-boundary.ai.yaml"
|