universal-dev-standards 6.13.0-beta.2 → 6.13.0-beta.5

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.
@@ -3,8 +3,8 @@
3
3
 
4
4
  id: turn-completion-integrity
5
5
  meta:
6
- version: "1.4.0"
7
- updated: "2026-09-25"
6
+ version: "1.4.1"
7
+ updated: "2026-09-28"
8
8
  source: core/turn-completion-integrity.md
9
9
  description: An agent must not end a turn having stated a next action it did not take; enforced at turn end, not by instruction
10
10
  related:
@@ -134,12 +134,18 @@ supported_harnesses:
134
134
  config_file: ".codex/hooks.json"
135
135
  block_shape: '{"decision":"block","reason":...}, exit 0 — plain text or empty stdout documented as invalid for this event'
136
136
  script: scripts/hooks/check-turn-completion-codex.mjs
137
+ activation: "Codex skips a project hook until the project is trusted AND the exact hook definition is trusted via /hooks (trust keyed to the definition's hash). Under codex exec an untrusted hook is skipped silently. Measured 2026-09-28, codex-cli 0.155.1"
137
138
  known_limit: "R9 (human-directed stop exemption) is best-effort — Codex gives the assistant's last message directly but not the human's; a failed transcript read leaves the human side empty rather than skipping detection"
138
139
  - harness: gemini-cli
139
140
  event: AfterAgent
140
141
  config_file: ".gemini/settings.json"
141
142
  block_shape: '{"decision":"deny","reason":...}, exit 0 — the documented preferred path over exit code 2'
142
143
  script: scripts/hooks/check-turn-completion-gemini.mjs
144
+ status: legacy
145
+ reason: "Google retired Gemini CLI for personal accounts on 2026-06-18 in favour of Antigravity CLI; enterprise accounts keep both. Adapter kept for them; never confirmed against a real Gemini CLI session"
146
+ - harness: antigravity-cli
147
+ status: not-yet-supported
148
+ reason: "Documented Stop contract differs: config .agents/hooks.json, payload has only transcriptPath (no final or human message), block is {\"decision\":\"continue\",\"reason\":...}. Adapter waits until that contract is observed against a real session (R3)"
143
149
  - harness: cursor
144
150
  status: evaluated-not-supported
145
151
  reason: "Whether Cursor's stop hook can actually block a turn was unresolved as of writing; shipping an adapter against an unverified contract repeats the exact failure R3 exists to prevent"
@@ -2,8 +2,8 @@
2
2
 
3
3
  > **Language**: English | [繁體中文](../locales/zh-TW/core/turn-completion-integrity.md)
4
4
 
5
- **Version**: 1.4.0
6
- **Last Updated**: 2026-09-25
5
+ **Version**: 1.4.1
6
+ **Last Updated**: 2026-09-28
7
7
  **Applicability**: Any harness where an agent ends a turn and hands control back to a human
8
8
  **Scope**: universal
9
9
  **Industry Standards**: none claimed — derived from observed failures, see Evidence
@@ -149,13 +149,22 @@ prevent, one level up.
149
149
  ## Supported harnesses
150
150
 
151
151
  The check is enforced only where a harness adapter exists and a hook is
152
- actually wired into that harness's own config. As of v1.4.0:
152
+ actually wired into that harness's own config. As of v1.4.1:
153
153
 
154
154
  | Harness | Event | Config file | Block contract |
155
155
  |---|---|---|---|
156
156
  | Claude Code | Stop | `.claude/settings.json` | stdout `{"decision":"block","reason":...}`, exit 0; silence allows |
157
157
  | Codex | Stop | `.codex/hooks.json` | stdout `{"decision":"block","reason":...}`, exit 0 — plain text or empty stdout is documented as invalid for this event |
158
- | Gemini CLI | AfterAgent | `.gemini/settings.json` | stdout `{"decision":"deny","reason":...}`, exit 0 — the documented preferred path over exit code 2 |
158
+ | Gemini CLI (legacy) | AfterAgent | `.gemini/settings.json` | stdout `{"decision":"deny","reason":...}`, exit 0 — the documented preferred path over exit code 2 |
159
+
160
+ Wired is not running on Codex. Codex skips a project hook until the project
161
+ is trusted **and** that exact hook definition has been trusted through `/hooks`
162
+ in an interactive Codex session; trust is recorded against the definition's
163
+ hash, so a changed definition needs trusting again. Under `codex exec` an
164
+ untrusted hook is skipped with no message at all (measured 2026-09-28,
165
+ codex-cli 0.155.1: the adapter was never invoked until the hook was trusted,
166
+ then blocked and allowed exactly as its tests say). `uds init --with-hooks`
167
+ prints this next to the install line.
159
168
 
160
169
  Codex's R9 exemption is best-effort, not silent failure: Codex's Stop payload
161
170
  gives the assistant's final message directly but not the human's, so reading
@@ -172,6 +181,18 @@ shape) still leaves the human side empty rather than throwing — detection
172
181
  still runs on the assistant's message, only the R9 exemption for that one
173
182
  turn may be missed.
174
183
 
184
+ Gemini CLI is legacy. Google retired it for personal accounts on
185
+ 2026-06-18 in favour of Antigravity CLI (`agy`); enterprise accounts keep
186
+ access to both. The adapter stays for those users, but it has never been
187
+ confirmed against a real Gemini CLI session, and new adopters on Google's
188
+ tooling should expect Antigravity CLI, which is **not yet supported**. Its
189
+ documented Stop hook contract differs from every adapter above in the ways
190
+ that matter: the hook is configured in `.agents/hooks.json`, the payload
191
+ carries only a `transcriptPath` (no final message, no human message), and a
192
+ block is `{"decision":"continue","reason":...}`, not `block` or `deny`. An
193
+ adapter will be added once that contract has been observed against a real
194
+ session — the same reason Cursor below has none.
195
+
175
196
  Cursor was evaluated and is not supported: whether its stop hook can actually
176
197
  block a turn in the way this standard requires was unresolved as of this
177
198
  writing, and shipping an adapter against an unverified contract would repeat
@@ -6,7 +6,8 @@
6
6
  * commitment to a next action that the turn then ended without taking.
7
7
  *
8
8
  * Contract (Claude Code Stop hook):
9
- * stdin — JSON with session_id, transcript_path, stop_hook_active
9
+ * stdin — JSON with session_id, transcript_path, stop_hook_active, and
10
+ * (current Claude Code) last_assistant_message
10
11
  * block — print {"decision":"block","reason":"..."} on stdout, exit 0
11
12
  * allow — print nothing, exit 0
12
13
  *
@@ -21,6 +22,17 @@
21
22
  * is reading Claude Code's stdin/transcript shape and writing Claude Code's
22
23
  * output shape.
23
24
  *
25
+ * The agent's final message is taken from stdin's `last_assistant_message`
26
+ * when present, NOT from the transcript. Measured 2026-09-28 against Claude
27
+ * Code 2.1.283 in a live `claude -p` session: at the moment the Stop hook runs,
28
+ * the transcript does not yet contain the final assistant message — the
29
+ * adapter read "" and allowed 5 of 5 turns that should have blocked. (In a
30
+ * longer session it would have read the PREVIOUS turn's message instead.)
31
+ * Every self-test and unit test passed throughout, because they all hand the
32
+ * adapter a transcript that is already complete. The transcript is still read
33
+ * for the human's side (R9), which is written at the start of the turn, and
34
+ * as the fallback for Claude Code versions that do not send the field.
35
+ *
24
36
  * Usage: node check-turn-completion.mjs (reads stdin)
25
37
  * node check-turn-completion.mjs --self-test
26
38
  * node check-turn-completion.mjs --languages
@@ -82,15 +94,19 @@ async function main() {
82
94
  if (!data || typeof data !== 'object') return;
83
95
  if (data.stop_hook_active === true) return;
84
96
 
85
- const tp = data.transcript_path;
86
- if (!tp || !existsSync(tp)) return; // cannot tell is not the same as should block
97
+ const fromStdin = typeof data.last_assistant_message === 'string' ? data.last_assistant_message : null;
87
98
 
88
- let msgs;
89
- try { msgs = lastMessages(tp); } catch { return; }
99
+ const tp = data.transcript_path;
100
+ let msgs = { assistant: '', user: '' };
101
+ if (tp && existsSync(tp)) {
102
+ try { msgs = lastMessages(tp); } catch { /* human side unknown; see below */ }
103
+ } else if (fromStdin === null) {
104
+ return; // no message from either source: cannot tell is not the same as should block
105
+ }
90
106
 
91
107
  const verdict = await decide({
92
108
  sessionId: data.session_id,
93
- assistantText: msgs.assistant,
109
+ assistantText: fromStdin !== null ? fromStdin : msgs.assistant,
94
110
  userText: msgs.user,
95
111
  });
96
112
  if (!verdict.fire) return;
@@ -14,7 +14,7 @@
14
14
  import { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
15
15
  import { homedir } from 'node:os';
16
16
  import { join, dirname } from 'node:path';
17
- import { fileURLToPath } from 'node:url';
17
+ import { fileURLToPath, pathToFileURL } from 'node:url';
18
18
  import { detectCommitment, userAskedToStop } from './detect.mjs';
19
19
 
20
20
  export const VERSION = '1.2.0';
@@ -53,7 +53,16 @@ export async function loadPacks() {
53
53
  const failed = [];
54
54
  for (const id of SHIPPED_LOCALES) {
55
55
  try {
56
- packs.push(await import(join(HERE, 'locales', `${id}.mjs`)));
56
+ // A filesystem path (`C:\...` on Windows) is not a valid ESM import
57
+ // specifier — dynamic `import()` needs a `file://` URL there. On
58
+ // POSIX both happen to look like absolute paths that Node accepts, so
59
+ // this went unnoticed until measured 2026-09-27 in CI
60
+ // (windows-latest): every pack failed to load, `failed` was silently
61
+ // non-empty, and every adapter test that expects a block/deny decision
62
+ // got `undefined` instead — the hook ran, found no packs, and let
63
+ // every turn end uninspected. pathToFileURL(...).href is the one
64
+ // form valid on every platform.
65
+ packs.push(await import(pathToFileURL(join(HERE, 'locales', `${id}.mjs`)).href));
57
66
  } catch (e) {
58
67
  failed.push({ id, why: String((e && e.message) || e) });
59
68
  }
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  source: ../../CHANGELOG.md
3
- source_version: 6.13.0-beta.2
4
- translation_version: 6.13.0-beta.2
5
- last_synced: 2026-09-26
3
+ source_version: 6.13.0-beta.5
4
+ translation_version: 6.13.0-beta.5
5
+ last_synced: 2026-09-27
6
6
  status: current
7
7
  ---
8
8
 
@@ -17,6 +17,40 @@ status: current
17
17
 
18
18
  ## [Unreleased]
19
19
 
20
+ ## [6.13.0-beta.5] - 2026-09-28
21
+
22
+ > **测试版** — 以 `npm install -g universal-dev-standards@beta` 安装。要测什么、已知限制、如何退回正式版:见 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。**装过 6.13.0-beta.1~beta.4 的请升级:那几版的 Claude Code 回合收尾关卡从未拦下过。**
23
+
24
+ ### 修复
25
+
26
+ - **Claude Code 的回合收尾关卡在真实的 Claude Code 会话里从未拦下过任何回合——6.13.0-beta.1 到 beta.4 的每个采用者都接上了、也在执行,而它放行了每一个回合。** 2026-09-28 以 Claude Code 2.1.283 在真实 `claude -p` 会话实测:Stop hook 执行的那一刻,对话记录里还没有最后一条 AI 回复,因此只读对话记录的适配层看到空消息,应拦的 5 次全部放行;在较长的对话里,它判断的会是上一轮的回复。自我测试与单元测试从头到尾都通过,因为每一个都喂给适配层一份已经写完的对话记录。适配层现在改从 stdin 的 `last_assistant_message`(Claude Code 会发送)取得最后一条回复,对话记录只用来读人的那一侧(R9),以及在不发送这个字段的版本上作为备援。修复后已再次实测。新测试重现关卡当下实际看到的形状,在旧适配层上会失败。
27
+ - **Codex 的关卡装上了却从未执行,而且没有任何提示。** Codex 会跳过项目级的 hook,直到项目被信任、且这一支 hook 通过 `/hooks` 被信任为止——在 `codex exec` 下完全无声。`uds init --with-hooks` 现在会在 Codex 安装那一行旁边说明这件事,标准的〈支持的执行环境〉与 `docs/PRE-RELEASE.md` 也补上这个步骤。信任之后,Codex 适配层的拦截与放行与它的测试完全一致(2026-09-28 实测,codex-cli 0.155.1)。
28
+
29
+ ### 变更
30
+
31
+ - **`turn-completion-integrity` 1.4.1:Gemini CLI 适配层标为过时,并明列 Antigravity CLI 尚未支持。** Google 于 2026-06-18 对个人账号停用 Gemini CLI,改由 Antigravity CLI(`agy`)取代;企业账号两者都还能用。适配层为他们保留,但从未在真实的 Gemini CLI 会话中验证过,因此不再与 Claude Code、Codex 适配层并列。Antigravity CLI 文档记载的 Stop hook 契约与所有已发布的适配层都不同(配置在 `.agents/hooks.json`;传入数据只有 `transcriptPath`,没有最后一条回复或人的消息;拦截是 `{"decision":"continue"}`),因此在真实会话中观察到这份契约之前不发布适配层——与 Cursor 不支持是同一条规则。行为不变:选了 Gemini CLI 时,`uds init --with-hooks` 仍会接上它的关卡。
32
+
33
+ ## [6.13.0-beta.4] - 2026-09-28
34
+
35
+ > **测试版** — 以 `npm install -g universal-dev-standards@beta` 安装。要测什么、已知限制、如何退回正式版:见 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。注意:6.13.0-beta.3 从未上架 npm,其内容随本版发布。
36
+
37
+ ### 修复
38
+
39
+ - **`turn-completion-integrity` 的 Stop hook 在 Windows 上一路到 6.13.0-beta.3 都静默失效——它照跑、找不到任何语言包、然后放行每一轮对话,且什么都不打印。** `engine.mjs` 的 `loadPacks()` 用 `join(HERE, 'locales', ...)` 拼出每个语言包的路径,直接把这个文件系统路径交给动态 `import()`;在 Windows 上那是 `C:\...` 这种路径,不是合法的 ESM import 指定字符串(POSIX 上的绝对路径恰好也能被解析成合法指定字符串,这正是为何在 macOS/Linux 上从未被发现)。2026-09-27 于 CI(windows-latest)实测:每一个出货的语言包都加载失败,而每一个原本该回 `block`/`deny` 决策的适配层(Claude Code、Codex、Gemini CLI)全部返回 `undefined`。修复方式改用 `pathToFileURL(...).href`,与 `cli/src/utils/standard-fixer.js`/`standard-validator.js` 既有的正确写法一致。四支 `scripts/check-*.ts` 开发工具脚本有同样的写法(Windows CI job 不会跑到它们,因为它们只在 `ubuntu-latest` 上执行,但那里同样是坏的),一并以同样方式修正。新增一支全 repo 走查测试(`cli/tests/unit/scripts/no-fs-path-dynamic-import.test.js`)扫描 `scripts/`、`cli/src/`、`cli/scripts/` 找这个写法,未来新增的一处不需要有人记得这次事故也会被挡下。
40
+ - **由 `uds init` 写入的 husky 管理 `.husky/pre-commit`,即使 `core.hooksPath` 已正确接好,在 Windows 上一路到 6.13.0-beta.3 都会让每一次提交失败,错误是 `error: cannot spawn .husky/pre-commit: No such file or directory`。** husky v9 自己的模板没有 shebang 行,这在 macOS/Linux 上一直能用,因为 POSIX git 在脚本没有 shebang 时(`ENOEXEC`)会回退用 `/bin/sh` 执行;git for Windows 没有这个后备机制,完全无法对没有 shebang 的文件 spawn,而且错误信息指向 hook 文件本身而非缺失的解释器——很容易被误判成 wiring 问题而非内容问题。`uds init` 现在会在缺少 shebang 时,于 husky 管理的 hook 最前面补上 `#!/bin/sh`,不分平台一律如此,无论是写新 hook 还是动到既有的采用者文件(只会插入,绝不重写采用者自己的 shebang 或任何其他行)。`uds check` 的 `[pre-commit]` 警告新增 `missingShebang` 信号,独立于 wiring 报告(一个 hook 可以完全接好但在 Windows 上仍因此失败),且维持既有设计,只读不写。
41
+
42
+ ## [6.13.0-beta.3] - 2026-09-27
43
+
44
+ > **测试版** — 以 `npm install -g universal-dev-standards@beta` 安装。要测什么、已知限制、如何退回正式版:见 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
45
+
46
+ ### Fixed
47
+
48
+ - **`uds init` 会写入提交前检查(`.husky/pre-commit`,非 Node 项目则为 `.git/hooks/pre-commit`),却从未确认 git 真的会执行它。** 它依赖 husky 自己的 bootstrap 机制——`npm install` 触发 husky 的 `prepare` script 去设定 `core.hooksPath`——而这个时机只在**下一次** `npm install` 执行时才会发生;如果 `node_modules` 早就存在,这件事就永远不会发生。实测三个既有采用者(asiaostrich-telemetry-server、asiaostrich-telemetry-client、machine-setup,2026-09-26):三者都有调用 `npx uds check` 的 `.husky/pre-commit`,但 `core.hooksPath` 均未设定,提交时检查从未跑过——而且完全没有任何错误信息。`uds init` 现在不再替用户安装 husky 或改动 package.json 的依赖;改为直接执行 `git config --local core.hooksPath .husky`(与 husky 自己 bootstrap 内部所做的事完全相同),让检查在 `uds init` 执行完就立刻生效,无论 husky 有没有安装。绝不覆盖用户既有的 `core.hooksPath`,或既有的 `.git/hooks/pre-commit`——两者都会被保留原状,并打印信息说明检查**未启用**及原因。非 Node 项目的原生 hook 路径也不再无条件覆写既有的 `.git/hooks/pre-commit`(过去会)。新增 `uds check` 警告 `[pre-commit]`:检测 UDS 写入的检查文件存在,但实际不在 git 真正会执行的路径上(涵盖直接设定、husky 自己的 `<dir>/_` shim 转发、以及原生默认路径三种形状),并附上修复方式——这就是像上述三个既有采用者这样“已经中招”的项目能发现问题的渠道。此警告仅提示、不影响 `uds check --ci` 的退出码,因为这是每个 clone 各自的本机设定落差,不是标准本身不合规。`core.hooksPath` 不会进版控,所以这道启用只对执行 `uds init` 的那个 clone 生效——信息与新警告都会说明这一点。
49
+
50
+ - **后续修正(2026-09-27):上面那个修法,若既有采用者手上的 `.husky/pre-commit` 还是 husky v8 旧模板(含 `_/husky.sh` 那一行),照做反而会让每一次提交都失败。** 实测其中一个既有采用者的一次性 clone:照 `uds check` 原本建议的修法(`git config --local core.hooksPath .husky`)执行,结果打印 `.husky/pre-commit: line 2: .husky/_/husky.sh: No such file or directory`,`git commit` 以 exit 1 失败——比原本“静默不跑”的缺陷更糟,因为 `.husky/_/` 这个目录只有在 husky 自己的 bootstrap 真的跑过后才存在,而直接把 `core.hooksPath` 设成 `.husky` 会让 git 原封不动地执行这个文件。`uds init` 的 `setupHuskyHook` 现在会检测并移除这一行后再改写 `.husky/pre-commit`(其余内容——用户自己加的命令、既有的 `uds check` 那一行——全部保留);`uds check` 的 `[pre-commit]` 警告检测到这种旧模板时,不再只单独建议设定 hooksPath,改为给出“先删那一行、再设定 hooksPath”的两步修法——因为 `uds init` 对已初始化的项目会直接拒绝执行,修不了这三个既有采用者的问题。`git-hooks.js` 新增共用函数 `hasLegacyHuskyShLine`/`stripLegacyHuskyShLine`。
51
+
52
+ - **`bump-version.mjs` 在“预发布→预发布”的版本升版时,把 `SECURITY.md`“最新正式版”那一行标错——实测发生于 6.13.0-beta.2 发版当下(2026-09-26),当时以手动更正。** 它的 `SECURITY.md` 修补逻辑找“裸版号(无后缀)那一行”来认定是正式版行;新的预发布版号(如 `6.13.0-beta.2`)永远带着连字符、永远不会符合“裸版号”,于是修补逻辑退而求其次改到唯一真正裸版号的那一行——也就是不相关的正式版行——把它的版号换成新的预发布版号,而原本该更新、已经过期的预发布行(仍是 `6.13.0-beta.1`)反而原封不动。已将产生表格的逻辑(`generate-docs.mjs` 原本就写对、但 `bump-version.mjs` 未使用)抽成共用的 `scripts/lib/security-versions.mjs`,两支脚本现在都改成用 `(version, stableVersion)` 整段重新产生 2 或 3 行的表格,而不是找一行去 patch——已针对全部四种版本类型转换(正式→正式、正式→预发布、预发布→预发布、预发布→正式)、三种语言,通过对隔离副本执行一次真正的端到端 `bump-version.mjs` 验证正确。`check-version-sync.sh` 原本的 SECURITY.md 检查只比对第一行数据的版号是否等于 `package.json`(一种位置代理,恰好抓到了这次事故);现在还会逐行比对“标签”与“该行版号的形状”是否吻合(“最新正式版/Latest stable”行若版号带连字符、或“预发布版本/Pre-release”行若版号不带连字符,即使位置检查会通过,仍会被标记为错误)。
53
+
20
54
  ## [6.13.0-beta.2] - 2026-09-26
21
55
 
22
56
  > **测试版** — 以 `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.13.0-beta.2 (Pre-release) | **发布日期**: 2026-09-26 | **授权**: [双重授权](../../LICENSE) (CC BY 4.0 + MIT)
18
+ **版本**: 6.13.0-beta.5 (Pre-release) | **发布日期**: 2026-09-28 | **授权**: [双重授权](../../LICENSE) (CC BY 4.0 + MIT)
19
19
 
20
20
  语言无关、框架无关的软件项目文档标准。通过 AI 原生工作流,确保不同技术栈之间的一致性、质量和可维护性。
21
21
 
@@ -13,7 +13,7 @@ status: current
13
13
  <!-- UDS_SUPPORTED_VERSIONS_START -->
14
14
  | 版本 | 支持状态 |
15
15
  |------|--------|
16
- | 6.13.0-beta.2 | ✅ 预发布版本 |
16
+ | 6.13.0-beta.5 | ✅ 预发布版本 |
17
17
  | 6.12.0 | ✅ 最新正式版 |
18
18
  | < 6.0.0 | ❌ 已终止支持 |
19
19
  <!-- UDS_SUPPORTED_VERSIONS_END -->
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  source: ../../../core/turn-completion-integrity.md
3
- source_version: 1.4.0
4
- translation_version: 1.4.0
5
- last_synced: 2026-09-26
6
- source_hash: 401b74843abc
3
+ source_version: 1.4.1
4
+ translation_version: 1.4.1
5
+ last_synced: 2026-09-28
6
+ source_hash: 0c04676006c0
7
7
  status: current
8
8
  ---
9
9
 
@@ -11,8 +11,8 @@ status: current
11
11
 
12
12
  > **语言**: [English](../../../core/turn-completion-integrity.md) | [繁體中文](../../zh-TW/core/turn-completion-integrity.md) | 简体中文
13
13
 
14
- **版本**: 1.4.0
15
- **最后更新**: 2026-09-25
14
+ **版本**: 1.4.1
15
+ **最后更新**: 2026-09-28
16
16
  **适用范围**: 任何由 agent 结束回合、把控制权交还给人的执行环境
17
17
  **Scope**: universal
18
18
  **行业标准**: 不声称任何来源——由实际观察到的失败归纳,见「证据」
@@ -144,13 +144,19 @@ agent 写下「我接着做 X」,然后结束回合,而 X 没有做。
144
144
  ## 支持的执行环境
145
145
 
146
146
  这个检查只在「适配层存在,且 hook 真的被接入该执行环境自己的配置」时才生效。
147
- 截至 v1.4.0:
147
+ 截至 v1.4.1:
148
148
 
149
149
  | 执行环境 | 事件 | 配置文件 | 拦截契约 |
150
150
  |---|---|---|---|
151
151
  | Claude Code | Stop | `.claude/settings.json` | stdout 输出 `{"decision":"block","reason":...}`,exit 0;沉默即放行 |
152
152
  | Codex | Stop | `.codex/hooks.json` | stdout 输出 `{"decision":"block","reason":...}`,exit 0——官方文档写明这个事件纯文本或空输出无效 |
153
- | Gemini CLI | AfterAgent | `.gemini/settings.json` | stdout 输出 `{"decision":"deny","reason":...}`,exit 0——官方文档标记为优先于 exit code 2 的做法 |
153
+ | Gemini CLI(过时) | AfterAgent | `.gemini/settings.json` | stdout 输出 `{"decision":"deny","reason":...}`,exit 0——官方文档标记为优先于 exit code 2 的做法 |
154
+
155
+ 在 Codex 上,接上了不等于会执行。Codex 会跳过项目级的 hook,直到项目被信任、**而且**
156
+ 这一支 hook 的定义在交互式 Codex 会话里通过 `/hooks` 被信任为止;信任记录绑定在定义的
157
+ 哈希值上,定义一改就要重新信任。在 `codex exec` 下,未被信任的 hook 会被跳过,而且完全
158
+ 没有任何消息(2026-09-28 实测,codex-cli 0.155.1:hook 被信任之前适配层一次都没被调用;
159
+ 信任之后,拦截与放行都与它的测试完全一致)。`uds init --with-hooks` 会在安装那一行旁边打印这件事。
154
160
 
155
161
  Codex 的 R9 豁免是尽力而为,不是静默失效:Codex 的 Stop payload 直接给出
156
162
  agent 的最后一条消息,却不给出用户的;要拿到用户那一侧必须解析一份
@@ -165,6 +171,15 @@ agent 的最后一条消息,却不给出用户的;要拿到用户那一侧
165
171
  用户那一侧变空、不会抛出异常——检测仍照样运行在 agent 消息上,只有那一轮
166
172
  的 R9 豁免可能漏掉。
167
173
 
174
+ Gemini CLI 已过时。Google 于 2026-06-18 对个人账号停用 Gemini CLI,
175
+ 改由 Antigravity CLI(`agy`)取代;企业账号两者都还能用。这个适配层为那些用户保留,
176
+ 但它从未在真实的 Gemini CLI 会话中验证过;使用 Google 工具的新采用者应预期的是
177
+ Antigravity CLI,而它**尚未支持**。它文档记载的 Stop hook 契约,在关键之处与上表每一个
178
+ 适配层都不同:hook 配置在 `.agents/hooks.json`、传入数据只有 `transcriptPath`
179
+ (没有最后一条回复、也没有人的消息)、拦截是 `{"decision":"continue","reason":...}`
180
+ 而不是 `block` 或 `deny`。等这份契约在真实会话中观察到之后才会加入适配层——
181
+ 与下方 Cursor 没有适配层是同一个理由。
182
+
168
183
  Cursor 已评估但不支持:截至撰写本文时,Cursor 的 stop hook 能不能真的
169
184
  拦下一个回合仍未确定,若对着一个没人验证过的契约交付一份适配层,
170
185
  等于重演 R3 要防的那个失败——一个没人确认过真的在执法的执法机制。
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  source: ../../../docs/CLI-INIT-OPTIONS.md
3
- source_version: 3.6.0
4
- translation_version: 3.6.0
5
- last_synced: 2026-09-25
3
+ source_version: 3.7.0
4
+ translation_version: 3.7.0
5
+ last_synced: 2026-09-26
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-TW/docs/CLI-INIT-OPTIONS.md) | 简体中文
12
12
  >
13
- > **版本**: 3.6.0
14
- > **最后更新**: 2026-09-25
13
+ > **版本**: 3.7.0
14
+ > **最后更新**: 2026-09-26
15
15
 
16
16
  本文档详细说明 `uds init` 命令的每一个选项,包含使用情境、影响范围和建议选择。
17
17
 
@@ -838,6 +838,34 @@ uds init --experimental
838
838
  | Claude Code 目标文件 | `--claude-target` | Claude Code 集成内容要写到哪里:`project`(`CLAUDE.md`,默认)或 `local`(`CLAUDE.local.md`) |
839
839
  | 模式(已弃用) | `-m, --mode` | 安装模式(skills, full)- 请改用 `--skills-location` |
840
840
 
841
+ ### 提交前标准检查(git hook 接线)
842
+
843
+ `uds init` 一律会设定“`git commit` 时跑 `uds check`”——这不是标志,只要项目
844
+ 是 git 仓库就会执行。Node.js 项目(检测到 `package.json`)写入
845
+ `.husky/pre-commit`,否则写入 `.git/hooks/pre-commit`,接着会确保 git 真的
846
+ 会执行它:设定 `git config --local core.hooksPath .husky`(非 Node 项目则
847
+ 保留原生 `.git/hooks` 默认不动)——这与 husky 自己的 `npx husky` bootstrap
848
+ 内部所做的事完全相同,只是直接做,让检查立刻生效,不论最后有没有装 husky。
849
+
850
+ 以下两件事是刻意不做的:
851
+
852
+ - **替你安装 husky,或改动 `package.json` 的依赖。** husky 要不要作为依赖
853
+ 由你决定;上面的接线不论有没有 husky 都能运作。若项目已经把 husky 列为
854
+ 依赖,`uds init` 也会顺手串接它的 `prepare` script(`"prepare": "既有内容
855
+ && husky"`),让未来的 `npm install` 也保持 husky 自己的 bootstrap 同步
856
+ ——这是锦上添花,不是这道接线能否生效的关键。
857
+ - **覆盖你自己的 git hook 设定。** 若 `core.hooksPath` 已经指向别处,或
858
+ `.git/hooks/pre-commit` 已经存在,`uds init` 会两者都不动,并打印检查
859
+ **未启用**及原因。
860
+
861
+ `git config core.hooksPath` 是**本机、per-clone 的设定——不会进版控。**
862
+ 跑 `uds init` 只会让“跑过这个命令的那个 clone”生效;其他人 clone 这个
863
+ 仓库后要自己再跑一次 `uds init`(或 `uds check` 打印的那一行修复命令)。
864
+ `uds check` 会检测“`.husky/pre-commit`/`.git/hooks/pre-commit` 存在,但
865
+ 实际不在 git 真正会执行的路径上”的情况——包含在这次修复之前就已采用
866
+ UDS 的项目——并在 `[pre-commit]` 下回报同样的修复方式;此警告不影响
867
+ `uds check --ci` 的退出码。
868
+
841
869
  ### Claude Code 以外的强制执行 Hooks
842
870
 
843
871
  `--with-hooks` 一定会安装进 `.claude/settings.json`。四个有 hook 支持的标准
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  source: ../../CHANGELOG.md
3
- source_version: 6.13.0-beta.2
4
- translation_version: 6.13.0-beta.2
5
- last_synced: 2026-09-26
3
+ source_version: 6.13.0-beta.5
4
+ translation_version: 6.13.0-beta.5
5
+ last_synced: 2026-09-27
6
6
  status: current
7
7
  ---
8
8
 
@@ -17,6 +17,40 @@ status: current
17
17
 
18
18
  ## [Unreleased]
19
19
 
20
+ ## [6.13.0-beta.5] - 2026-09-28
21
+
22
+ > **測試版** — 以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、已知限制、如何退回正式版:見 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。**裝過 6.13.0-beta.1~beta.4 的請升級:那幾版的 Claude Code 回合收尾關卡從未擋下過。**
23
+
24
+ ### 修正
25
+
26
+ - **Claude Code 的回合收尾關卡在真實的 Claude Code 工作階段裡從未擋下過任何回合——6.13.0-beta.1 到 beta.4 的每個採用者都接上了、也在執行,而它放行了每一個回合。** 2026-09-28 以 Claude Code 2.1.283 在真實 `claude -p` 工作階段實測:Stop hook 執行的那一刻,逐字稿裡還沒有最後一則 AI 回覆,因此只讀逐字稿的適配層看到空訊息,應擋的 5 次全部放行;在較長的對話裡,它判斷的會是上一輪的回覆。自我測試與單元測試從頭到尾都通過,因為每一支都餵給適配層一份已經寫完的逐字稿。適配層現在改從 stdin 的 `last_assistant_message`(Claude Code 會送)取得最後一則回覆,逐字稿只用來讀人的那一側(R9),以及在不送這個欄位的版本上當備援。修正後已再次實測。新測試重現關卡當下實際看到的形狀,在舊適配層上會失敗。
27
+ - **Codex 的關卡裝上了卻從未執行,而且沒有任何提示。** Codex 會略過專案層級的 hook,直到專案被信任、且這一支 hook 透過 `/hooks` 被信任為止——在 `codex exec` 底下完全無聲。`uds init --with-hooks` 現在會在 Codex 安裝那一行旁邊說明這件事,標準的〈支援的執行環境〉與 `docs/PRE-RELEASE.md` 也補上這個步驟。信任之後,Codex 適配層的擋與放行與它的測試完全一致(2026-09-28 實測,codex-cli 0.155.1)。
28
+
29
+ ### 變更
30
+
31
+ - **`turn-completion-integrity` 1.4.1:Gemini CLI 適配層標為過時,並明列 Antigravity CLI 尚未支援。** Google 於 2026-06-18 對個人帳號停用 Gemini CLI,改由 Antigravity CLI(`agy`)取代;企業帳號兩者都還能用。適配層為他們保留,但從未在真實的 Gemini CLI 工作階段中驗證過,因此不再與 Claude Code、Codex 適配層並列。Antigravity CLI 文件記載的 Stop hook 契約與所有已出貨的適配層都不同(設定在 `.agents/hooks.json`;傳入資料只有 `transcriptPath`,沒有最後一則回覆或人的訊息;攔截是 `{"decision":"continue"}`),因此在真實工作階段中觀察到這份契約之前不出貨適配層——與 Cursor 不支援是同一條規則。行為不變:選了 Gemini CLI 時,`uds init --with-hooks` 仍會接上它的關卡。
32
+
33
+ ## [6.13.0-beta.4] - 2026-09-28
34
+
35
+ > **測試版** — 以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、已知限制、如何退回正式版:見 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。注意:6.13.0-beta.3 從未上架 npm,其內容隨本版出貨。
36
+
37
+ ### 修復
38
+
39
+ - **`turn-completion-integrity` 的 Stop hook 在 Windows 上一路到 6.13.0-beta.3 都靜默失效——它照跑、找不到任何語言包、然後放行每一輪對話,且什麼都不印。** `engine.mjs` 的 `loadPacks()` 用 `join(HERE, 'locales', ...)` 組出每個語言包的路徑,直接把這個檔案系統路徑交給動態 `import()`;在 Windows 上那是 `C:\...` 這種路徑,不是合法的 ESM import 指定字串(POSIX 上的絕對路徑恰好也能被解析成合法指定字串,這正是為何在 macOS/Linux 上從未被發現)。2026-09-27 於 CI(windows-latest)實測:每一個出貨的語言包都載入失敗,而每一個原本該回 `block`/`deny` 決策的介接層(Claude Code、Codex、Gemini CLI)全部回傳 `undefined`。修法改用 `pathToFileURL(...).href`,與 `cli/src/utils/standard-fixer.js`/`standard-validator.js` 既有的正確寫法一致。四支 `scripts/check-*.ts` 開發工具腳本有同樣的寫法(Windows CI job 不會跑到它們,因為它們只在 `ubuntu-latest` 上執行,但那裡同樣是壞的),一併以同樣方式修正。新增一支全 repo 走訪測試(`cli/tests/unit/scripts/no-fs-path-dynamic-import.test.js`)掃描 `scripts/`、`cli/src/`、`cli/scripts/` 找這個寫法,未來新增的一處不需要有人記得這次事故也會被擋下。
40
+ - **由 `uds init` 寫入的 husky 管理 `.husky/pre-commit`,即使 `core.hooksPath` 已正確接好,在 Windows 上一路到 6.13.0-beta.3 都會讓每一次提交失敗,錯誤是 `error: cannot spawn .husky/pre-commit: No such file or directory`。** husky v9 自己的範本沒有 shebang 行,這在 macOS/Linux 上一直能動,因為 POSIX git 在腳本沒有 shebang 時(`ENOEXEC`)會退回用 `/bin/sh` 執行;git for Windows 沒有這個後備機制,完全無法對沒有 shebang 的檔案 spawn,而且錯誤訊息指向 hook 檔本身而非缺少的直譯器——很容易被誤判成 wiring 問題而非內容問題。`uds init` 現在會在缺少 shebang 時,於 husky 管理的 hook 最前面補上 `#!/bin/sh`,不分平台一律如此,無論是寫新 hook 還是動到既有的採用者檔案(只會插入,絕不重寫採用者自己的 shebang 或任何其他行)。`uds check` 的 `[pre-commit]` 警告新增 `missingShebang` 訊號,獨立於 wiring 回報(一個 hook 可以完全接好但在 Windows 上仍因此失敗),且維持既有設計,只讀不寫。
41
+
42
+ ## [6.13.0-beta.3] - 2026-09-27
43
+
44
+ > **測試版** — 以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、已知限制、如何退回正式版:見 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
45
+
46
+ ### Fixed
47
+
48
+ - **`uds init` 會寫入提交前檢查(`.husky/pre-commit`,非 Node 專案則為 `.git/hooks/pre-commit`),卻從未確認 git 真的會執行它。** 它依賴 husky 自己的 bootstrap 機制——`npm install` 觸發 husky 的 `prepare` script 去設定 `core.hooksPath`——而這個時機只在**下一次** `npm install` 執行時才會發生;如果 `node_modules` 早就存在,這件事就永遠不會發生。實測三個既有採用者(asiaostrich-telemetry-server、asiaostrich-telemetry-client、machine-setup,2026-09-26):三者都有呼叫 `npx uds check` 的 `.husky/pre-commit`,但 `core.hooksPath` 皆未設定,提交時檢查從未跑過——而且完全沒有任何錯誤訊息。`uds init` 現在不再替使用者安裝 husky 或改動 package.json 的依賴;改成直接執行 `git config --local core.hooksPath .husky`(與 husky 自己 bootstrap 內部做的事完全相同),讓檢查在 `uds init` 執行完就立刻生效,無論 husky 有沒有裝。絕不覆蓋使用者既有的 `core.hooksPath`,或既有的 `.git/hooks/pre-commit`——兩者都會被保留原狀,並印出訊息說明檢查**未啟用**及原因。非 Node 專案的原生 hook 路徑也不再無條件覆寫既有的 `.git/hooks/pre-commit`(過去會)。新增 `uds check` 警告 `[pre-commit]`:偵測 UDS 寫入的檢查檔存在,但實際不在 git 真正會執行的路徑上(涵蓋直接設定、husky 自己的 `<dir>/_` shim 轉呼叫、以及原生預設路徑三種形狀),並附上修復方式——這就是像上述三個既有採用者這樣「已經中招」的專案能發現問題的管道。此警告只提示不影響 `uds check --ci` 的結束碼,因為這是每個 clone 各自的本機設定落差,不是標準本身不合規。`core.hooksPath` 不會進版控,所以這道啟用只對執行 `uds init` 的那個 clone 生效——訊息與新警告都會說明這一點。
49
+
50
+ - **後續修正(2026-09-27):上面那個修法,若既有採用者手上的 `.husky/pre-commit` 還是 husky v8 舊範本(含 `_/husky.sh` 那一行),照做反而會讓每一次提交都失敗。** 實測其中一個既有採用者的拋棄式 clone:照 `uds check` 原本建議的修法(`git config --local core.hooksPath .husky`)執行,結果印出 `.husky/pre-commit: line 2: .husky/_/husky.sh: No such file or directory`,`git commit` 以 exit 1 失敗——比原本「靜默不跑」的缺陷更糟,因為 `.husky/_/` 這個目錄只有在 husky 自己的 bootstrap 真的跑過後才存在,而直接把 `core.hooksPath` 設成 `.husky` 會讓 git 原封不動地執行這個檔案。`uds init` 的 `setupHuskyHook` 現在會偵測並移除這一行後再改寫 `.husky/pre-commit`(其餘內容——使用者自己加的指令、既有的 `uds check` 那一行——全部保留);`uds check` 的 `[pre-commit]` 警告偵測到這種舊範本時,不再只單獨建議設定 hooksPath,改成給「先刪那一行、再設定 hooksPath」的兩步修法——因為 `uds init` 對已初始化的專案會直接拒絕執行,修不了這三個既有採用者的問題。`git-hooks.js` 新增共用函式 `hasLegacyHuskyShLine`/`stripLegacyHuskyShLine`。
51
+
52
+ - **`bump-version.mjs` 在「預發布→預發布」的版本升版時,把 `SECURITY.md`「最新正式版」那一列標錯——實測發生於 6.13.0-beta.2 發版當下(2026-09-26),當時以手動更正。** 它的 `SECURITY.md` 修補邏輯找「裸版號(無尾碼)那一列」來認定是正式版列;新的預發布版號(如 `6.13.0-beta.2`)永遠帶著連字號、永遠不會符合「裸版號」,於是修補邏輯退而求其次改到唯一真正裸版號的那一列——也就是不相關的正式版列——把它的版號換成新的預發布版號,而原本該更新、已經過期的預發布列(仍是 `6.13.0-beta.1`)反而原封不動。已將產生表格的邏輯(`generate-docs.mjs` 原本就寫對、但 `bump-version.mjs` 未使用)抽成共用的 `scripts/lib/security-versions.mjs`,兩支腳本現在都改成用 `(version, stableVersion)` 整段重新產生 2 或 3 列的表格,而不是找一列去 patch——已針對全部四種版本型態轉換(正式→正式、正式→預發布、預發布→預發布、預發布→正式)、三種語言,透過對隔離複本執行一次真正的端到端 `bump-version.mjs` 驗證正確。`check-version-sync.sh` 原本的 SECURITY.md 檢查只比對第一列資料的版號是否等於 `package.json`(一種位置代理,恰好抓到了這次事故);現在還會逐列比對「標籤」與「該列版號的形狀」是否吻合(「最新正式版/Latest stable」列若版號帶連字號、或「預發布版本/Pre-release」列若版號不帶連字號,即使位置檢查會通過,仍會被標記為錯誤)。
53
+
20
54
  ## [6.13.0-beta.2] - 2026-09-26
21
55
 
22
56
  > **測試版** — 以 `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.13.0-beta.2 (Pre-release) | **發布日期**: 2026-09-26 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
18
+ **版本**: 6.13.0-beta.5 (Pre-release) | **發布日期**: 2026-09-28 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
19
19
 
20
20
  語言無關、框架無關的軟體專案文件標準。透過 AI 原生工作流,確保不同技術堆疊之間的一致性、品質和可維護性。
21
21
 
@@ -13,7 +13,7 @@ status: current
13
13
  <!-- UDS_SUPPORTED_VERSIONS_START -->
14
14
  | 版本 | 支援狀態 |
15
15
  |------|--------|
16
- | 6.13.0-beta.2 | ✅ 預發布版本 |
16
+ | 6.13.0-beta.5 | ✅ 預發布版本 |
17
17
  | 6.12.0 | ✅ 最新正式版 |
18
18
  | < 6.0.0 | ❌ 已終止支援 |
19
19
  <!-- UDS_SUPPORTED_VERSIONS_END -->
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  source: ../../../core/turn-completion-integrity.md
3
- source_version: 1.4.0
4
- translation_version: 1.4.0
5
- last_synced: 2026-09-26
6
- source_hash: 401b74843abc
3
+ source_version: 1.4.1
4
+ translation_version: 1.4.1
5
+ last_synced: 2026-09-28
6
+ source_hash: 0c04676006c0
7
7
  status: current
8
8
  ---
9
9
 
@@ -11,8 +11,8 @@ status: current
11
11
 
12
12
  > **Language**: [English](../../../core/turn-completion-integrity.md) | 繁體中文
13
13
 
14
- **版本**: 1.4.0
15
- **最後更新**: 2026-09-25
14
+ **版本**: 1.4.1
15
+ **最後更新**: 2026-09-28
16
16
  **適用範圍**: 任何由 agent 結束回合、把控制權交還給人的執行環境
17
17
  **Scope**: universal
18
18
  **產業標準**: 不宣稱任何來源——由實際觀察到的失敗歸納,見「證據」
@@ -144,13 +144,19 @@ agent 寫下「我接著做 X」,然後結束回合,而 X 沒有做。
144
144
  ## 支援的執行環境
145
145
 
146
146
  這個檢查只在「轉接層存在,且 hook 真的被接進該執行環境自己的設定」時才生效。
147
- 截至 v1.4.0:
147
+ 截至 v1.4.1:
148
148
 
149
149
  | 執行環境 | 事件 | 設定檔 | 阻擋契約 |
150
150
  |---|---|---|---|
151
151
  | Claude Code | Stop | `.claude/settings.json` | stdout 印 `{"decision":"block","reason":...}`,exit 0;沉默即放行 |
152
152
  | Codex | Stop | `.codex/hooks.json` | stdout 印 `{"decision":"block","reason":...}`,exit 0——官方文件寫明這個事件純文字或空輸出無效 |
153
- | Gemini CLI | AfterAgent | `.gemini/settings.json` | stdout 印 `{"decision":"deny","reason":...}`,exit 0——官方文件標記為優先於 exit code 2 的做法 |
153
+ | Gemini CLI(過時) | AfterAgent | `.gemini/settings.json` | stdout 印 `{"decision":"deny","reason":...}`,exit 0——官方文件標記為優先於 exit code 2 的做法 |
154
+
155
+ 在 Codex 上,接上了不等於會執行。Codex 會略過專案層級的 hook,直到專案被信任、**而且**
156
+ 這一支 hook 的定義在互動式 Codex 工作階段裡透過 `/hooks` 被信任為止;信任紀錄綁在定義的
157
+ 雜湊值上,定義一改就要重新信任。在 `codex exec` 底下,未被信任的 hook 會被略過,而且完全
158
+ 沒有任何訊息(2026-09-28 實測,codex-cli 0.155.1:hook 被信任之前適配層一次都沒被呼叫;
159
+ 信任之後,擋與放行都與它的測試完全一致)。`uds init --with-hooks` 會在安裝那一行旁邊印出這件事。
154
160
 
155
161
  Codex 的 R9 豁免是盡力而為,不是靜默失效:Codex 的 Stop payload 直接給
156
162
  agent 的最後一則訊息,卻不給使用者的;要拿到使用者那一側必須解析一份
@@ -165,6 +171,15 @@ agent 的最後一則訊息,卻不給使用者的;要拿到使用者那一
165
171
  使用者那一側變空、不會拋出例外——偵測仍照樣跑在 agent 訊息上,只有那一輪
166
172
  的 R9 豁免可能漏掉。
167
173
 
174
+ Gemini CLI 已過時。Google 於 2026-06-18 對個人帳號停用 Gemini CLI,
175
+ 改由 Antigravity CLI(`agy`)取代;企業帳號兩者都還能用。這個適配層為那些使用者保留,
176
+ 但它從未在真實的 Gemini CLI 工作階段中驗證過;使用 Google 工具的新採用者該預期的是
177
+ Antigravity CLI,而它**尚未支援**。它文件記載的 Stop hook 契約,在關鍵之處與上表每一個
178
+ 適配層都不同:hook 設定在 `.agents/hooks.json`、傳入資料只有 `transcriptPath`
179
+ (沒有最後一則回覆、也沒有人的訊息)、攔截是 `{"decision":"continue","reason":...}`
180
+ 而不是 `block` 或 `deny`。等這份契約在真實工作階段中觀察到之後才會加入適配層——
181
+ 與下方 Cursor 沒有適配層是同一個理由。
182
+
168
183
  Cursor 已評估但不支援:截至撰寫本文時,Cursor 的 stop hook 能不能真的
169
184
  擋下一個回合仍未確定,若對著一個沒人驗證過的契約出一份轉接層,
170
185
  等於重演 R3 要防的那個失敗——一個沒人確認過真的在執法的執法機制。
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  source: ../../../docs/CLI-INIT-OPTIONS.md
3
- source_version: 3.6.0
4
- translation_version: 3.6.0
5
- last_synced: 2026-09-25
3
+ source_version: 3.7.0
4
+ translation_version: 3.7.0
5
+ last_synced: 2026-09-26
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.6.0
14
- > **最後更新**: 2026-09-25
13
+ > **版本**: 3.7.0
14
+ > **最後更新**: 2026-09-26
15
15
 
16
16
  本文件詳細說明 `uds init` 命令的每一個選項,包含使用情境、影響範圍和建議選擇。
17
17
 
@@ -838,6 +838,34 @@ uds init --experimental
838
838
  | Claude Code 目標檔 | `--claude-target` | Claude Code 整合內容要寫到哪裡:`project`(`CLAUDE.md`,預設)或 `local`(`CLAUDE.local.md`) |
839
839
  | 模式(已棄用) | `-m, --mode` | 安裝模式(skills, full)- 請改用 `--skills-location` |
840
840
 
841
+ ### 提交前標準檢查(git hook 接線)
842
+
843
+ `uds init`一律會設定「`git commit` 時跑 `uds check`」——這不是旗標,只要專案是
844
+ git repo 就會執行。Node.js 專案(偵測到 `package.json`)寫入
845
+ `.husky/pre-commit`,否則寫入 `.git/hooks/pre-commit`,接著會確保 git 真的會
846
+ 執行它:設定 `git config --local core.hooksPath .husky`(非 Node 專案則保留
847
+ 原生 `.git/hooks` 預設不動)——這與 husky 自己的 `npx husky` bootstrap 內部
848
+ 做的事完全相同,只是直接做,讓檢查立刻生效,不論最後有沒有裝 husky。
849
+
850
+ 以下兩件事是刻意不做的:
851
+
852
+ - **替你安裝 husky,或動 `package.json` 的依賴。** husky 要不要是依賴由你
853
+ 決定;上面的接線不論有沒有 husky 都能運作。若專案已經把 husky 列為依賴,
854
+ `uds init` 也會順手串接它的 `prepare` script(`"prepare": "既有內容 &&
855
+ husky"`),讓未來的 `npm install` 也保持 husky 自己的 bootstrap 同步——
856
+ 這是錦上添花,不是這道接線能不能生效的關鍵。
857
+ - **覆蓋你自己的 git hook 設定。** 若 `core.hooksPath` 已經指向別處,或
858
+ `.git/hooks/pre-commit` 已經存在,`uds init` 會兩者都不動,並印出檢查
859
+ **未啟用**及原因。
860
+
861
+ `git config core.hooksPath` 是**本機、per-clone 的設定——不會進版控。**
862
+ 跑 `uds init` 只會讓「跑過這個指令的那個 clone」生效;其他人 clone 這個
863
+ repo 後要自己再跑一次 `uds init`(或 `uds check` 印出的那一行修復指令)。
864
+ `uds check` 會偵測「`.husky/pre-commit`/`.git/hooks/pre-commit` 存在,
865
+ 但實際不在 git 真正會執行的路徑上」的情況——包含在這個修正之前就已採用
866
+ UDS 的專案——並在 `[pre-commit]` 底下回報同樣的修復方式;此警告不影響
867
+ `uds check --ci` 的結束碼。
868
+
841
869
  ### Claude Code 以外的強制執行 Hooks
842
870
 
843
871
  `--with-hooks` 一定會安裝進 `.claude/settings.json`。四個有 hook 支援的標準
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-dev-standards",
3
- "version": "6.13.0-beta.2",
3
+ "version": "6.13.0-beta.5",
4
4
  "description": "CLI tool for adopting Universal Development Standards",
5
5
  "keywords": [
6
6
  "documentation",