universal-dev-standards 6.14.0-beta.2 → 6.14.0-beta.3
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/open-work-tracking.ai.yaml +4 -1
- package/bundled/core/open-work-tracking.md +1 -1
- package/bundled/locales/zh-CN/CHANGELOG.md +19 -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 +19 -2
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/open-work-tracking.md +3 -3
- package/package.json +1 -1
- package/src/commands/check.js +9 -0
- package/src/commands/init.js +71 -18
- package/src/commands/uninstall.js +144 -30
- package/src/commands/update.js +59 -1
- package/src/core/install-records.js +191 -0
- package/src/i18n/messages.js +39 -6
- package/src/installers/hooks-installer.js +61 -30
- package/src/installers/integration-installer.js +5 -1
- package/src/uninstallers/hook-uninstaller.js +216 -32
- package/src/uninstallers/integration-uninstaller.js +35 -5
- package/src/utils/git-hooks.js +135 -7
- package/src/utils/hasher.js +36 -0
- package/src/utils/integration-generator.js +16 -6
- package/src/utils/legacy-hook-migration.js +112 -0
- package/src/utils/open-work-tracking.mjs +124 -23
- package/standards-registry.json +7 -7
|
@@ -8,7 +8,7 @@ standard:
|
|
|
8
8
|
|
|
9
9
|
meta:
|
|
10
10
|
version: "1.1.0"
|
|
11
|
-
updated: "2026-09-
|
|
11
|
+
updated: "2026-09-30"
|
|
12
12
|
source: core/open-work-tracking.md
|
|
13
13
|
description: >
|
|
14
14
|
三種不同的「工作不見了」的方式(沒地方記新想法、等待中沒有解除條件、
|
|
@@ -218,6 +218,9 @@ standard:
|
|
|
218
218
|
- named_unresolved
|
|
219
219
|
- unnamed # 唯一的違反
|
|
220
220
|
coverage: "辨認路徑/指令/測試名稱/編號本身是樣式比對,涵蓋率未知(OWT-011);認不出的格式回報為未點名"
|
|
221
|
+
fields_read: "參考程序以同一份詞彙讀三種形狀:小節標題、行內標籤、表頭在詞彙內的表格欄(該欄每一列各算一個欄位)"
|
|
222
|
+
ragged_table_row: "欄數與表頭不一致的列列為判定不了、不當成空白;別處沒有違反時結束碼為 2(不是通過)"
|
|
223
|
+
empty_field: "空白、—、-、已完成的儲存格只計數不評估,不算違反(OWT-019 判斷寫了的下一步,沒寫是另一種失效)"
|
|
221
224
|
|
|
222
225
|
# ── 刻意不採納(來源:使用者轉貼的提示詞,作者不明,僅借設計形狀)──────────
|
|
223
226
|
not_adopted:
|
|
@@ -366,7 +366,7 @@ DEX-003 扮演的角色相同。上面每一條都指名了 artefact 與它們
|
|
|
366
366
|
|
|
367
367
|
## What enforces this standard
|
|
368
368
|
|
|
369
|
-
**Nothing in UDS gates on it, and that is recorded rather than implied.** UDS states the relations a carrier of open work must satisfy; whether anything decides them is the adopting project's call, per the [writing constraint](#how-this-standard-is-written--and-why-it-is-written-that-way) above — the same boundary [deferred-item-exit](deferred-item-exit.md) draws for its own exits. Since 1.1.0 UDS does ship one **reference decision procedure** for OWT-017–OWT-019 — `uds open-work next-action | revision | separation` from the npm package (`uds open-work self-test` runs the checker's own arms; from a clone of the UDS repository `node scripts/check-open-work-tracking.mjs` runs the same code) — offered as evidence in the OWT-015 sense — it has been observed to fail against violating samples — for an adopter to run or to reimplement. It is not wired into any UDS release gate, because UDS carries no open-work carrier for it to check.
|
|
369
|
+
**Nothing in UDS gates on it, and that is recorded rather than implied.** UDS states the relations a carrier of open work must satisfy; whether anything decides them is the adopting project's call, per the [writing constraint](#how-this-standard-is-written--and-why-it-is-written-that-way) above — the same boundary [deferred-item-exit](deferred-item-exit.md) draws for its own exits. Since 1.1.0 UDS does ship one **reference decision procedure** for OWT-017–OWT-019 — `uds open-work next-action | revision | separation` from the npm package (`uds open-work self-test` runs the checker's own arms; from a clone of the UDS repository `node scripts/check-open-work-tracking.mjs` runs the same code) — offered as evidence in the OWT-015 sense — it has been observed to fail against violating samples — for an adopter to run or to reimplement. It is not wired into any UDS release gate, because UDS carries no open-work carrier for it to check. For OWT-019 it reads a next-action field in three shapes, all through one vocabulary: a heading section, an inline label, and every row of a table column whose header is in that vocabulary. A table row whose cell count differs from its header is listed as undecidable (never read as empty; with no violation elsewhere the exit code is 2, not a pass), and an empty, `—`, `-` or done cell is counted and not evaluated — not a violation, because OWT-019 judges a next action that was written, and a missing one is a different failure it does not decide.
|
|
370
370
|
|
|
371
371
|
**UDS 不對本標準設任何閘門,而這件事是被記錄的,不是被暗示的。** UDS 陳述一個承載開放工作的地方
|
|
372
372
|
必須滿足的關係;有沒有東西去判定它,依上面的[寫法約束](#how-this-standard-is-written--and-why-it-is-written-that-way),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../CHANGELOG.md
|
|
3
|
-
source_version: 6.14.0-beta.
|
|
4
|
-
translation_version: 6.14.0-beta.
|
|
3
|
+
source_version: 6.14.0-beta.3
|
|
4
|
+
translation_version: 6.14.0-beta.3
|
|
5
5
|
last_synced: 2026-09-30
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
@@ -17,6 +17,23 @@ status: current
|
|
|
17
17
|
|
|
18
18
|
## [Unreleased]
|
|
19
19
|
|
|
20
|
+
## [6.14.0-beta.3] - 2026-09-30
|
|
21
|
+
|
|
22
|
+
> **测试版**——以 `npm install -g universal-dev-standards@beta` 安装。要测什么、如何退回正式版:[docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
|
|
23
|
+
>
|
|
24
|
+
> **既有采用者:请执行一次 `uds update`。**旧版 `uds init` 写进 pre-commit hook 的是单行 `npx uds check`,它可能请 npm 去拿一个叫 `uds`、但不是本项目的软件包。新安装不再写这一行,而 `uds update` 会替换既有 `.husky/pre-commit` 里 UDS 自己写的那一行。另外:`uds uninstall` 只移除能证明是 UDS 写的东西(早期 UDS 安装的项目会保留一部分文件并说明原因),`uds open-work next-action` 读得懂写成表格列的下一步。
|
|
25
|
+
|
|
26
|
+
### 变更
|
|
27
|
+
|
|
28
|
+
- **`uds open-work next-action` 现在读得懂写成 Markdown 表格列的“下一步”,不再只认小节标题与行内标签。** 表头在“下一步”词汇内的列(`Next action`、`Next step`、`下一步`、`下一動`,以及新增的 `回來要做什麼`)每一行都会被读取,报告附带行号与该行第一格内容,便于定位。词汇仍然只有一份:标题、行内标签与表头都读同一份。放在引用块(`> | … |`)里的表格现在看得见;代码片段内或反斜杠之后的 `|` 不再切开单元格。列数与表头不一致的行会列为 `UNDECIDABLE`(无法判定),不会被当成空白;别处没有违反时退出码为 2,因为干净的结果只涵盖了字段的一部分。空白、`—`、`-` 的单元格只计数、不评估,也不算 OWT-019 违反。此事是在真实工作记录上量测 DEC-122 H2 基准时发现的:80 行中有 32 行的列数与表头不同。`回來要做什麼` 是该工作记录实际使用的表头(其文字说明把该列叫作 下一動),属未校准判断(OWT-016)。检查器自身的突变测试新增八个表格突变,原有十七个仍然转红。
|
|
29
|
+
|
|
30
|
+
### 修复
|
|
31
|
+
|
|
32
|
+
- **`uds uninstall` 不再留下 UDS 自己写的文件,也不再移除它无法证明是 UDS 写的东西。** 走过 `init` → `update --with-hooks` → `uninstall -y` 之后,它报告“已移除 5、已跳过 1、错误 0”,却留下 `scripts/hooks/` 下的 15 个 hook 脚本、一个空的 `.codex/`、一份生成标头仍指向已删除 `.standards/` 的 AGENTS.md,以及 `uds init` 写入的 `.git/hooks/pre-commit` 脚本主体。现在的规则是:整个文件只有在 manifest 记录了“UDS 写的”(`installedArtifacts`,由 `init` 与 `update --with-hooks` 写入)**并且**内容仍与记录的哈希相符时才会删除。其他一律保留,并在输出中说明原因(`kept: modified since UDS wrote it`、`kept: no install record — ...`)。文件夹只有在 UDS 创建且现已为空时才移除;采用者自己的 `scripts/hooks/*.mjs`、`.agents/rules/*` 与 hook 条目都会保留。由旧版 UDS 安装的项目没有记录,其脚本、AGENTS.md 生成文字与原生 pre-commit 主体会被保留并说明,而不是猜测。每一行“已移除”现在都对应一次真实的删除或修改,带着错误结束的运行也会以非 0 结束。
|
|
33
|
+
- **`uds uninstall` 在没有人能回答时不再画出提示或抛出错误堆栈,做不了事时也不再以 0 结束。** `--dry-run` 从不提示(它不写任何东西),并预览所有类别。没有 `--yes` 又没有终端时,实际运行会以退出码 2 拒绝,而不是假定“是”;提示被关闭时退出码为 130;项目未初始化时退出码为 1。
|
|
34
|
+
- **`uds init --with-hooks` 在 Windows 上不再打印 `'chmod' is not recognized`。** pre-commit hook 原本用 try/catch 包住的 `execSync("chmod +x ...")` 赋予执行权限;catch 对代码隐藏了失败,但 `execSync` 已先把 cmd.exe 的错误送到终端。现在改用 `fs.chmodSync`,并在没有执行位的 Windows 上跳过此步骤。
|
|
35
|
+
- **安全性:`uds init` 写入的 pre-commit hook 不再向 npm 要一个叫 `uds`、但不是本项目的包。** 该 hook 原本是单行 `npx uds check`。`npx` 先找 `node_modules/.bin` 与 `PATH`,两处都没有才去 npm registry,而 registry 上的 `uds` 是不相干的项目(维护者 wizawu、`github.com/wizawu/uds`、v0.3.6、2022 年后未更新、目前没有 `bin`)。装了 UDS 的机器不受影响;没装的 clone 则会用名称去抓陌生人的包——目前无害只是因为该包*尚*无可执行文件,对方一旦发布带 `uds` bin 的版本,每位采用者的每次 commit 都会执行它。`--no-install` 不是解法:用会记录每个请求的本机 registry 实测(npm 10.9.9、11.20.0、12.1.0,三者一致),`npx --no-install uds` 仍会发出 `GET /uds`,而 `npx --no-install --package=universal-dev-standards uds` 完全找不到全局安装。hook 现在两者都不用:它在项目的 `node_modules/.bin`、再到 `PATH` 找 `universal-dev-standards`(包本名,只有本项目能发布)并执行 `check`;两处都找不到时打印该装什么并以非 0 退出——不跳过检查、不下载任何东西,而且即使采用者自己的命令排在后面,检查失败也会拦下 commit。`uds uninstall` 依标记整块移除新写法。给人看的文字同样修正:生成的 `CLAUDE.md`/`AGENTS.md` 区块内的警告行与 hook 提示改写为 `npx universal-dev-standards init` / `update`(警告行多了几个 token,所以 `scripts/prompt-footprint-baseline.json` 依实测值各调高 3–6)。**既有采用者:**`uds update`(除了 `--skills`、`--commands`、`--integrations-only`、`--standards-only` 与 `--rollback` 之外的所有模式,以及 `--with-hooks`;`--plan` 只报告不写入)会替换 `.husky/pre-commit` 中 UDS 自己写的那一行——只认 UDS 曾生成过的两种确切写法(`npx uds check`,以及较早的 `npx uds check --standard checkin-standards`),且必须紧接在 `# UDS Standard Check` 标记下方;你自己写或改过的行不会被动,并会连同行号报告。此步骤在“已是最新版本”的提前返回**之前**执行,所以标准已是最新的采用者也会被处理。`uds check` 现在会警告仍使用裸名称的 hook。新增一个测试遍历 `npm pack` 出货的全部内容,只要有字符串以包运行器(npx、bunx、pnpm dlx、yarn dlx、npm exec)执行裸名称 `uds` 就会失败;非 Node 项目的原生 hook(`uds check`,只从 `PATH` 解析、不经 registry)不受影响,保持原样。
|
|
36
|
+
|
|
20
37
|
## [6.14.0-beta.2] - 2026-09-30
|
|
21
38
|
|
|
22
39
|
> **测试版**——以 `npm install -g universal-dev-standards@beta` 安装。要测什么、如何退回正式版:[docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
|
|
@@ -15,7 +15,7 @@ status: current
|
|
|
15
15
|
|
|
16
16
|
> **语言**: [English](../../README.md) | [繁體中文](../zh-TW/README.md) | 简体中文
|
|
17
17
|
|
|
18
|
-
**版本**: 6.14.0-beta.
|
|
18
|
+
**版本**: 6.14.0-beta.3 (Pre-release) | **发布日期**: 2026-09-30 | **授权**: [双重授权](../../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.14.0-beta.
|
|
4
|
-
translation_version: 6.14.0-beta.
|
|
3
|
+
source_version: 6.14.0-beta.3
|
|
4
|
+
translation_version: 6.14.0-beta.3
|
|
5
5
|
last_synced: 2026-09-30
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
@@ -17,6 +17,23 @@ status: current
|
|
|
17
17
|
|
|
18
18
|
## [Unreleased]
|
|
19
19
|
|
|
20
|
+
## [6.14.0-beta.3] - 2026-09-30
|
|
21
|
+
|
|
22
|
+
> **測試版**——以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、如何退回正式版:[docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
|
|
23
|
+
>
|
|
24
|
+
> **既有採用者:請執行一次 `uds update`。**舊版 `uds init` 寫進 pre-commit hook 的是單行 `npx uds check`,它可能請 npm 去拿一個叫 `uds`、但不是本專案的套件。新安裝不再寫這一行,而 `uds update` 會替換既有 `.husky/pre-commit` 裡 UDS 自己寫的那一行。另外:`uds uninstall` 只移除能證明是 UDS 寫的東西(早期 UDS 安裝的專案會保留一部分檔案並說明原因),`uds open-work next-action` 讀得懂寫成表格欄的下一步。
|
|
25
|
+
|
|
26
|
+
### 變更
|
|
27
|
+
|
|
28
|
+
- **`uds open-work next-action` 現在讀得懂寫成 Markdown 表格欄的「下一步」,不再只認小節標題與行內標籤。** 表頭在「下一步」詞彙內的欄(`Next action`、`Next step`、`下一步`、`下一動`,以及新加的 `回來要做什麼`)每一列都會被讀,回報帶行號與該列第一格內容,方便定位。詞彙仍然只有一份:標題、行內標籤與表頭都讀同一份。放在引用區塊(`> | … |`)裡的表格現在看得見;程式碼片段內或反斜線之後的 `|` 不再切開儲存格。欄數與表頭不一致的列會列為 `UNDECIDABLE`(判定不了),不會被當成空白;別處沒有違反時結束碼為 2,因為乾淨的結果只涵蓋了欄位的一部分。空白、`—`、`-` 的儲存格只計數、不評估,也不算 OWT-019 違反。此事是在真實工作紀錄上量測 DEC-122 H2 基準時發現的:80 列中有 32 列的欄數與表頭不同。`回來要做什麼` 是該工作紀錄實際使用的表頭(它的文字說明把該欄叫作 下一動),屬未校準判斷(OWT-016)。檢查器自己的突變測試新增八個表格突變,原有十七個仍然轉紅。
|
|
29
|
+
|
|
30
|
+
### 修正
|
|
31
|
+
|
|
32
|
+
- **`uds uninstall` 不再留下 UDS 自己寫的檔案,也不再移除它無法證明是 UDS 寫的東西。** 走過 `init` → `update --with-hooks` → `uninstall -y` 之後,它回報「已移除 5、已跳過 1、錯誤 0」,卻留下 `scripts/hooks/` 底下 15 個 hook 腳本、一個空的 `.codex/`、一份生成標頭仍指向已刪除 `.standards/` 的 AGENTS.md,以及 `uds init` 寫入的 `.git/hooks/pre-commit` 腳本主體。現在的規則是:整個檔案只有在 manifest 記錄了「UDS 寫的」(`installedArtifacts`,由 `init` 與 `update --with-hooks` 寫入)**而且**內容仍與記錄的雜湊相符時才會刪除。其他一律保留,並在輸出說明原因(`kept: modified since UDS wrote it`、`kept: no install record — ...`)。資料夾只有在 UDS 建立且現已為空時才移除;採用者自己的 `scripts/hooks/*.mjs`、`.agents/rules/*` 與 hook 項目都會保留。由舊版 UDS 安裝的專案沒有記錄,其腳本、AGENTS.md 生成文字與原生 pre-commit 主體會被保留並說明,而不是猜測。每一行「已移除」現在都對應一次真實的刪除或修改,帶著錯誤結束的執行也會以非 0 結束。
|
|
33
|
+
- **`uds uninstall` 在沒有人能回答時不再畫出提示或噴錯誤堆疊,做不了事時也不再以 0 結束。** `--dry-run` 從不提示(它不寫任何東西),並預覽所有類別。沒有 `--yes` 又沒有終端機時,實際執行會以結束碼 2 拒絕,而不是假定「是」;提示被關閉時結束碼為 130;專案未初始化時結束碼為 1。
|
|
34
|
+
- **`uds init --with-hooks` 在 Windows 上不再印出 `'chmod' is not recognized`。** pre-commit hook 原本用 try/catch 包住的 `execSync("chmod +x ...")` 賦予執行權限;catch 對程式碼藏起了失敗,但 `execSync` 已先把 cmd.exe 的錯誤送到終端機。現在改用 `fs.chmodSync`,並在沒有執行位元的 Windows 上略過此步驟。
|
|
35
|
+
- **安全性:`uds init` 寫入的 pre-commit hook 不再向 npm 要一個叫 `uds`、但不是本專案的套件。** 該 hook 原本是單行 `npx uds check`。`npx` 先找 `node_modules/.bin` 與 `PATH`,兩處都沒有才去 npm registry,而 registry 上的 `uds` 是不相干的專案(維護者 wizawu、`github.com/wizawu/uds`、v0.3.6、2022 年後未更新、目前沒有 `bin`)。裝了 UDS 的機器不受影響;沒裝的 clone 則會用名稱去抓陌生人的套件——目前無害只是因為該套件*尚*無可執行檔,對方一旦發布帶 `uds` bin 的版本,每位採用者的每次 commit 都會執行它。`--no-install` 不是解法:用會記錄每個請求的本機 registry 實測(npm 10.9.9、11.20.0、12.1.0,三者一致),`npx --no-install uds` 仍會發出 `GET /uds`,而 `npx --no-install --package=universal-dev-standards uds` 完全找不到全域安裝。hook 現在兩者都不用:它在專案的 `node_modules/.bin`、再到 `PATH` 找 `universal-dev-standards`(套件本名,只有本專案能發布)並執行 `check`;兩處都找不到時印出該裝什麼並以非 0 結束——不跳過檢查、不下載任何東西,而且即使採用者自己的指令排在後面,檢查失敗也會擋下 commit。`uds uninstall` 依標記整塊移除新寫法。給人看的文字同樣修正:產生的 `CLAUDE.md`/`AGENTS.md` 區塊內的警告行與 hook 提示改寫為 `npx universal-dev-standards init` / `update`(警告行多了幾個 token,所以 `scripts/prompt-footprint-baseline.json` 依實測值各調高 3–6)。**既有採用者:**`uds update`(除了 `--skills`、`--commands`、`--integrations-only`、`--standards-only` 與 `--rollback` 之外的所有模式,以及 `--with-hooks`;`--plan` 只回報不寫入)會替換 `.husky/pre-commit` 中 UDS 自己寫的那一行——只認 UDS 曾產生過的兩種確切寫法(`npx uds check`,以及較早的 `npx uds check --standard checkin-standards`),且必須緊接在 `# UDS Standard Check` 標記下方;你自己寫或改過的行不會被動,並會連同行號回報。此步驟在「已是最新版本」的提前返回**之前**執行,所以標準已是最新的採用者也會被處理。`uds check` 現在會警告仍使用裸名稱的 hook。新增一個測試走訪 `npm pack` 出貨的全部內容,只要有字串以套件執行器(npx、bunx、pnpm dlx、yarn dlx、npm exec)執行裸名稱 `uds` 就會失敗;非 Node 專案的原生 hook(`uds check`,只從 `PATH` 解析、不經 registry)不受影響,維持原樣。
|
|
36
|
+
|
|
20
37
|
## [6.14.0-beta.2] - 2026-09-30
|
|
21
38
|
|
|
22
39
|
> **測試版**——以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、如何退回正式版:[docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
|
|
@@ -15,7 +15,7 @@ status: current
|
|
|
15
15
|
|
|
16
16
|
> **語言**: [English](../../README.md) | 繁體中文 | [简体中文](../zh-CN/README.md)
|
|
17
17
|
|
|
18
|
-
**版本**: 6.14.0-beta.
|
|
18
|
+
**版本**: 6.14.0-beta.3 (Pre-release) | **發布日期**: 2026-09-30 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
|
|
19
19
|
|
|
20
20
|
語言無關、框架無關的軟體專案文件標準。透過 AI 原生工作流,確保不同技術堆疊之間的一致性、品質和可維護性。
|
|
21
21
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
source: ../../../core/open-work-tracking.md
|
|
3
3
|
source_version: 1.1.0
|
|
4
4
|
translation_version: 1.1.0
|
|
5
|
-
last_synced: 2026-09-
|
|
6
|
-
source_hash:
|
|
5
|
+
last_synced: 2026-09-30
|
|
6
|
+
source_hash: fb60809a8ff2
|
|
7
7
|
status: current
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -286,7 +286,7 @@ DEX-003 扮演的角色相同。上面每一條都指名了 artefact 與它們
|
|
|
286
286
|
是採用專案的決定——與 [deferred-item-exit](deferred-item-exit.md) 對自己出口劃的界線相同。
|
|
287
287
|
自 1.1.0 起,UDS 為 OWT-017–OWT-019 附上一支**參考判定程序**——npm 安裝包裡的 `uds open-work next-action | revision | separation`(`uds open-work self-test` 只跑檢查器自己的自測臂;在 UDS repo 的副本裡,`node scripts/check-open-work-tracking.mjs` 跑的是同一份程式)——
|
|
288
288
|
作為 OWT-015 意義上的證據——它已被觀察到對違反的樣本回報失敗——供採用者直接執行或自行重做。
|
|
289
|
-
它沒有接進任何 UDS 發版閘門,因為 UDS
|
|
289
|
+
它沒有接進任何 UDS 發版閘門,因為 UDS 本身沒有承載開放工作的地方可供它檢查。對 OWT-019,它以同一份詞彙讀三種形狀的「下一步」欄位:小節標題、行內標籤、以及表頭在該詞彙內的表格欄(該欄每一列各算一個欄位)。欄數與表頭不一致的表格列會被列為「判定不了」(不會被當成空白;若別處沒有違反,結束碼是 2,不是通過);空白、`—`、`-` 或已完成的儲存格只計數、不評估——它不算違反,因為 OWT-019 判斷的是「寫了的下一步有沒有點名對象」,沒寫是另一種失效,它不判定。
|
|
290
290
|
|
|
291
291
|
本標準做的事,是讓那個決定顯形:OWT-014 保證這裡每一條**能**被判定,OWT-015 固定
|
|
292
292
|
「一次判定要算數需要什麼」,OWT-005/OWT-011 固定「一次不完整的判定容許印出什麼」。
|
package/package.json
CHANGED
package/src/commands/check.js
CHANGED
|
@@ -1281,6 +1281,15 @@ export function checkPreCommitWiring(projectPath, msg) {
|
|
|
1281
1281
|
const result = checkPreCommitHookWiring(projectPath);
|
|
1282
1282
|
if (!result.relevant) return; // 沒有 UDS 管理的 hook
|
|
1283
1283
|
|
|
1284
|
+
// 與 wiring 無關的獨立缺陷面:hook 即使已接上,仍可能請 npm 去解析裸名稱 `uds`
|
|
1285
|
+
// ——npm registry 上那個名稱不是本專案。見 git-hooks.js buildPreCommitBlock。
|
|
1286
|
+
if (result.legacyBareUds) {
|
|
1287
|
+
console.log(chalk.yellow((msg.hookBareUdsTitle || '⚠ [pre-commit] {file} asks npm to run the bare name "uds", which on the npm registry is an unrelated package.')
|
|
1288
|
+
.replace('{file}', result.hookFile)));
|
|
1289
|
+
console.log(chalk.gray(msg.hookBareUdsFix || ' Fix: run `uds update`.'));
|
|
1290
|
+
console.log();
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1284
1293
|
if (result.wired) {
|
|
1285
1294
|
// 🔴 「已確認會執行」在 POSIX 上為真,在 Windows 上不一定——git for
|
|
1286
1295
|
// Windows 沒有 POSIX 的 ENOEXEC → /bin/sh 後備機制,缺 shebang 的 hook
|
package/src/commands/init.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { createSpinner } from '../utils/spinner.js';
|
|
3
|
-
import {
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'fs';
|
|
5
4
|
import { join } from 'path';
|
|
6
5
|
import {
|
|
7
6
|
manifestExists as isInitialized,
|
|
@@ -28,7 +27,9 @@ import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
|
28
27
|
import { readInstallYaml } from '../utils/config-manager.js';
|
|
29
28
|
import { resolveIntegrationTargetFile } from '../utils/integration-generator.js';
|
|
30
29
|
import { withFileTransaction } from '../utils/transaction.js';
|
|
31
|
-
import {
|
|
30
|
+
import { newRecorder, mkdirTracked, recordFile, persistRecorder, RECORD_KINDS } from '../core/install-records.js';
|
|
31
|
+
import { wireGitHooksPath, getLocalHooksPathConfig, stripLegacyHuskyShLine, ensureShebang, hookRunsUdsCheck, buildPreCommitBlock } from '../utils/git-hooks.js';
|
|
32
|
+
import { migrateLegacyHuskyHook } from '../utils/legacy-hook-migration.js';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Init command - initialize standards in current project
|
|
@@ -213,7 +214,11 @@ export async function initCommand(options) {
|
|
|
213
214
|
skillHashes: skillsResults.skillHashes,
|
|
214
215
|
commandHashes: skillsResults.commandHashes,
|
|
215
216
|
integrationBlockHashes: integrationResults.integrationBlockHashes,
|
|
216
|
-
manifestIntegrationConfigs: integrationResults.manifestIntegrationConfigs
|
|
217
|
+
manifestIntegrationConfigs: integrationResults.manifestIntegrationConfigs,
|
|
218
|
+
createdIntegrationFiles: [
|
|
219
|
+
...(integrationResults.createdFiles ?? []),
|
|
220
|
+
...(agentsMdResult.created ? [agentsMdResult.path] : [])
|
|
221
|
+
]
|
|
217
222
|
};
|
|
218
223
|
},
|
|
219
224
|
// A half-installed project must never be committed: if any installer
|
|
@@ -258,9 +263,13 @@ export async function initCommand(options) {
|
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
// 4.6. Install enforcement hooks (if --with-hooks)
|
|
266
|
+
// What this step writes is recorded in the manifest (installedArtifacts) so
|
|
267
|
+
// `uds uninstall` can remove exactly it — hook scripts, and the folders UDS had
|
|
268
|
+
// to create for them — and leave everything the adopter put there alone.
|
|
269
|
+
const installRecorder = newRecorder();
|
|
261
270
|
if (config.withHooks) {
|
|
262
271
|
const { installHooks } = await import('../installers/hooks-installer.js');
|
|
263
|
-
const hookResult = installHooks(projectPath);
|
|
272
|
+
const hookResult = installHooks(projectPath, { recorder: installRecorder });
|
|
264
273
|
if (hookResult.installed) {
|
|
265
274
|
console.log(chalk.green(
|
|
266
275
|
` ✓ Enforcement hooks installed (${hookResult.scriptsCount} scripts, `
|
|
@@ -292,7 +301,7 @@ export async function initCommand(options) {
|
|
|
292
301
|
const selectedTools = config.integrations || config.aiTools || [];
|
|
293
302
|
if (selectedTools.includes('codex')) {
|
|
294
303
|
const { installCodexHooks } = await import('../installers/hooks-installer.js');
|
|
295
|
-
const codexResult = installCodexHooks(projectPath);
|
|
304
|
+
const codexResult = installCodexHooks(projectPath, { recorder: installRecorder });
|
|
296
305
|
if (codexResult.installed) {
|
|
297
306
|
console.log(chalk.green(' ✓ Codex Stop hook installed (turn-completion-integrity)'));
|
|
298
307
|
// Measured 2026-09-28 (codex-cli 0.155.1): Codex skips a project hook
|
|
@@ -306,7 +315,7 @@ export async function initCommand(options) {
|
|
|
306
315
|
}
|
|
307
316
|
if (selectedTools.includes('gemini-cli')) {
|
|
308
317
|
const { installGeminiHooks } = await import('../installers/hooks-installer.js');
|
|
309
|
-
const geminiResult = installGeminiHooks(projectPath);
|
|
318
|
+
const geminiResult = installGeminiHooks(projectPath, { recorder: installRecorder });
|
|
310
319
|
if (geminiResult.installed) {
|
|
311
320
|
console.log(chalk.green(' ✓ Gemini CLI AfterAgent hook installed (turn-completion-integrity; Gemini CLI is legacy — see the standard)'));
|
|
312
321
|
} else {
|
|
@@ -315,7 +324,7 @@ export async function initCommand(options) {
|
|
|
315
324
|
}
|
|
316
325
|
if (selectedTools.includes('antigravity')) {
|
|
317
326
|
const { installAgyHooks } = await import('../installers/hooks-installer.js');
|
|
318
|
-
const agyResult = installAgyHooks(projectPath);
|
|
327
|
+
const agyResult = installAgyHooks(projectPath, { recorder: installRecorder });
|
|
319
328
|
if (agyResult.installed) {
|
|
320
329
|
console.log(chalk.green(' ✓ Antigravity CLI Stop hook installed (turn-completion-integrity; .agents/hooks.json)'));
|
|
321
330
|
// Verified 2026-09-29 (agy 1.2.12): single turn, no tool calls, `agy -p`.
|
|
@@ -328,6 +337,15 @@ export async function initCommand(options) {
|
|
|
328
337
|
}
|
|
329
338
|
}
|
|
330
339
|
|
|
340
|
+
// 4.7. Record the integration files UDS created from nothing, and the hook
|
|
341
|
+
// files/folders written above. Their text outside the UDS marker block (the
|
|
342
|
+
// `> Auto-generated by ...` header, the generated sections) is what makes
|
|
343
|
+
// "delete the whole file" provable at uninstall time.
|
|
344
|
+
for (const created of combinedResults.createdIntegrationFiles ?? []) {
|
|
345
|
+
recordFile(installRecorder, projectPath, created, RECORD_KINDS.INTEGRATION_FILE);
|
|
346
|
+
}
|
|
347
|
+
persistRecorder(projectPath, installRecorder);
|
|
348
|
+
|
|
331
349
|
// 5. Setup Pre-commit Hook
|
|
332
350
|
await setupHuskyHook(projectPath);
|
|
333
351
|
|
|
@@ -339,7 +357,7 @@ export async function initCommand(options) {
|
|
|
339
357
|
* - Node.js projects: use husky
|
|
340
358
|
* - Non-Node.js projects: write native .git/hooks/pre-commit
|
|
341
359
|
*/
|
|
342
|
-
export async function setupHuskyHook(projectPath, { allowInTest = false } = {}) {
|
|
360
|
+
export async function setupHuskyHook(projectPath, { allowInTest = false, platform = process.platform } = {}) {
|
|
343
361
|
// 2026-07-30:本函式在跑測試時改寫了 universal-dev-standards 自己
|
|
344
362
|
// ——`npm install --save-dev husky`、改 package.json、建 .husky/pre-commit。
|
|
345
363
|
//
|
|
@@ -359,6 +377,9 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
359
377
|
|
|
360
378
|
const isNodeProject = existsSync(join(projectPath, 'package.json'));
|
|
361
379
|
|
|
380
|
+
// What this function creates, for `uds uninstall` (see core/install-records.js).
|
|
381
|
+
const recorder = newRecorder();
|
|
382
|
+
|
|
362
383
|
if (isNodeProject) {
|
|
363
384
|
console.log(chalk.cyan('Configuring Pre-commit Hook...'));
|
|
364
385
|
|
|
@@ -436,7 +457,7 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
436
457
|
// 3. Ensure .husky directory exists
|
|
437
458
|
if (!existsSync(huskyDir)) {
|
|
438
459
|
try {
|
|
439
|
-
|
|
460
|
+
mkdirTracked(recorder, projectPath, huskyDir);
|
|
440
461
|
} catch (e) {
|
|
441
462
|
console.log(chalk.red(` ✗ Failed to create .husky directory: ${e.message}`));
|
|
442
463
|
return;
|
|
@@ -445,8 +466,10 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
445
466
|
|
|
446
467
|
// 4. Add pre-commit hook content
|
|
447
468
|
const preCommitPath = join(huskyDir, 'pre-commit');
|
|
448
|
-
const udsCmd = 'npx uds check';
|
|
449
469
|
|
|
470
|
+
// Only a hook file UDS creates from nothing can later be proven to be wholly
|
|
471
|
+
// its own; one that already existed is appended to and stays the adopter's.
|
|
472
|
+
const preCommitExisted = existsSync(preCommitPath);
|
|
450
473
|
try {
|
|
451
474
|
// husky v9's own templates carry no shebang, no `_/husky.sh` sourcing
|
|
452
475
|
// (that is v8 syntax, deprecated in v9 and removed in v10). Verified
|
|
@@ -464,6 +487,18 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
464
487
|
// — cheap, harmless on POSIX, and required on Windows. Existing files
|
|
465
488
|
// are appended to, never rewritten — their contents are the adopter's,
|
|
466
489
|
// not ours; ensureShebang only ever prepends a missing first line.
|
|
490
|
+
// A hook an older UDS wrote asks npx for the bare name `uds`, which on the
|
|
491
|
+
// npm registry is an unrelated package (see buildPreCommitBlock). Swap the
|
|
492
|
+
// line UDS itself wrote for the current block before anything else reads
|
|
493
|
+
// the file; a line the adopter edited is left alone and reported below.
|
|
494
|
+
const legacy = existsSync(preCommitPath) ? migrateLegacyHuskyHook(projectPath) : null;
|
|
495
|
+
if (legacy?.state === 'migrated') {
|
|
496
|
+
console.log(chalk.green(' ✓ Replaced the pre-commit line an older UDS wrote (it asked npm to resolve the bare name "uds", which is not this project) with one that runs only the installed UDS CLI'));
|
|
497
|
+
}
|
|
498
|
+
for (const k of legacy?.kept ?? []) {
|
|
499
|
+
console.log(chalk.yellow(` ⚠ .husky/pre-commit line ${k.line} ("${k.text}") asks npm to resolve the bare name "uds", which on the npm registry is an unrelated package. UDS did not write this exact line, so it was left alone — change it to "universal-dev-standards check" (with the CLI installed in this project or on PATH).`));
|
|
500
|
+
}
|
|
501
|
+
|
|
467
502
|
let content = existsSync(preCommitPath) ? readFileSync(preCommitPath, 'utf-8') : '';
|
|
468
503
|
|
|
469
504
|
// A pre-existing file may still carry husky v8's `_/husky.sh` sourcing
|
|
@@ -479,10 +514,10 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
479
514
|
const { content: destripped, removed: hadLegacyLine } = stripLegacyHuskyShLine(content);
|
|
480
515
|
content = destripped;
|
|
481
516
|
|
|
482
|
-
const needsAppend = !content
|
|
517
|
+
const needsAppend = !hookRunsUdsCheck(content);
|
|
483
518
|
if (needsAppend) {
|
|
484
519
|
const sep = content && !content.endsWith('\n') ? '\n' : '';
|
|
485
|
-
content = `${content}${sep}\n
|
|
520
|
+
content = `${content}${sep}\n${buildPreCommitBlock()}`;
|
|
486
521
|
}
|
|
487
522
|
|
|
488
523
|
const { content: shebanged, added: addedShebang } = ensureShebang(content);
|
|
@@ -490,10 +525,22 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
490
525
|
|
|
491
526
|
if (hadLegacyLine || needsAppend || addedShebang) {
|
|
492
527
|
writeFileSync(preCommitPath, content, 'utf-8');
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
528
|
+
if (!preCommitExisted) recordFile(recorder, projectPath, '.husky/pre-commit', RECORD_KINDS.GIT_HOOK);
|
|
529
|
+
// 🔴 Was `execSync(`chmod +x ...`)` inside try/catch. On Windows that
|
|
530
|
+
// spawned cmd.exe, which printed "'chmod' is not recognized as an
|
|
531
|
+
// internal or external command" straight to the user's terminal —
|
|
532
|
+
// execSync inherits the child's stderr by default, so the catch hid the
|
|
533
|
+
// failure from the code and left it loud for the human (adopter report,
|
|
534
|
+
// 6.14.0-beta.2, Windows 11). No shell is needed to change a mode, and
|
|
535
|
+
// Windows has no exec bit to set (git for Windows runs the hook through
|
|
536
|
+
// its own sh regardless), so it is skipped there.
|
|
537
|
+
if (platform !== 'win32') {
|
|
538
|
+
try {
|
|
539
|
+
chmodSync(preCommitPath, 0o755);
|
|
540
|
+
} catch {
|
|
541
|
+
// A filesystem that refuses the mode change (FAT, some mounts) is not
|
|
542
|
+
// a reason to fail the install; git reports a non-executable hook itself.
|
|
543
|
+
}
|
|
497
544
|
}
|
|
498
545
|
}
|
|
499
546
|
|
|
@@ -512,6 +559,8 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
512
559
|
console.log(chalk.red(` ✗ Failed to configure pre-commit hook: ${e.message}`));
|
|
513
560
|
}
|
|
514
561
|
|
|
562
|
+
persistRecorder(projectPath, recorder);
|
|
563
|
+
|
|
515
564
|
// 5. Wire git so the hook ACTUALLY runs — this is the fix. Setting
|
|
516
565
|
// `core.hooksPath` ourselves is what husky's own bootstrap does internally
|
|
517
566
|
// (verified against husky ^9.1.7's source: `git config core.hooksPath
|
|
@@ -567,7 +616,7 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
567
616
|
|
|
568
617
|
if (existsSync(hookPath)) {
|
|
569
618
|
const existingContent = readFileSync(hookPath, 'utf-8');
|
|
570
|
-
if (existingContent
|
|
619
|
+
if (hookRunsUdsCheck(existingContent)) {
|
|
571
620
|
console.log(chalk.gray(' ✓ Pre-commit hook already configured'));
|
|
572
621
|
} else {
|
|
573
622
|
// Never clobber an adopter's own hook (this fix — it used to be
|
|
@@ -597,6 +646,10 @@ uds check 2>/dev/null || true
|
|
|
597
646
|
echo "Pre-commit checks passed"
|
|
598
647
|
`;
|
|
599
648
|
writeFileSync(hookPath, hookContent, { mode: 0o755 });
|
|
649
|
+
// Created from nothing (the branch above is "file does not exist"), so the
|
|
650
|
+
// whole file is UDS's — record its hash for `uds uninstall`.
|
|
651
|
+
recordFile(recorder, projectPath, '.git/hooks/pre-commit', RECORD_KINDS.GIT_HOOK);
|
|
652
|
+
persistRecorder(projectPath, recorder);
|
|
600
653
|
console.log(chalk.green(' ✓ Installed .git/hooks/pre-commit (native git hook)'));
|
|
601
654
|
}
|
|
602
655
|
} catch (e) {
|