universal-dev-standards 6.7.3 → 6.7.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/locales/zh-CN/CHANGELOG.md +11 -2
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/CHANGELOG.md +11 -2
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/package.json +1 -1
- package/src/reconciler/actual-state-scanner.js +19 -2
- package/src/reconciler/desired-state-calculator.js +10 -2
- package/src/reconciler/diff-engine.js +11 -1
- package/src/reconciler/index.js +19 -6
- package/src/utils/skills-installer.js +111 -29
- package/standards-registry.json +7 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../CHANGELOG.md
|
|
3
|
-
source_version: 6.7.
|
|
4
|
-
translation_version: 6.7.
|
|
3
|
+
source_version: 6.7.4
|
|
4
|
+
translation_version: 6.7.4
|
|
5
5
|
last_synced: 2026-08-18
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
@@ -17,6 +17,15 @@ status: current
|
|
|
17
17
|
|
|
18
18
|
## [Unreleased]
|
|
19
19
|
|
|
20
|
+
## [6.7.4] - 2026-08-18
|
|
21
|
+
|
|
22
|
+
### 修正
|
|
23
|
+
|
|
24
|
+
- **6.7.3 让 `uds check` 每个 repo 报出 52 个不存在的「缺失」文件。** 6.7.3 的两个改动要**同时在场**才会互相作用:R1 给了 skill 内容哈希,使它们第一次走进 `unchanged` 分支;而 R6b 会把那个分支的条目收进 `manifest.fileHashes`。那张表是用 `isFile()` 验证的,而 skill 是目录,于是每一个都被报成缺失。**没有任何东西被删除——错的是记录不是磁盘**(已验证:59 个 skill 目录与其内容完好)。`verifiedPristine` 现在限缩于文件类别;而且**修好写入端不会让已经写进去的东西消失**,所以 `uds update` 也会清掉 `fileHashes` 里的 `.claude/skills/` 与 `.claude/commands/` 键——**一个坏掉的键不会有第二次机会被访问**,因为磁盘上没有任何东西对应它。
|
|
25
|
+
- **commands 也改为内容比对(XSPEC-382 R7)。** R1 只涵盖 skills,把 commands 留在 `hash: null`,于是无条件重装的分支对它们仍然活着。与 skills 同一个形状:`resolveCommandContent(name, agent, locale)` 说明一次安装会包含什么(locale 选择+英文回退,加上逐 agent 的转换),安装器写它、计划器哈希它。
|
|
26
|
+
- **装了 locale 版的人,拿到的 SKILL.md 指向从来不会被安装的文件。** locale 包不是英文来源的完整副本——实测 **zh-TW 59 个中有 4 个、zh-CN 59 个中有 5 个**缺少英文有出货的文件——而安装器把 locale 目录整包换上去,于是那些伴随文件根本没被写入。其中两个缺口**是被引用的**:zh-TW 的 `dev-workflow-guide/SKILL.md` 引用 `workflow-phases.md` 三次、`testing-guide/SKILL.md` 引用 `test-skeleton-templates.md` 三次。**回退改为逐文件而非逐 skill**——locale 有就用 locale 的,没有就用英文的。
|
|
27
|
+
- **UDS 不出货的文件,不再让一个 skill 永远停在「已变更」。** 安装一个 skill 现在会移除其目录下**解析结果没有指名**的顶层文件(实地发现:`deploy-assistant/guide.md`,它不在任何 UDS skills 树里,而且从来就不在)。范围由同一个 provenance 判准界定,也就是让采用者自己的 skill 目录不进入此处任何路径的那一个——**已验证手写的 skill 目录与其额外文件在 `--force` 之后完好无损**——子目录一律不动,因为安装器从来没有写过它们。
|
|
28
|
+
|
|
20
29
|
## [6.7.3] - 2026-08-18
|
|
21
30
|
|
|
22
31
|
### 修正
|
|
@@ -15,7 +15,7 @@ status: current
|
|
|
15
15
|
|
|
16
16
|
> **语言**: [English](../../README.md) | [繁體中文](../zh-TW/README.md) | 简体中文
|
|
17
17
|
|
|
18
|
-
**版本**: 6.7.
|
|
18
|
+
**版本**: 6.7.4 | **发布日期**: 2026-08-18 | **授权**: [双重授权](../../LICENSE) (CC BY 4.0 + MIT)
|
|
19
19
|
|
|
20
20
|
语言无关、框架无关的软件项目文档标准。通过 AI 原生工作流,确保不同技术栈之间的一致性、质量和可维护性。
|
|
21
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../CHANGELOG.md
|
|
3
|
-
source_version: 6.7.
|
|
4
|
-
translation_version: 6.7.
|
|
3
|
+
source_version: 6.7.4
|
|
4
|
+
translation_version: 6.7.4
|
|
5
5
|
last_synced: 2026-08-18
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
@@ -17,6 +17,15 @@ status: current
|
|
|
17
17
|
|
|
18
18
|
## [Unreleased]
|
|
19
19
|
|
|
20
|
+
## [6.7.4] - 2026-08-18
|
|
21
|
+
|
|
22
|
+
### 修正
|
|
23
|
+
|
|
24
|
+
- **6.7.3 讓 `uds check` 每個 repo 報出 52 個不存在的「遺失」檔案。** 6.7.3 的兩個改動要**同時在場**才會互相作用:R1 給了 skill 內容雜湊,使它們第一次走進 `unchanged` 分支;而 R6b 會把那個分支的項目收進 `manifest.fileHashes`。那張表是用 `isFile()` 驗證的,而 skill 是目錄,於是每一個都被報成遺失。**沒有任何東西被刪除——錯的是紀錄不是磁碟**(已驗證:59 個 skill 目錄與其內容完好)。`verifiedPristine` 現在限縮於檔案類別;而且**修好寫入端不會讓已經寫進去的東西消失**,所以 `uds update` 也會清掉 `fileHashes` 裡的 `.claude/skills/` 與 `.claude/commands/` 鍵——**一個壞掉的鍵不會有第二次機會被造訪**,因為磁碟上沒有任何東西對應它。
|
|
25
|
+
- **commands 也改為內容比對(XSPEC-382 R7)。** R1 只涵蓋 skills,把 commands 留在 `hash: null`,於是無條件重裝的分支對它們仍然活著。與 skills 同一個形狀:`resolveCommandContent(name, agent, locale)` 說明一次安裝會包含什麼(locale 選擇+英文回退,加上逐 agent 的轉換),安裝器寫它、計畫器雜湊它。
|
|
26
|
+
- **裝了 locale 版的人,拿到的 SKILL.md 指向從來不會被安裝的檔案。** locale 包不是英文來源的完整複本——實測 **zh-TW 59 個中有 4 個、zh-CN 59 個中有 5 個**缺少英文有出貨的檔——而安裝器把 locale 目錄整包換上去,於是那些伴隨檔根本沒被寫入。其中兩個缺口**是被引用的**:zh-TW 的 `dev-workflow-guide/SKILL.md` 引用 `workflow-phases.md` 三次、`testing-guide/SKILL.md` 引用 `test-skeleton-templates.md` 三次。**回退改為逐檔而非逐 skill**——locale 有就用 locale 的,沒有就用英文的。
|
|
27
|
+
- **UDS 不出貨的檔案,不再讓一個 skill 永遠停在「已變更」。** 安裝一個 skill 現在會移除其目錄下**解析結果沒有指名**的頂層檔案(實地發現:`deploy-assistant/guide.md`,它不在任何 UDS skills 樹裡,而且從來就不在)。範圍由同一個 provenance 判準界定,也就是讓採用者自己的 skill 目錄不進入此處任何路徑的那一個——**已驗證手寫的 skill 目錄與其額外檔案在 `--force` 之後完好無損**——子目錄一律不動,因為安裝器從來沒有寫過它們。
|
|
28
|
+
|
|
20
29
|
## [6.7.3] - 2026-08-18
|
|
21
30
|
|
|
22
31
|
### 修正
|
|
@@ -15,7 +15,7 @@ status: current
|
|
|
15
15
|
|
|
16
16
|
> **語言**: [English](../../README.md) | 繁體中文 | [简体中文](../zh-CN/README.md)
|
|
17
17
|
|
|
18
|
-
**版本**: 6.7.
|
|
18
|
+
**版本**: 6.7.4 | **發布日期**: 2026-08-18 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
|
|
19
19
|
|
|
20
20
|
語言無關、框架無關的軟體專案文件標準。透過 AI 原生工作流,確保不同技術堆疊之間的一致性、品質和可維護性。
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -434,9 +434,26 @@ function scanCommands(state, projectPath, manifest) {
|
|
|
434
434
|
? getRelativePath(projectPath, join(cmdsDir, entry.name))
|
|
435
435
|
: join(cmdsDir, entry.name);
|
|
436
436
|
|
|
437
|
+
// Content hash of the installed file, for UDS-managed commands only —
|
|
438
|
+
// same reasoning as skills: an adopter's own command has no desired
|
|
439
|
+
// counterpart to compare against. (XSPEC-382 R7)
|
|
440
|
+
const cmdUdsManaged = shippedCommandNames().has(cmdName);
|
|
441
|
+
let cmdHash = null;
|
|
442
|
+
if (cmdUdsManaged) {
|
|
443
|
+
try {
|
|
444
|
+
cmdHash = `sha256:${createHash('sha256')
|
|
445
|
+
.update(readFileSync(join(cmdsDir, entry.name), 'utf-8'))
|
|
446
|
+
.digest('hex')}`;
|
|
447
|
+
} catch {
|
|
448
|
+
// Unreadable → no hash. A partial answer would claim a match that
|
|
449
|
+
// was never checked.
|
|
450
|
+
cmdHash = null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
437
454
|
state.commands.set(key, {
|
|
438
455
|
relativePath: relPath,
|
|
439
|
-
hash:
|
|
456
|
+
hash: cmdHash,
|
|
440
457
|
size: null,
|
|
441
458
|
category: 'command',
|
|
442
459
|
sourcePath: null,
|
|
@@ -445,7 +462,7 @@ function scanCommands(state, projectPath, manifest) {
|
|
|
445
462
|
level,
|
|
446
463
|
commandName: cmdName,
|
|
447
464
|
scanned: true,
|
|
448
|
-
udsManaged:
|
|
465
|
+
udsManaged: cmdUdsManaged
|
|
449
466
|
}
|
|
450
467
|
});
|
|
451
468
|
}
|
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
getAvailableSkillNames,
|
|
27
27
|
getAvailableCommandNames,
|
|
28
28
|
resolveSkillFiles,
|
|
29
|
-
computeSkillContentHash
|
|
29
|
+
computeSkillContentHash,
|
|
30
|
+
resolveCommandContent,
|
|
31
|
+
computeCommandContentHash
|
|
30
32
|
} from '../utils/skills-installer.js';
|
|
31
33
|
import { MARKETPLACE_NAMES_SENTINEL } from '../core/manifest.js';
|
|
32
34
|
|
|
@@ -379,6 +381,9 @@ function calculateSkills(state, projectPath, manifest) {
|
|
|
379
381
|
function calculateCommands(state, projectPath, manifest) {
|
|
380
382
|
const commands = manifest.commands;
|
|
381
383
|
if (!commands || !commands.installed) return;
|
|
384
|
+
// Commands are installed alongside skills and share their locale; fall back
|
|
385
|
+
// to the skills locale, then English.
|
|
386
|
+
const commandsLocale = commands.locale || manifest.skills?.locale || 'en';
|
|
382
387
|
|
|
383
388
|
const commandNames = getAvailableCommandNames();
|
|
384
389
|
if (commandNames.length === 0) {
|
|
@@ -404,9 +409,12 @@ function calculateCommands(state, projectPath, manifest) {
|
|
|
404
409
|
: null;
|
|
405
410
|
|
|
406
411
|
if (relativeBase) {
|
|
412
|
+
// Content hash of what installing this command WOULD produce, from the
|
|
413
|
+
// same function the installer writes from. (XSPEC-382 R7)
|
|
414
|
+
const resolvedCmd = resolveCommandContent(commandName, agent, commandsLocale);
|
|
407
415
|
state.commands.set(`command:${agent}:${level}:${commandName}`, {
|
|
408
416
|
relativePath: relativeBase,
|
|
409
|
-
hash: null,
|
|
417
|
+
hash: resolvedCmd.error ? null : computeCommandContentHash(resolvedCmd.content),
|
|
410
418
|
size: null,
|
|
411
419
|
category: 'command',
|
|
412
420
|
sourcePath: null,
|
|
@@ -182,7 +182,17 @@ function diffCategory(desiredMap, actualMap, category, actions, warnings, summar
|
|
|
182
182
|
// reverted `fileHashes` along with `upstream.version`). Recording it here
|
|
183
183
|
// is safe precisely because we got here by proving disk === desired.
|
|
184
184
|
summary.unchanged++;
|
|
185
|
-
|
|
185
|
+
// Only file-backed categories. `manifest.fileHashes` is a FILE map that
|
|
186
|
+
// `uds check` validates with a `isFile()` test; skills are directories and
|
|
187
|
+
// commands live in `commandHashes`, so recording either there makes the
|
|
188
|
+
// integrity check report them as missing.
|
|
189
|
+
//
|
|
190
|
+
// This branch was unreachable for skills until R1 gave them hashes, so
|
|
191
|
+
// the two changes shipped together and the fault only appeared once both
|
|
192
|
+
// were in: 6.7.3 put 52 skill directory paths into `fileHashes` in every
|
|
193
|
+
// adopter repo and `uds check` reported 52 phantom missing files. Nothing
|
|
194
|
+
// was deleted — the record was wrong, not the disk. (XSPEC-382 R7)
|
|
195
|
+
if (actualEntry.hash && (category === 'standard' || category === 'option')) {
|
|
186
196
|
verifiedPristine.push({
|
|
187
197
|
path: desiredEntry.relativePath,
|
|
188
198
|
hash: actualEntry.hash,
|
package/src/reconciler/index.js
CHANGED
|
@@ -59,15 +59,28 @@ import { rollback } from './backup-manager.js';
|
|
|
59
59
|
* is the one thing that check exists to do.
|
|
60
60
|
*/
|
|
61
61
|
function reconcileFileHashes(manifest, verifiedPristine) {
|
|
62
|
-
if (!verifiedPristine?.length) return null;
|
|
63
|
-
|
|
64
62
|
const current = manifest.fileHashes || {};
|
|
63
|
+
|
|
64
|
+
// Drop entries 6.7.3 wrote here that do not belong: skill directories and
|
|
65
|
+
// command files are tracked in `skillHashes` / `commandHashes`, and
|
|
66
|
+
// `uds check` validates everything in `fileHashes` with an `isFile()` test,
|
|
67
|
+
// so those entries surfaced as phantom "missing" reports — 52 of them per
|
|
68
|
+
// adopter repo. Fixing the writer does not remove what it already wrote, and
|
|
69
|
+
// there is no other path that would: a bad key is never revisited because
|
|
70
|
+
// nothing on disk corresponds to it. (XSPEC-382 R7)
|
|
71
|
+
const stale = Object.keys(current).filter(
|
|
72
|
+
(k) => k.startsWith('.claude/skills/') || k.startsWith('.claude/commands/')
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (!verifiedPristine?.length && stale.length === 0) return null;
|
|
76
|
+
|
|
77
|
+
const kept = Object.fromEntries(Object.entries(current).filter(([k]) => !stale.includes(k)));
|
|
65
78
|
const corrected = {};
|
|
66
|
-
let count =
|
|
79
|
+
let count = stale.length;
|
|
67
80
|
|
|
68
|
-
for (const entry of verifiedPristine) {
|
|
81
|
+
for (const entry of verifiedPristine || []) {
|
|
69
82
|
const key = entry.path.replace(/\\/g, '/');
|
|
70
|
-
const recorded =
|
|
83
|
+
const recorded = kept[key];
|
|
71
84
|
if (recorded && recorded.hash === entry.hash && recorded.size === entry.size) continue;
|
|
72
85
|
corrected[key] = {
|
|
73
86
|
hash: entry.hash,
|
|
@@ -79,7 +92,7 @@ function reconcileFileHashes(manifest, verifiedPristine) {
|
|
|
79
92
|
|
|
80
93
|
if (count === 0) return null;
|
|
81
94
|
return {
|
|
82
|
-
manifest: { ...manifest, fileHashes: { ...
|
|
95
|
+
manifest: { ...manifest, fileHashes: { ...kept, ...corrected } },
|
|
83
96
|
count
|
|
84
97
|
};
|
|
85
98
|
}
|
|
@@ -452,12 +452,36 @@ export function resolveSkillFiles(skillName, locale = 'en') {
|
|
|
452
452
|
return { files: [], fallbackToEn, error: `Skill not found: ${skillName}` };
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// Per-FILE fallback to English, not per-skill.
|
|
456
|
+
//
|
|
457
|
+
// A locale pack that has SKILL.md but not its companion files used to install
|
|
458
|
+
// just the SKILL.md, leaving the companions uninstalled entirely. Measured:
|
|
459
|
+
// 4 of 59 localized skills in zh-TW and 5 of 59 in zh-CN are short of the
|
|
460
|
+
// English source, and two of those gaps are REFERENCED — the zh-TW
|
|
461
|
+
// `dev-workflow-guide/SKILL.md` points at `workflow-phases.md` three times
|
|
462
|
+
// and `testing-guide/SKILL.md` at `test-skeleton-templates.md` three times,
|
|
463
|
+
// neither of which the locale pack ships. Those installs shipped a document
|
|
464
|
+
// pointing at files that were never going to be there.
|
|
465
|
+
//
|
|
466
|
+
// Taking the union — locale file where it exists, English where it does not —
|
|
467
|
+
// is also what makes the content comparison usable: without it those skills
|
|
468
|
+
// would report as changed on every upgrade forever. (XSPEC-382 R7)
|
|
469
|
+
const names = new Map(); // fileName -> absolute source path
|
|
470
|
+
const collect = (dir) => {
|
|
471
|
+
if (!existsSync(dir)) return;
|
|
472
|
+
for (const fileName of readdirSync(dir)) {
|
|
473
|
+
const sourcePath = join(dir, fileName);
|
|
474
|
+
// Subdirectories are skipped — matching what the installer has always done.
|
|
475
|
+
if (statSync(sourcePath).isDirectory()) continue;
|
|
476
|
+
if (!names.has(fileName)) names.set(fileName, sourcePath);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
collect(sourceDir); // locale first, so it wins
|
|
480
|
+
if (sourceDir !== enSourceDir) collect(enSourceDir);
|
|
481
|
+
|
|
455
482
|
const files = [];
|
|
456
|
-
for (const
|
|
457
|
-
|
|
458
|
-
// Subdirectories are skipped — matching what the installer has always done.
|
|
459
|
-
if (statSync(sourcePath).isDirectory()) continue;
|
|
460
|
-
files.push({ name: fileName, content: readFileSync(sourcePath, 'utf-8') });
|
|
483
|
+
for (const [name, sourcePath] of names) {
|
|
484
|
+
files.push({ name, content: readFileSync(sourcePath, 'utf-8') });
|
|
461
485
|
}
|
|
462
486
|
|
|
463
487
|
if (needsFrontmatterMerge) {
|
|
@@ -547,7 +571,32 @@ function installSingleSkill(skillName, targetBaseDir, locale = 'en') {
|
|
|
547
571
|
writeFileSync(join(targetDir, file.name), file.content, 'utf-8');
|
|
548
572
|
}
|
|
549
573
|
|
|
550
|
-
|
|
574
|
+
// Remove top-level files UDS does not ship for this skill.
|
|
575
|
+
//
|
|
576
|
+
// Without this, a file UDS once shipped (or that arrived some other way)
|
|
577
|
+
// stays forever, and the content comparison reports the skill as changed on
|
|
578
|
+
// every upgrade with no way to make it stop — the same permanently-true
|
|
579
|
+
// false positive that made `uds check` useless before R6. Found in vibeops:
|
|
580
|
+
// `deploy-assistant/guide.md`, which is in no UDS skills tree and never was.
|
|
581
|
+
//
|
|
582
|
+
// Scoped to files directly inside a directory whose name UDS itself ships,
|
|
583
|
+
// which is the same provenance test that keeps an adopter's OWN skill
|
|
584
|
+
// directories out of every code path here. Subdirectories are left alone —
|
|
585
|
+
// the installer has never written them, so it has no claim on them.
|
|
586
|
+
// (XSPEC-382 R7)
|
|
587
|
+
const shipped = new Set(resolved.files.map((f) => f.name));
|
|
588
|
+
const pruned = [];
|
|
589
|
+
for (const entry of readdirSync(targetDir, { withFileTypes: true })) {
|
|
590
|
+
if (!entry.isFile() || shipped.has(entry.name)) continue;
|
|
591
|
+
try {
|
|
592
|
+
unlinkSync(join(targetDir, entry.name));
|
|
593
|
+
pruned.push(entry.name);
|
|
594
|
+
} catch {
|
|
595
|
+
// Leave it; a stale file is a smaller problem than a failed install.
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return { success: true, skillName, path: targetDir, fallbackToEn, pruned };
|
|
551
600
|
} catch (error) {
|
|
552
601
|
return {
|
|
553
602
|
success: false,
|
|
@@ -683,44 +732,77 @@ export async function installCommandsForAgent(agent, level = 'project', commandN
|
|
|
683
732
|
* @param {string} locale - Locale for command content (default: 'en')
|
|
684
733
|
* @returns {Object} Result
|
|
685
734
|
*/
|
|
686
|
-
|
|
735
|
+
/**
|
|
736
|
+
* Resolve what installing a command would put on disk, without writing anything.
|
|
737
|
+
*
|
|
738
|
+
* The command-side twin of `resolveSkillFiles`, and for the same reason: what
|
|
739
|
+
* gets installed is not what is in the source tree. The source is chosen at
|
|
740
|
+
* runtime by locale with fallback to English, and `transformCommandForAgent`
|
|
741
|
+
* rewrites the content per agent. Hashing the source file would report every
|
|
742
|
+
* command as changed on every upgrade for any agent that transforms.
|
|
743
|
+
*
|
|
744
|
+
* XSPEC-382 R1 covered skills and left commands on `hash: null`, which kept the
|
|
745
|
+
* unconditional-reinstall branch alive for them. R7 closes it the same way —
|
|
746
|
+
* one function the installer writes from and the planner hashes, rather than a
|
|
747
|
+
* second copy of the resolution in the planner.
|
|
748
|
+
*
|
|
749
|
+
* @param {string} cmdName
|
|
750
|
+
* @param {string} agent
|
|
751
|
+
* @param {string} locale
|
|
752
|
+
* @returns {{content: string|null, error: string|null}}
|
|
753
|
+
*/
|
|
754
|
+
export function resolveCommandContent(cmdName, agent, locale = 'en') {
|
|
687
755
|
let sourcePath = join(COMMANDS_LOCAL_DIR, `${cmdName}.md`);
|
|
688
756
|
|
|
689
|
-
// Check for localized command file
|
|
690
757
|
if (isLocalizedLocale(locale)) {
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
if (existsSync(localizedPath)) {
|
|
694
|
-
sourcePath = localizedPath;
|
|
695
|
-
}
|
|
758
|
+
const localizedPath = join(getLocalizedCommandsSourceDir(locale), `${cmdName}.md`);
|
|
759
|
+
if (existsSync(localizedPath)) sourcePath = localizedPath;
|
|
696
760
|
// else: fall back to English source
|
|
697
761
|
}
|
|
698
762
|
|
|
699
|
-
const targetExt = getCommandFileExtension(agent);
|
|
700
|
-
const targetPath = join(targetDir, `${cmdName}${targetExt}`);
|
|
701
|
-
|
|
702
763
|
if (!existsSync(sourcePath)) {
|
|
764
|
+
return { content: null, error: `Command not found: ${cmdName}` };
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
try {
|
|
703
768
|
return {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
error: `Command not found: ${cmdName}`
|
|
769
|
+
content: transformCommandForAgent(readFileSync(sourcePath, 'utf-8'), cmdName, agent),
|
|
770
|
+
error: null
|
|
707
771
|
};
|
|
772
|
+
} catch (error) {
|
|
773
|
+
return { content: null, error: error.message };
|
|
708
774
|
}
|
|
775
|
+
}
|
|
709
776
|
|
|
710
|
-
|
|
711
|
-
|
|
777
|
+
/**
|
|
778
|
+
* Content hash of a resolved command.
|
|
779
|
+
*
|
|
780
|
+
* Same `sha256:` shape as `computeSkillContentHash` so the two sides of the
|
|
781
|
+
* diff can be compared without caring which category an entry came from.
|
|
782
|
+
*
|
|
783
|
+
* @param {string|null} content
|
|
784
|
+
* @returns {string|null}
|
|
785
|
+
*/
|
|
786
|
+
export function computeCommandContentHash(content) {
|
|
787
|
+
if (content === null || content === undefined) return null;
|
|
788
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function installSingleCommand(cmdName, targetDir, agent, locale = 'en') {
|
|
792
|
+
const targetPath = join(targetDir, `${cmdName}${getCommandFileExtension(agent)}`);
|
|
712
793
|
|
|
713
|
-
|
|
714
|
-
|
|
794
|
+
// Write what `resolveCommandContent` says this install is; the planner asks
|
|
795
|
+
// the same function what it WOULD be. (XSPEC-382 R7)
|
|
796
|
+
const resolved = resolveCommandContent(cmdName, agent, locale);
|
|
797
|
+
if (resolved.error) {
|
|
798
|
+
return { success: false, command: cmdName, error: resolved.error };
|
|
799
|
+
}
|
|
715
800
|
|
|
716
|
-
|
|
801
|
+
try {
|
|
802
|
+
writeFileSync(targetPath, resolved.content);
|
|
717
803
|
return { success: true, command: cmdName, path: targetPath };
|
|
718
804
|
} catch (error) {
|
|
719
|
-
return {
|
|
720
|
-
success: false,
|
|
721
|
-
command: cmdName,
|
|
722
|
-
error: error.message
|
|
723
|
-
};
|
|
805
|
+
return { success: false, command: cmdName, error: error.message };
|
|
724
806
|
}
|
|
725
807
|
}
|
|
726
808
|
|
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.7.
|
|
3
|
+
"version": "6.7.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.7.
|
|
61
|
+
"version": "6.7.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.7.
|
|
68
|
+
"version": "6.7.4",
|
|
69
69
|
"note": "Skills are now included in the main repository under skills/"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
@@ -2260,7 +2260,7 @@
|
|
|
2260
2260
|
"id": "license-compliance",
|
|
2261
2261
|
"name": "License Compliance Standards",
|
|
2262
2262
|
"nameZh": "授權合規標準",
|
|
2263
|
-
"version": "6.7.
|
|
2263
|
+
"version": "6.7.4",
|
|
2264
2264
|
"source": {
|
|
2265
2265
|
"human": "core/license-compliance.md",
|
|
2266
2266
|
"ai": "ai/standards/license-compliance.ai.yaml"
|
|
@@ -2272,7 +2272,7 @@
|
|
|
2272
2272
|
"id": "verification-oracle",
|
|
2273
2273
|
"name": "Verification Oracle Standards",
|
|
2274
2274
|
"nameZh": "驗證 Oracle 標準",
|
|
2275
|
-
"version": "6.7.
|
|
2275
|
+
"version": "6.7.4",
|
|
2276
2276
|
"source": {
|
|
2277
2277
|
"human": "core/verification-oracle.md",
|
|
2278
2278
|
"ai": "ai/standards/verification-oracle.ai.yaml"
|
|
@@ -2284,7 +2284,7 @@
|
|
|
2284
2284
|
"id": "model-provenance",
|
|
2285
2285
|
"name": "Model Provenance Policy Standards",
|
|
2286
2286
|
"nameZh": "模型來源政策標準",
|
|
2287
|
-
"version": "6.7.
|
|
2287
|
+
"version": "6.7.4",
|
|
2288
2288
|
"source": {
|
|
2289
2289
|
"human": "core/model-provenance.md",
|
|
2290
2290
|
"ai": "ai/standards/model-provenance.ai.yaml"
|
|
@@ -2296,7 +2296,7 @@
|
|
|
2296
2296
|
"id": "resource-cost-boundary",
|
|
2297
2297
|
"name": "Resource / Cost Boundary Declaration Standards",
|
|
2298
2298
|
"nameZh": "資源/成本邊界宣告標準",
|
|
2299
|
-
"version": "6.7.
|
|
2299
|
+
"version": "6.7.4",
|
|
2300
2300
|
"source": {
|
|
2301
2301
|
"human": "core/resource-cost-boundary.md",
|
|
2302
2302
|
"ai": "ai/standards/resource-cost-boundary.ai.yaml"
|