universal-dev-standards 6.10.0 → 6.12.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/bin/uds.js +2 -0
- package/bundled/ai/standards/open-work-tracking.ai.yaml +216 -0
- package/bundled/core/open-work-tracking.md +333 -0
- package/bundled/locales/zh-CN/CHANGELOG.md +29 -3
- package/bundled/locales/zh-CN/CLAUDE.md +1 -1
- package/bundled/locales/zh-CN/README.md +2 -2
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +2 -1
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +52 -5
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +6 -3
- package/bundled/locales/zh-TW/CHANGELOG.md +29 -3
- package/bundled/locales/zh-TW/CLAUDE.md +1 -1
- package/bundled/locales/zh-TW/README.md +2 -2
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/open-work-tracking.md +255 -0
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +2 -1
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +52 -5
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +6 -3
- package/bundled/locales/zh-TW/integrations/claude-code/README.md +14 -5
- package/package.json +1 -1
- package/src/commands/check.js +253 -21
- package/src/commands/config.js +15 -9
- package/src/commands/init.js +24 -3
- package/src/commands/update.js +311 -47
- package/src/core/manifest.js +39 -1
- package/src/flows/init-flow.js +9 -1
- package/src/generators/layered-claudemd.js +13 -4
- package/src/i18n/messages.js +3 -3
- package/src/installers/integration-installer.js +13 -6
- package/src/installers/manifest-installer.js +4 -0
- package/src/reconciler/actual-state-scanner.js +29 -2
- package/src/reconciler/desired-state-calculator.js +51 -2
- package/src/reconciler/diff-engine.js +19 -3
- package/src/reconciler/plan-executor.js +17 -16
- package/src/utils/hasher.js +61 -5
- package/src/utils/integration-generator.js +239 -28
- package/src/utils/marker-locator.js +140 -0
- package/src/utils/reference-sync.js +53 -1
- package/standards-registry.json +19 -7
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../../docs/CLI-INIT-OPTIONS.md
|
|
3
|
-
source_version: 3.5.
|
|
4
|
-
translation_version: 3.5.
|
|
5
|
-
last_synced: 2026-
|
|
3
|
+
source_version: 3.5.2
|
|
4
|
+
translation_version: 3.5.2
|
|
5
|
+
last_synced: 2026-09-18
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,8 +10,8 @@ status: current
|
|
|
10
10
|
|
|
11
11
|
> **語言**: [English](../../../docs/CLI-INIT-OPTIONS.md) | 繁體中文 | [简体中文](../../zh-CN/docs/CLI-INIT-OPTIONS.md)
|
|
12
12
|
>
|
|
13
|
-
> **版本**: 3.5.
|
|
14
|
-
> **最後更新**: 2026-
|
|
13
|
+
> **版本**: 3.5.2
|
|
14
|
+
> **最後更新**: 2026-09-18
|
|
15
15
|
|
|
16
16
|
本文件詳細說明 `uds init` 命令的每一個選項,包含使用情境、影響範圍和建議選擇。
|
|
17
17
|
|
|
@@ -835,8 +835,49 @@ uds init --experimental
|
|
|
835
835
|
| 不生成 AGENTS.md | `--no-agents-md` | 跳過 AGENTS.md 生成 |
|
|
836
836
|
| 強制執行 Hooks | `--with-hooks` | 安裝強制執行 hooks(commit-msg、security、logging) |
|
|
837
837
|
| 內容佈局 | `--content-layout` | 內容佈局(`flat`、`layered`)- 預設:`flat` |
|
|
838
|
+
| Claude Code 目標檔 | `--claude-target` | Claude Code 整合內容要寫到哪裡:`project`(`CLAUDE.md`,預設)或 `local`(`CLAUDE.local.md`) |
|
|
838
839
|
| 模式(已棄用) | `-m, --mode` | 安裝模式(skills, full)- 請改用 `--skills-location` |
|
|
839
840
|
|
|
841
|
+
### Claude Code 整合目標檔(`--claude-target`)
|
|
842
|
+
|
|
843
|
+
UDS 預設把 Claude Code 內容寫進 `CLAUDE.md`——團隊共用、會進版控的那個檔案。
|
|
844
|
+
若你是在一個**已有團隊 `CLAUDE.md`** 的 repo 裡**個人採用** UDS,改用
|
|
845
|
+
`--claude-target local`:UDS 會改寫入 `CLAUDE.local.md`,這是
|
|
846
|
+
[Claude Code 原生支援](https://code.claude.com/docs/en/memory.md)、
|
|
847
|
+
緊接在 `CLAUDE.md` 之後讀入的檔案,且完全不動團隊的檔案。
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
# 在有團隊 CLAUDE.md 的 repo 裡個人採用
|
|
851
|
+
uds init -y --claude-target local
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
使用前有三件事要知道:
|
|
855
|
+
|
|
856
|
+
1. **要自己把它加進 gitignore。** UDS 不會寫 `.gitignore` 或
|
|
857
|
+
`.git/info/exclude`——請自行把 `CLAUDE.local.md` 加進其中一個,
|
|
858
|
+
否則它會像任何新檔案一樣被 commit。
|
|
859
|
+
2. **只存在於建立它的那個 worktree。** 因為(你 gitignore 之後)它是未受版控的檔案,
|
|
860
|
+
在某個 `git worktree` 建立的 `CLAUDE.local.md` 在同一個 repo 的另一個 worktree
|
|
861
|
+
看不到——每個 worktree 有自己的工作目錄,未受版控的檔案不會在 worktree 之間共享。
|
|
862
|
+
若你使用多個 worktree,需要在每一個裡分別執行
|
|
863
|
+
`uds init --claude-target local`(或下方的 `uds update --claude-target local`)。
|
|
864
|
+
3. **`AGENTS.md` 不受影響。** `--claude-target` 只改變 Claude Code 內容要寫到哪裡。
|
|
865
|
+
若 `--agents-md` 生成了通用的 `AGENTS.md` 摘要,它仍照常寫進 `AGENTS.md`;
|
|
866
|
+
若也不想讓它進版控,一樣要自己排除(例如透過 `.git/info/exclude`)。
|
|
867
|
+
|
|
868
|
+
已經用預設目標檔裝好了,想不重裝就切換?`uds update` 支援同一個旗標:
|
|
869
|
+
|
|
870
|
+
```bash
|
|
871
|
+
# 把既有安裝的 Claude Code 內容從 CLAUDE.md 搬到 CLAUDE.local.md
|
|
872
|
+
uds update --claude-target local
|
|
873
|
+
|
|
874
|
+
# 搬回去
|
|
875
|
+
uds update --claude-target project
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
這會從舊檔移除 UDS 區塊(保留你自己寫在裡面的其他內容)、寫進新檔,並更新
|
|
879
|
+
manifest——之後 `uds check` 驗的是新目標檔,不是舊的。
|
|
880
|
+
|
|
840
881
|
### 完整 CLI 範例
|
|
841
882
|
|
|
842
883
|
```bash
|
|
@@ -876,6 +917,12 @@ uds init -y --output-lang traditional-chinese --locale zh-tw
|
|
|
876
917
|
|
|
877
918
|
# PHP 專案
|
|
878
919
|
uds init -y --lang php --framework fat-free
|
|
920
|
+
|
|
921
|
+
# 在有團隊 CLAUDE.md 的 repo 裡個人採用
|
|
922
|
+
uds init -y --claude-target local
|
|
923
|
+
|
|
924
|
+
# 之後把既有安裝切換到 CLAUDE.local.md,不需重裝
|
|
925
|
+
uds update --claude-target local
|
|
879
926
|
```
|
|
880
927
|
|
|
881
928
|
---
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# UDS 功能參考手冊
|
|
2
2
|
|
|
3
3
|
> Universal Development Standards - 完整功能文件
|
|
4
|
-
> Auto-generated | Last updated: 2026-09-
|
|
4
|
+
> Auto-generated | Last updated: 2026-09-23
|
|
5
5
|
|
|
6
6
|
**Language**: [English](../../../docs/reference/FEATURE-REFERENCE.md) | 繁體中文 | [简体中文](../../zh-CN/docs/FEATURE-REFERENCE.md)
|
|
7
7
|
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
3. [技能](#skills) (55)
|
|
15
15
|
4. [代理](#agents) (5)
|
|
16
16
|
5. [工作流程](#workflows) (5)
|
|
17
|
-
6. [核心規範](#core-standards) (
|
|
17
|
+
6. [核心規範](#core-standards) (153)
|
|
18
18
|
7. [腳本](#scripts) (59)
|
|
19
19
|
|
|
20
|
-
**Total Features:
|
|
20
|
+
**Total Features: 351**
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
| `--no-agents-md` | Skip AGENTS.md generation |
|
|
55
55
|
| `--with-hooks` | Install enforcement hooks declared by the installed standards |
|
|
56
56
|
| `--content-layout` | Content layout (flat, layered) [default: flat] |
|
|
57
|
+
| `--claude-target` | Claude Code integration target: project (default, writes CLAUDE.md) or local (writes CLAUDE.local.md — not committed to git; gitignore it yourself) |
|
|
57
58
|
| `-y, --yes` | Use defaults, skip interactive prompts |
|
|
58
59
|
| `-E, --experimental` | Enable experimental features (methodology) |
|
|
59
60
|
| `--force` | Bypass UDS source-repo self-adoption guard (DEC-044 / XSPEC-071) |
|
|
@@ -155,6 +156,7 @@
|
|
|
155
156
|
| `--force` | Force update all files, ignoring hash comparison |
|
|
156
157
|
| `--prune` | Delete .standards/ files UDS wrote but no longer ships (listed without this flag; never touches files UDS did not write) |
|
|
157
158
|
| `--rollback` | Rollback to the most recent backup |
|
|
159
|
+
| `--claude-target` | Switch an existing install to a different Claude Code integration target: project (CLAUDE.md) or local (CLAUDE.local.md) — moves the UDS block, keeps your content, no reinstall |
|
|
158
160
|
| `--locale` | Override locale for skills install (zh-tw, zh-cn, en); also reads .uds/install.yaml + UDS_LOCALE env |
|
|
159
161
|
|
|
160
162
|
### `uds skills`
|
|
@@ -514,6 +516,7 @@
|
|
|
514
516
|
| `mutation-testing` | 1.1.0 | Mutation testing evaluates test suite effectiveness by injecting artificial bugs |
|
|
515
517
|
| `no-cicd-deployment` | - | |
|
|
516
518
|
| `observability-standards` | 1.0.0 | |
|
|
519
|
+
| `open-work-tracking` | 1.0.0 | The deferred-item-exit standard requires that a deferred item leave its document |
|
|
517
520
|
| `packaging-standards` | 1.1.0 | This standard defines a Recipe-based packaging framework that enables user proje |
|
|
518
521
|
| `performance-standards` | 1.2.0 | This standard defines comprehensive guidelines for software performance engineer |
|
|
519
522
|
| `pii-classification` | 1.1.0 | **Status**: Active | **Updated**: 2026-06-19 | |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../../../integrations/claude-code/README.md
|
|
3
|
-
source_version: 1.
|
|
4
|
-
translation_version: 1.
|
|
5
|
-
last_synced: 2026-
|
|
3
|
+
source_version: 1.2.0
|
|
4
|
+
translation_version: 1.2.0
|
|
5
|
+
last_synced: 2026-09-18
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -10,8 +10,8 @@ status: current
|
|
|
10
10
|
|
|
11
11
|
> **語言**: English | [繁體中文](README.md)
|
|
12
12
|
|
|
13
|
-
**版本**: 1.
|
|
14
|
-
**最後更新**: 2026-
|
|
13
|
+
**版本**: 1.2.0
|
|
14
|
+
**最後更新**: 2026-09-18
|
|
15
15
|
|
|
16
16
|
本目錄包含將通用開發標準 (Universal Development Standards) 與 [Claude Code](https://docs.anthropic.com/claude-code) 整合的資源。
|
|
17
17
|
|
|
@@ -63,6 +63,15 @@ npx universal-dev-standards init
|
|
|
63
63
|
2. 確保專案中存在 `core/` 目錄。
|
|
64
64
|
3. 如有需要,安裝技能(請參閱 `skills/README.md`)。
|
|
65
65
|
|
|
66
|
+
### 在已有團隊 `CLAUDE.md` 的 repo 裡個人採用(XSPEC-418)
|
|
67
|
+
|
|
68
|
+
若 repo 已有進版控、團隊共用的 `CLAUDE.md`,而你要為自己個人採用 UDS,
|
|
69
|
+
對 `init`(或 `update`,用於切換既有安裝)加上 `--claude-target local`,
|
|
70
|
+
讓 UDS 改寫進 `CLAUDE.local.md`——Claude Code 會緊接在 `CLAUDE.md` 之後讀取它。
|
|
71
|
+
你必須自己把 `CLAUDE.local.md` 加進 `.gitignore`(或 `.git/info/exclude`),
|
|
72
|
+
且注意未受版控的檔案只存在於建立它的那個 git worktree。
|
|
73
|
+
完整說明見 [CLI-INIT-OPTIONS.md § Claude Code 整合目標檔](../../../../docs/CLI-INIT-OPTIONS.md)。
|
|
74
|
+
|
|
66
75
|
## 驗證
|
|
67
76
|
|
|
68
77
|
要驗證整合是否運作正常:
|
package/package.json
CHANGED
package/src/commands/check.js
CHANGED
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
computeFileHash,
|
|
13
13
|
compareFileHash,
|
|
14
14
|
hasFileHashes,
|
|
15
|
-
compareIntegrationBlockHash
|
|
15
|
+
compareIntegrationBlockHash,
|
|
16
|
+
computeIntegrationBlockHash,
|
|
17
|
+
pruneIntegrationFileHashes
|
|
16
18
|
} from '../utils/hasher.js';
|
|
17
19
|
import { downloadFromGitHub, getMarketplaceSkillsInfo } from '../utils/github.js';
|
|
18
20
|
import {
|
|
@@ -30,7 +32,9 @@ import {
|
|
|
30
32
|
findBrokenPathMentions,
|
|
31
33
|
compareStandardsWithReferences
|
|
32
34
|
} from '../utils/reference-sync.js';
|
|
33
|
-
import { extractMarkedContent,
|
|
35
|
+
import { extractMarkedContent, resolveIntegrationTargetFile, parseStandardsIndexCount, writeIntegrationFile } from '../utils/integration-generator.js';
|
|
36
|
+
import { AmbiguousMarkerError } from '../utils/marker-locator.js';
|
|
37
|
+
import { bumpManifestVersion } from '../core/manifest.js';
|
|
34
38
|
import { INTEGRATION_MAPPINGS } from '../installers/integration-installer.js';
|
|
35
39
|
import { getToolFormat } from '../core/constants.js';
|
|
36
40
|
import { checkForUpdates } from '../utils/npm-registry.js';
|
|
@@ -137,6 +141,19 @@ function performFileIntegrityCheck(projectPath, manifest, msg) {
|
|
|
137
141
|
if (hasFileHashes(manifest)) {
|
|
138
142
|
// Hash-based integrity check
|
|
139
143
|
for (const [relativePath, hashInfo] of Object.entries(manifest.fileHashes)) {
|
|
144
|
+
// XSPEC-418 R6 gap 2: a manifest written before this fix can still
|
|
145
|
+
// carry a whole-file entry for a path that is ALSO tracked by its UDS
|
|
146
|
+
// block (integrationBlockHashes) — the pruning added for R6 only runs
|
|
147
|
+
// on a write (uds update / check --restore / --migrate), so a project
|
|
148
|
+
// that has not run one of those yet stayed red on `uds check --ci`
|
|
149
|
+
// forever, for content outside the block that the block check itself
|
|
150
|
+
// says is fine. This is a READ-time skip — it does not touch the
|
|
151
|
+
// manifest, so a plain `uds check` alone cannot fix the underlying
|
|
152
|
+
// stale entry; the write paths still do that (see
|
|
153
|
+
// pruneIntegrationFileHashes in hasher.js).
|
|
154
|
+
if (manifest.integrationBlockHashes && relativePath in manifest.integrationBlockHashes) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
140
157
|
const fullPath = join(projectPath, relativePath);
|
|
141
158
|
const status = compareFileHash(fullPath, hashInfo);
|
|
142
159
|
|
|
@@ -401,12 +418,38 @@ export async function checkCommand(options = {}) {
|
|
|
401
418
|
// Check Commands integrity if commandHashes exist
|
|
402
419
|
checkCommandsIntegrity(manifest, projectPath, msg);
|
|
403
420
|
|
|
421
|
+
// XSPEC adopter-report Q3: neither of the two checks above (content-hash
|
|
422
|
+
// integrity) says anything about an installed Skills/Commands version
|
|
423
|
+
// being behind the latest UDS release — that was only ever computed by
|
|
424
|
+
// `uds update --plan --skills`/`--commands`. A plain `uds check` gave no
|
|
425
|
+
// signal at all that `uds update --skills` had anything to do.
|
|
426
|
+
checkSkillsCommandsVersionStaleness(manifest, projectPath, msg);
|
|
427
|
+
|
|
404
428
|
// Check Integration blocks integrity if integrationBlockHashes exist
|
|
405
|
-
|
|
429
|
+
// XSPEC-418 R1: the return value used to be discarded, so a removed/modified
|
|
430
|
+
// UDS block never affected the final verdict below — `uds check --ci` printed
|
|
431
|
+
// "compliant" and exited 0 with a visible ✗ still on screen.
|
|
432
|
+
const integrationBlockStatus = checkIntegrationBlocksIntegrity(manifest, projectPath, msg);
|
|
406
433
|
|
|
407
434
|
// Handle --restore option
|
|
408
435
|
if (options.restore) {
|
|
409
|
-
|
|
436
|
+
// XSPEC-418 R6 gap 1: integration files no longer live in `fileHashes`
|
|
437
|
+
// at all (that was the point of R6), so `fileStatus` — built entirely
|
|
438
|
+
// from `fileHashes` — never contains them any more. Without this,
|
|
439
|
+
// `--restore` silently did nothing for a damaged UDS block: it
|
|
440
|
+
// regenerated zero files and printed "Restored 0 file(s)" while the
|
|
441
|
+
// block-modified/markers-removed content sat untouched.
|
|
442
|
+
// `restoreSingleFile`'s integration-file branch (`manifest.integrationConfigs`)
|
|
443
|
+
// does not depend on fileHashes, so feeding these paths in is enough —
|
|
444
|
+
// it already regenerates just the block and leaves everything else alone.
|
|
445
|
+
const integrationFilesToRestore = [
|
|
446
|
+
...integrationBlockStatus.modified,
|
|
447
|
+
...integrationBlockStatus.noMarkers,
|
|
448
|
+
...integrationBlockStatus.missing
|
|
449
|
+
];
|
|
450
|
+
await restoreFiles(projectPath, manifest, [
|
|
451
|
+
...new Set([...fileStatus.modified, ...fileStatus.missing, ...integrationFilesToRestore])
|
|
452
|
+
]);
|
|
410
453
|
return;
|
|
411
454
|
}
|
|
412
455
|
|
|
@@ -470,8 +513,15 @@ export async function checkCommand(options = {}) {
|
|
|
470
513
|
displayWorkflowStatus(projectPath);
|
|
471
514
|
|
|
472
515
|
// Final status
|
|
516
|
+
// XSPEC-418 R1: integration block problems (UDS markers removed, block
|
|
517
|
+
// modified, or the tracked file missing) now feed the verdict — they used to
|
|
518
|
+
// be checked and printed above, then silently dropped here.
|
|
473
519
|
const allGood = fileStatus.missing.length === 0 &&
|
|
474
|
-
fileStatus.modified.length === 0
|
|
520
|
+
fileStatus.modified.length === 0 &&
|
|
521
|
+
integrationBlockStatus.modified.length === 0 &&
|
|
522
|
+
integrationBlockStatus.missing.length === 0 &&
|
|
523
|
+
integrationBlockStatus.noMarkers.length === 0 &&
|
|
524
|
+
integrationBlockStatus.ambiguous.length === 0;
|
|
475
525
|
if (allGood) {
|
|
476
526
|
console.log(chalk.green(msg.projectCompliant));
|
|
477
527
|
} else {
|
|
@@ -584,6 +634,11 @@ async function interactiveMode(projectPath, manifest, fileStatus, msg) {
|
|
|
584
634
|
}
|
|
585
635
|
|
|
586
636
|
if (manifestUpdated) {
|
|
637
|
+
// XSPEC-418 R6: interactive mode still has a "keep current content" branch
|
|
638
|
+
// that calls the whole-file updateFileHash(); if issue.file happens to be
|
|
639
|
+
// an integration file left in fileHashes by an older CLI, this is the
|
|
640
|
+
// write point that stops it from surviving another round.
|
|
641
|
+
pruneIntegrationFileHashes(manifest);
|
|
587
642
|
writeManifest(manifest, projectPath);
|
|
588
643
|
console.log(chalk.green(msg.manifestUpdated));
|
|
589
644
|
console.log();
|
|
@@ -701,6 +756,9 @@ async function restoreFiles(projectPath, manifest, files) {
|
|
|
701
756
|
}
|
|
702
757
|
|
|
703
758
|
// Update manifest
|
|
759
|
+
// XSPEC-418 R6: catches any stale whole-file entry for an integration file
|
|
760
|
+
// this particular restore run didn't touch, not just the ones it did.
|
|
761
|
+
pruneIntegrationFileHashes(manifest);
|
|
704
762
|
writeManifest(manifest, projectPath);
|
|
705
763
|
console.log(chalk.gray(` ${msg.manifestUpdatedShort}`));
|
|
706
764
|
console.log();
|
|
@@ -728,7 +786,13 @@ export async function restoreSingleFile(projectPath, manifest, relativePath, msg
|
|
|
728
786
|
installedStandards: genConfig.installedStandards || manifest.standards || []
|
|
729
787
|
}, projectPath);
|
|
730
788
|
if (result.success) {
|
|
731
|
-
|
|
789
|
+
// XSPEC-418 R6: this rewrites the UDS block, so the BLOCK hash is what
|
|
790
|
+
// must be refreshed — not a whole-file hash. Restoring used to call
|
|
791
|
+
// updateFileHash() here, which recorded the whole file (block +
|
|
792
|
+
// whatever the adopter wrote outside it) while leaving the stale block
|
|
793
|
+
// hash untouched; the next `uds check` then reported "UDS block
|
|
794
|
+
// modified" for a block that had just been correctly restored.
|
|
795
|
+
updateIntegrationBlockHash(manifest, relativePath, result.blockHashInfo);
|
|
732
796
|
console.log(chalk.green(` ✓ ${relativePath}: ${msg.restored}`));
|
|
733
797
|
return true;
|
|
734
798
|
}
|
|
@@ -750,7 +814,32 @@ export async function restoreSingleFile(projectPath, manifest, relativePath, msg
|
|
|
750
814
|
// Integration file - copy to root
|
|
751
815
|
const result = await copyIntegration(sourcePath, relativePath, projectPath);
|
|
752
816
|
if (result.success) {
|
|
753
|
-
|
|
817
|
+
// XSPEC-418 R6: same rule as the genConfig branch above — track the
|
|
818
|
+
// block, not the whole file. This legacy static-copy fallback has no
|
|
819
|
+
// blockHashInfo returned to it, so compute one from what was just
|
|
820
|
+
// written; a template with no UDS markers at all (computeIntegration
|
|
821
|
+
// BlockHash returns null) falls back to the old whole-file behaviour,
|
|
822
|
+
// since there is no block to track instead.
|
|
823
|
+
// XSPEC adopter-report Q5: the file was just overwritten wholesale by
|
|
824
|
+
// copyIntegration (a full template copy, not a marker-preserving
|
|
825
|
+
// merge), so an ambiguous marker pair here cannot be this restore's
|
|
826
|
+
// own doing — fall back to whole-file hashing rather than fail a
|
|
827
|
+
// restore that already succeeded on disk.
|
|
828
|
+
let blockHashInfo;
|
|
829
|
+
try {
|
|
830
|
+
blockHashInfo = computeIntegrationBlockHash(join(projectPath, relativePath));
|
|
831
|
+
} catch (error) {
|
|
832
|
+
if (error instanceof AmbiguousMarkerError) {
|
|
833
|
+
blockHashInfo = null;
|
|
834
|
+
} else {
|
|
835
|
+
throw error;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (blockHashInfo) {
|
|
839
|
+
updateIntegrationBlockHash(manifest, relativePath, blockHashInfo);
|
|
840
|
+
} else {
|
|
841
|
+
updateFileHash(projectPath, manifest, relativePath);
|
|
842
|
+
}
|
|
754
843
|
console.log(chalk.green(` ✓ ${relativePath}: ${msg.restored}`));
|
|
755
844
|
return true;
|
|
756
845
|
} else {
|
|
@@ -770,6 +859,30 @@ export async function restoreSingleFile(projectPath, manifest, relativePath, msg
|
|
|
770
859
|
}
|
|
771
860
|
}
|
|
772
861
|
|
|
862
|
+
/**
|
|
863
|
+
* Update an integration file's UDS BLOCK hash in the manifest — not its
|
|
864
|
+
* whole-file hash (XSPEC-418 R6). Also drops any whole-file `fileHashes`
|
|
865
|
+
* entry for the same path, so restoring an integration file can never leave
|
|
866
|
+
* both records behind at once.
|
|
867
|
+
*
|
|
868
|
+
* @param {Object} manifest - Manifest object (mutated in place)
|
|
869
|
+
* @param {string} relativePath - Path as UDS reports it
|
|
870
|
+
* @param {Object|null|undefined} blockHashInfo - `{ blockHash, blockSize, fullHash, fullSize }`,
|
|
871
|
+
* e.g. from `writeIntegrationFile`'s result or `computeIntegrationBlockHash`
|
|
872
|
+
* @returns {boolean} Whether a block hash was recorded
|
|
873
|
+
*/
|
|
874
|
+
export function updateIntegrationBlockHash(manifest, relativePath, blockHashInfo) {
|
|
875
|
+
if (!blockHashInfo) return false;
|
|
876
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
877
|
+
if (!manifest.integrationBlockHashes) manifest.integrationBlockHashes = {};
|
|
878
|
+
manifest.integrationBlockHashes[normalizedPath] = {
|
|
879
|
+
...blockHashInfo,
|
|
880
|
+
installedAt: new Date().toISOString()
|
|
881
|
+
};
|
|
882
|
+
if (manifest.fileHashes) delete manifest.fileHashes[normalizedPath];
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
|
|
773
886
|
/**
|
|
774
887
|
* Update file hash in manifest
|
|
775
888
|
*/
|
|
@@ -918,21 +1031,54 @@ async function migrateToHashBasedTracking(projectPath, manifest) {
|
|
|
918
1031
|
}
|
|
919
1032
|
}
|
|
920
1033
|
|
|
921
|
-
// Process integrations
|
|
1034
|
+
// Process integrations — tracked by their UDS BLOCK hash, not a whole-file
|
|
1035
|
+
// hash (XSPEC-418 R6). This loop used to write the whole file into the same
|
|
1036
|
+
// `fileHashes` map as standards/extensions above, which is the same defect
|
|
1037
|
+
// as every other write site R6 fixes, just reached by `--migrate` instead
|
|
1038
|
+
// of a normal update: any content the adopter had outside the block then
|
|
1039
|
+
// read as "modified" by standards-file integrity, contradicting the block
|
|
1040
|
+
// check in the same `uds check` run.
|
|
1041
|
+
const integrationBlockHashes = { ...(manifest.integrationBlockHashes || {}) };
|
|
922
1042
|
for (const intEntry of manifest.integrations) {
|
|
923
1043
|
const int = resolveIntegrationFile(intEntry) || intEntry;
|
|
924
1044
|
const fullPath = join(projectPath, int);
|
|
925
1045
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1046
|
+
// XSPEC adopter-report Q5: this is a read-only manifest migration over
|
|
1047
|
+
// every tracked integration file at once — one file with an ambiguous
|
|
1048
|
+
// marker pair must not abort migrating the rest. It is simply left
|
|
1049
|
+
// without a block hash, same as a file with no markers at all; `uds
|
|
1050
|
+
// check`'s own block-integrity pass (below) is what surfaces the
|
|
1051
|
+
// ambiguity to the user, with line numbers.
|
|
1052
|
+
let blockHashInfo;
|
|
1053
|
+
try {
|
|
1054
|
+
blockHashInfo = computeIntegrationBlockHash(fullPath);
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
if (error instanceof AmbiguousMarkerError) {
|
|
1057
|
+
blockHashInfo = null;
|
|
1058
|
+
} else {
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (blockHashInfo) {
|
|
1063
|
+
integrationBlockHashes[int] = { ...blockHashInfo, installedAt: now };
|
|
929
1064
|
count++;
|
|
1065
|
+
} else {
|
|
1066
|
+
// No UDS markers found (plaintext template with none, or a file the
|
|
1067
|
+
// adopter fully rewrote) — fall back to whole-file tracking, same as
|
|
1068
|
+
// before this fix, rather than silently tracking nothing.
|
|
1069
|
+
const hashInfo = computeFileHash(fullPath);
|
|
1070
|
+
if (hashInfo) {
|
|
1071
|
+
fileHashes[int] = { ...hashInfo, installedAt: now };
|
|
1072
|
+
count++;
|
|
1073
|
+
}
|
|
930
1074
|
}
|
|
931
1075
|
}
|
|
932
1076
|
|
|
933
1077
|
// Update manifest
|
|
934
1078
|
manifest.fileHashes = fileHashes;
|
|
935
|
-
manifest.
|
|
1079
|
+
manifest.integrationBlockHashes = integrationBlockHashes;
|
|
1080
|
+
bumpManifestVersion(manifest);
|
|
1081
|
+
pruneIntegrationFileHashes(manifest);
|
|
936
1082
|
writeManifest(manifest, projectPath);
|
|
937
1083
|
|
|
938
1084
|
console.log(chalk.green(msg.migratedCount.replace('{count}', count)));
|
|
@@ -1220,7 +1366,9 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1220
1366
|
// verdict — the tools that share it are named on the line instead.
|
|
1221
1367
|
const toolsByFile = new Map();
|
|
1222
1368
|
for (const tool of manifest.aiTools) {
|
|
1223
|
-
|
|
1369
|
+
// XSPEC-418 R3: check must inspect the actual target file (e.g. CLAUDE.local.md),
|
|
1370
|
+
// not the tool's hardcoded default.
|
|
1371
|
+
const file = resolveIntegrationTargetFile(tool, manifest);
|
|
1224
1372
|
if (!file) continue;
|
|
1225
1373
|
if (!toolsByFile.has(file)) toolsByFile.set(file, []);
|
|
1226
1374
|
toolsByFile.get(file).push(tool);
|
|
@@ -1251,7 +1399,20 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1251
1399
|
|
|
1252
1400
|
// Check for standards index marker
|
|
1253
1401
|
const format = getToolFormat(tool);
|
|
1254
|
-
|
|
1402
|
+
let markedContent;
|
|
1403
|
+
try {
|
|
1404
|
+
({ content: markedContent } = extractMarkedContent(content, format));
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
if (error instanceof AmbiguousMarkerError) {
|
|
1407
|
+
// XSPEC adopter-report Q5: report explicitly with line numbers
|
|
1408
|
+
// instead of guessing which marker pair is real, or silently
|
|
1409
|
+
// treating the file as if it had no UDS block at all.
|
|
1410
|
+
console.log(chalk.red(` ✗ ${toolFile}: ${error.message}`));
|
|
1411
|
+
hasIssues = true;
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
throw error;
|
|
1415
|
+
}
|
|
1255
1416
|
const hasStandardsIndex = markedContent.length > 0 ||
|
|
1256
1417
|
content.includes('Standards Index') ||
|
|
1257
1418
|
content.includes('Standards Compliance');
|
|
@@ -1607,6 +1768,59 @@ async function checkCliVersion(bundledVersion) {
|
|
|
1607
1768
|
// Enhanced Integrity Check Functions (v3.3.0+)
|
|
1608
1769
|
// ============================================================
|
|
1609
1770
|
|
|
1771
|
+
/**
|
|
1772
|
+
* Warn when an installed Skills or Commands version is behind the latest
|
|
1773
|
+
* UDS release. This is deliberately modeled on the existing top-level
|
|
1774
|
+
* "Version: X → Y ⚠" row (see the `hasUpdate` check in the summary
|
|
1775
|
+
* dashboard below) rather than on `checkIntegrationBlocksIntegrity`
|
|
1776
|
+
* (XSPEC-418 R1): a version simply being behind the latest release is
|
|
1777
|
+
* ambient, expected state until the adopter chooses to update — not a
|
|
1778
|
+
* compliance defect the way a modified/missing/ambiguous UDS block is — so
|
|
1779
|
+
* it is reported but, unlike a block problem, does not fail `--ci`.
|
|
1780
|
+
*
|
|
1781
|
+
* Skills staleness is read per-installation from what is actually on disk
|
|
1782
|
+
* (`getInstalledSkillsInfoForAgent`), the same source `uds update --plan
|
|
1783
|
+
* --skills` uses, because different agents/levels can be out of sync
|
|
1784
|
+
* independently. Commands have no per-installation version on disk (only a
|
|
1785
|
+
* file count), so Commands staleness is read from the one version the
|
|
1786
|
+
* manifest itself records (`manifest.commands.version`).
|
|
1787
|
+
* // implements XSPEC adopter-report Q3
|
|
1788
|
+
*
|
|
1789
|
+
* @param {Object} manifest
|
|
1790
|
+
* @param {string} projectPath
|
|
1791
|
+
* @param {Object} msg - Localized messages (unused today; kept for symmetry
|
|
1792
|
+
* with the other Enhanced Integrity Check functions, which all take one)
|
|
1793
|
+
*/
|
|
1794
|
+
function checkSkillsCommandsVersionStaleness(manifest, projectPath, msg) { // eslint-disable-line no-unused-vars
|
|
1795
|
+
const repoInfo = getRepositoryInfo();
|
|
1796
|
+
const latestVersion = repoInfo.skills.version;
|
|
1797
|
+
const stale = [];
|
|
1798
|
+
|
|
1799
|
+
const skillsInstallations = (manifest.skills?.installations || []).filter((i) => i.level !== 'marketplace');
|
|
1800
|
+
for (const inst of skillsInstallations) {
|
|
1801
|
+
const info = getInstalledSkillsInfoForAgent(inst.agent, inst.level, projectPath);
|
|
1802
|
+
const current = info?.version;
|
|
1803
|
+
if (current && current !== latestVersion) {
|
|
1804
|
+
stale.push(`${getAgentDisplayName(inst.agent)} (${inst.level}): Skills v${current} → v${latestVersion}`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
if (manifest.commands?.installed && (manifest.commands?.installations || []).length > 0) {
|
|
1809
|
+
const current = manifest.commands.version;
|
|
1810
|
+
if (current && current !== latestVersion) {
|
|
1811
|
+
stale.push(`Commands: v${current} → v${latestVersion}`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
if (stale.length === 0) return;
|
|
1816
|
+
|
|
1817
|
+
for (const line of stale) {
|
|
1818
|
+
console.log(chalk.yellow(` ⚠ ${line}`));
|
|
1819
|
+
}
|
|
1820
|
+
console.log(chalk.gray(' Run `uds update --plan --skills` / `--commands` for details, then `--apply` to update.'));
|
|
1821
|
+
console.log();
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1610
1824
|
/**
|
|
1611
1825
|
* Check Skills files integrity against stored hashes
|
|
1612
1826
|
* @param {Object} manifest - Manifest object
|
|
@@ -1789,19 +2003,19 @@ export function checkCommandsIntegrity(manifest, projectPath, msg) {
|
|
|
1789
2003
|
* @param {Object} manifest - Manifest object
|
|
1790
2004
|
* @param {string} projectPath - Project root path
|
|
1791
2005
|
* @param {Object} msg - Localized messages
|
|
1792
|
-
* @returns {Object} Status { unchanged: [], modified: [], missing: [], noMarkers: [] }
|
|
2006
|
+
* @returns {Object} Status { unchanged: [], modified: [], missing: [], noMarkers: [], ambiguous: [] }
|
|
1793
2007
|
*/
|
|
1794
2008
|
function checkIntegrationBlocksIntegrity(manifest, projectPath, msg) {
|
|
1795
2009
|
const blockHashes = manifest.integrationBlockHashes;
|
|
1796
2010
|
|
|
1797
2011
|
// Skip if no block hashes tracked
|
|
1798
2012
|
if (!blockHashes || Object.keys(blockHashes).length === 0) {
|
|
1799
|
-
return { unchanged: [], modified: [], missing: [], noMarkers: [], tracked: false };
|
|
2013
|
+
return { unchanged: [], modified: [], missing: [], noMarkers: [], ambiguous: [], tracked: false };
|
|
1800
2014
|
}
|
|
1801
2015
|
|
|
1802
2016
|
console.log(chalk.cyan(msg.integrationBlocksCheck || 'Integration UDS Block Integrity'));
|
|
1803
2017
|
|
|
1804
|
-
const status = { unchanged: [], modified: [], missing: [], noMarkers: [], tracked: true };
|
|
2018
|
+
const status = { unchanged: [], modified: [], missing: [], noMarkers: [], ambiguous: [], tracked: true };
|
|
1805
2019
|
|
|
1806
2020
|
for (const [filePath, hashInfo] of Object.entries(blockHashes)) {
|
|
1807
2021
|
const fullPath = join(projectPath, filePath);
|
|
@@ -1813,7 +2027,21 @@ function checkIntegrationBlocksIntegrity(manifest, projectPath, msg) {
|
|
|
1813
2027
|
}
|
|
1814
2028
|
|
|
1815
2029
|
// Compare block hash
|
|
1816
|
-
|
|
2030
|
+
// XSPEC adopter-report Q5: a file with two real START or END marker
|
|
2031
|
+
// lines is not "modified" and not "no markers" — it is ambiguous, and
|
|
2032
|
+
// the whole point of this fix is to report that explicitly (with line
|
|
2033
|
+
// numbers) instead of silently picking one and guessing.
|
|
2034
|
+
let blockStatus;
|
|
2035
|
+
try {
|
|
2036
|
+
blockStatus = compareIntegrationBlockHash(fullPath, hashInfo);
|
|
2037
|
+
} catch (error) {
|
|
2038
|
+
if (error instanceof AmbiguousMarkerError) {
|
|
2039
|
+
status.ambiguous.push(filePath);
|
|
2040
|
+
console.log(chalk.red(` ✗ ${filePath}: ${error.message}`));
|
|
2041
|
+
continue;
|
|
2042
|
+
}
|
|
2043
|
+
throw error;
|
|
2044
|
+
}
|
|
1817
2045
|
|
|
1818
2046
|
switch (blockStatus) {
|
|
1819
2047
|
case 'unchanged':
|
|
@@ -1835,17 +2063,21 @@ function checkIntegrationBlocksIntegrity(manifest, projectPath, msg) {
|
|
|
1835
2063
|
}
|
|
1836
2064
|
|
|
1837
2065
|
// Summary
|
|
1838
|
-
if (status.modified.length === 0 && status.missing.length === 0 && status.noMarkers.length === 0) {
|
|
2066
|
+
if (status.modified.length === 0 && status.missing.length === 0 && status.noMarkers.length === 0 && status.ambiguous.length === 0) {
|
|
1839
2067
|
console.log(chalk.green(` ✓ ${msg.allBlocksIntact || 'All UDS blocks intact'} (${status.unchanged.length} files)`));
|
|
1840
2068
|
console.log(chalk.gray(` ${msg.userContentPreserved || 'User customizations outside UDS blocks are preserved'}`));
|
|
1841
2069
|
} else {
|
|
1842
2070
|
console.log(chalk.gray(` ${(msg.blocksIntegritySummary || '{unchanged} intact, {modified} modified, {missing} missing')
|
|
1843
2071
|
.replace('{unchanged}', status.unchanged.length)
|
|
1844
2072
|
.replace('{modified}', status.modified.length)
|
|
1845
|
-
.replace('{missing}', status.missing.length + status.noMarkers.length)}`));
|
|
2073
|
+
.replace('{missing}', status.missing.length + status.noMarkers.length + status.ambiguous.length)}`));
|
|
1846
2074
|
|
|
1847
2075
|
if (status.modified.length > 0 || status.noMarkers.length > 0) {
|
|
1848
|
-
|
|
2076
|
+
// XSPEC-418 R6 gap 1: `uds check --restore` now regenerates just the
|
|
2077
|
+
// UDS block for these files too (it used to do nothing for them — see
|
|
2078
|
+
// the --restore handler above), so it is named alongside `uds update
|
|
2079
|
+
// --integrations-only` rather than as a second-choice remedy.
|
|
2080
|
+
console.log(chalk.yellow(` ${msg.runUpdateIntegrations || 'Run "uds check --restore" or "uds update --integrations-only" to restore UDS content'}`));
|
|
1849
2081
|
}
|
|
1850
2082
|
}
|
|
1851
2083
|
|
package/src/commands/config.js
CHANGED
|
@@ -45,11 +45,11 @@ import {
|
|
|
45
45
|
import { displayLanguageToLocale } from '../utils/locale.js';
|
|
46
46
|
import {
|
|
47
47
|
writeIntegrationFile,
|
|
48
|
-
|
|
48
|
+
resolveIntegrationTargetFile
|
|
49
49
|
} from '../utils/integration-generator.js';
|
|
50
50
|
import { getMarketplaceSkillsInfo } from '../utils/github.js';
|
|
51
51
|
import { regenerateIntegrations } from './update.js';
|
|
52
|
-
import { mergeInstalledNames } from '../core/manifest.js';
|
|
52
|
+
import { mergeInstalledNames, bumpManifestVersion } from '../core/manifest.js';
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Get localized message with fallback (for config-specific keys)
|
|
@@ -667,13 +667,16 @@ export async function runProjectConfiguration(options) {
|
|
|
667
667
|
// Remove integration files for removed tools
|
|
668
668
|
const spinner = createSpinner(msgObj.removingIntegrations).start();
|
|
669
669
|
for (const tool of result.tools) {
|
|
670
|
-
|
|
670
|
+
// XSPEC-418 R3: remove the file this tool actually targets (e.g.
|
|
671
|
+
// CLAUDE.local.md), not always its hardcoded default.
|
|
672
|
+
const removedToolFile = resolveIntegrationTargetFile(tool, manifest);
|
|
673
|
+
const filePath = join(projectPath, removedToolFile);
|
|
671
674
|
if (existsSync(filePath)) {
|
|
672
675
|
try {
|
|
673
676
|
unlinkSync(filePath);
|
|
674
|
-
console.log(chalk.gray(` ${msgObj.removed}: ${
|
|
677
|
+
console.log(chalk.gray(` ${msgObj.removed}: ${removedToolFile}`));
|
|
675
678
|
} catch {
|
|
676
|
-
console.log(chalk.yellow(` ${msgObj.couldNotRemove}: ${
|
|
679
|
+
console.log(chalk.yellow(` ${msgObj.couldNotRemove}: ${removedToolFile}`));
|
|
677
680
|
}
|
|
678
681
|
}
|
|
679
682
|
|
|
@@ -711,7 +714,7 @@ export async function runProjectConfiguration(options) {
|
|
|
711
714
|
}
|
|
712
715
|
|
|
713
716
|
// Remove from integrationBlockHashes (keyed by tool file path)
|
|
714
|
-
const toolFileName =
|
|
717
|
+
const toolFileName = removedToolFile;
|
|
715
718
|
if (manifest.integrationBlockHashes?.[toolFileName]) {
|
|
716
719
|
delete manifest.integrationBlockHashes[toolFileName];
|
|
717
720
|
}
|
|
@@ -934,7 +937,8 @@ export async function runProjectConfiguration(options) {
|
|
|
934
937
|
const generatedFiles = new Set();
|
|
935
938
|
|
|
936
939
|
for (const tool of newAITools) {
|
|
937
|
-
|
|
940
|
+
// XSPEC-418 R3
|
|
941
|
+
const targetFile = resolveIntegrationTargetFile(tool, manifest);
|
|
938
942
|
if (generatedFiles.has(targetFile)) {
|
|
939
943
|
continue; // Skip if already generated (AGENTS.md sharing)
|
|
940
944
|
}
|
|
@@ -947,7 +951,9 @@ export async function runProjectConfiguration(options) {
|
|
|
947
951
|
standardsFormat: manifest.format || 'ai',
|
|
948
952
|
contentMode: newContentMode,
|
|
949
953
|
// Pass output_language for dynamic commit standards generation
|
|
950
|
-
outputLanguage: newOptions.output_language || 'english'
|
|
954
|
+
outputLanguage: newOptions.output_language || 'english',
|
|
955
|
+
// XSPEC-418 R2/R3
|
|
956
|
+
integrationTargets: manifest.integrationTargets
|
|
951
957
|
};
|
|
952
958
|
|
|
953
959
|
const result = writeIntegrationFile(tool, toolConfig, projectPath);
|
|
@@ -1052,7 +1058,7 @@ export async function runProjectConfiguration(options) {
|
|
|
1052
1058
|
manifest.options = newOptions;
|
|
1053
1059
|
manifest.contentMode = newContentMode;
|
|
1054
1060
|
manifest.aiTools = newAITools;
|
|
1055
|
-
manifest
|
|
1061
|
+
bumpManifestVersion(manifest);
|
|
1056
1062
|
|
|
1057
1063
|
// Update methodology
|
|
1058
1064
|
if (newMethodology) {
|