universal-dev-standards 6.13.0-beta.1 → 6.13.0-beta.2
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/core/turn-completion-integrity.md +12 -4
- package/bundled/hooks/check-turn-completion-codex.mjs +42 -15
- package/bundled/hooks/turn-completion/locales/en.mjs +21 -1
- package/bundled/hooks/turn-completion/locales/zh-TW.mjs +25 -2
- package/bundled/locales/zh-CN/CHANGELOG.md +13 -3
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/turn-completion-integrity.md +12 -5
- package/bundled/locales/zh-TW/CHANGELOG.md +13 -3
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/turn-completion-integrity.md +12 -5
- package/package.json +1 -1
- package/src/commands/uninstall.js +1 -1
- package/src/installers/hooks-installer.js +6 -2
- package/src/uninstallers/hook-uninstaller.js +190 -23
- package/standards-registry.json +7 -7
|
@@ -159,10 +159,18 @@ actually wired into that harness's own config. As of v1.4.0:
|
|
|
159
159
|
|
|
160
160
|
Codex's R9 exemption is best-effort, not silent failure: Codex's Stop payload
|
|
161
161
|
gives the assistant's final message directly but not the human's, so reading
|
|
162
|
-
the human side requires parsing a transcript file
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
the human side requires parsing a transcript file. Confirmed against a real
|
|
163
|
+
codex-cli 0.156.1 installation (2026-09-26): a user message in
|
|
164
|
+
`~/.codex/sessions/**/*.jsonl` is a `{"type":"response_item","payload":
|
|
165
|
+
{"type":"message","role":"user","content":[{"type":"input_text","text":...}]}}`
|
|
166
|
+
record — the message lives under `payload`, not at the top level of the
|
|
167
|
+
line or under a `message` key, and a `role: "developer"` record on the same
|
|
168
|
+
shape is not a human message. The 6.13.0-beta.1 adapter read neither of the
|
|
169
|
+
two shapes it tried against this real one, so R9 never exempted a turn on
|
|
170
|
+
Codex; fixed for 6.13.0-beta.2. A failed parse (or an unrecognized record
|
|
171
|
+
shape) still leaves the human side empty rather than throwing — detection
|
|
172
|
+
still runs on the assistant's message, only the R9 exemption for that one
|
|
173
|
+
turn may be missed.
|
|
166
174
|
|
|
167
175
|
Cursor was evaluated and is not supported: whether its stop hook can actually
|
|
168
176
|
block a turn in the way this standard requires was unresolved as of this
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
*
|
|
24
24
|
* R9 (exempt a human-directed stop) is best-effort here. Codex's Stop payload
|
|
25
25
|
* gives the assistant's last message directly but not the human's; this
|
|
26
|
-
* adapter
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* adapter reads transcript_path for it (rollout.jsonl), tolerantly. The real
|
|
27
|
+
* record shape has been confirmed against a codex-cli 0.156.1 install
|
|
28
|
+
* (2026-09-26; see extractMessage() below) — 6.13.0-beta.1 tried two guessed
|
|
29
|
+
* shapes that never matched it, so R9 never actually exempted a Codex turn.
|
|
30
|
+
* If the read still fails, or an unrecognized record type is seen, the user
|
|
31
|
+
* side is treated as empty — which means R9 cannot exempt that turn, not
|
|
32
|
+
* that the check goes silent (last_assistant_message still drives
|
|
33
|
+
* detection). See core/turn-completion-integrity.md's Codex section.
|
|
32
34
|
*
|
|
33
35
|
* Judgement (packs, cooldown, rolling window, self-echo) lives in
|
|
34
36
|
* turn-completion/engine.mjs and is shared with every other adapter; this
|
|
@@ -43,12 +45,40 @@
|
|
|
43
45
|
import { readFileSync } from 'node:fs';
|
|
44
46
|
import { decide, isSelfEcho, runSelfTest, printLanguages } from './turn-completion/engine.mjs';
|
|
45
47
|
|
|
48
|
+
/**
|
|
49
|
+
* A user-message record's `{ role, content }` payload, whichever of the
|
|
50
|
+
* plausible JSONL shapes `ev` turns out to be.
|
|
51
|
+
*
|
|
52
|
+
* 🔴 2026-09-26: verified against a real codex-cli 0.156.1
|
|
53
|
+
* `~/.codex/sessions/**\/*.jsonl` transcript. The actual shape is
|
|
54
|
+
* `{"type":"response_item","payload":{"type":"message","role":"user",
|
|
55
|
+
* "content":[{"type":"input_text","text":"…"}]}}` — the message lives under
|
|
56
|
+
* `ev.payload`, not `ev.message` and not `ev` itself. The two fallback shapes
|
|
57
|
+
* below were this file's original best-effort guesses (Claude-Code-style
|
|
58
|
+
* nested `message`, and a flat record); neither matches what Codex actually
|
|
59
|
+
* writes, so `bestEffortLastUserMessage` always returned '' and R9 (exempting
|
|
60
|
+
* a human-directed stop) never fired on Codex. They stay as fallbacks in case
|
|
61
|
+
* a different Codex version or record type uses one of them, but `ev.payload`
|
|
62
|
+
* is tried first since it is the confirmed real shape.
|
|
63
|
+
*
|
|
64
|
+
* Other record types seen in a real rollout (`event_msg`, `token_usage_record`,
|
|
65
|
+
* `session_meta`, `world_state`, and `response_item` payloads of type
|
|
66
|
+
* `reasoning`/`custom_tool_call`/…) do not have `role: "user"` on the object
|
|
67
|
+
* this function returns, so they fall through untouched. A `role: "developer"`
|
|
68
|
+
* message is deliberately NOT treated as a user message — only
|
|
69
|
+
* `payload.type === "message" && payload.role === "user"` counts.
|
|
70
|
+
*/
|
|
71
|
+
function extractMessage(ev) {
|
|
72
|
+
if (ev && ev.payload && ev.payload.type === 'message') return ev.payload;
|
|
73
|
+
if (ev && ev.message && typeof ev.message === 'object') return ev.message;
|
|
74
|
+
return ev;
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
/**
|
|
47
78
|
* Best-effort extraction of the human's last message from a Codex transcript.
|
|
48
|
-
* Tolerant of several plausible JSONL shapes
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* last_assistant_message from still being checked.
|
|
79
|
+
* Tolerant of several plausible JSONL shapes; any failure returns '', which
|
|
80
|
+
* is the same as "cannot tell" (R5) — it does not stop last_assistant_message
|
|
81
|
+
* from still being checked.
|
|
52
82
|
*/
|
|
53
83
|
function bestEffortLastUserMessage(transcriptPath) {
|
|
54
84
|
if (typeof transcriptPath !== 'string' || !transcriptPath) return '';
|
|
@@ -58,16 +88,13 @@ function bestEffortLastUserMessage(transcriptPath) {
|
|
|
58
88
|
if (!line.trim()) continue;
|
|
59
89
|
let ev;
|
|
60
90
|
try { ev = JSON.parse(line); } catch { continue; }
|
|
61
|
-
|
|
62
|
-
// { message: { role, content } } (Claude-Code-style rollout entry), or
|
|
63
|
-
// a flat { role, content }.
|
|
64
|
-
const msg = (ev && ev.message) || ev;
|
|
91
|
+
const msg = extractMessage(ev);
|
|
65
92
|
if (!msg || msg.role !== 'user') continue;
|
|
66
93
|
const c = msg.content;
|
|
67
94
|
const text = typeof c === 'string'
|
|
68
95
|
? c
|
|
69
96
|
: Array.isArray(c)
|
|
70
|
-
? c.filter((p) => p && (p.type === 'text' || typeof p.text === 'string')).map((p) => p.text || '').join('\n')
|
|
97
|
+
? c.filter((p) => p && (p.type === 'text' || p.type === 'input_text' || typeof p.text === 'string')).map((p) => p.text || '').join('\n')
|
|
71
98
|
: '';
|
|
72
99
|
if (text && text.trim() && !isSelfEcho(text)) user = text;
|
|
73
100
|
}
|
|
@@ -115,9 +115,18 @@ export function isAsking(text) {
|
|
|
115
115
|
* here disables the check for the rest of the session, which is worse than a
|
|
116
116
|
* missed block. It must read as an instruction to stop, not a mention of
|
|
117
117
|
* stopping.
|
|
118
|
+
*
|
|
119
|
+
* 🔴 2026-09-27: `\bI'?m (heading|going) (home|out)\b` was dead code — it can
|
|
120
|
+
* never match, because isStopRequest() always calls normalize() first, and
|
|
121
|
+
* normalize() rewrites "I'm" to "I am" before this pattern ever sees the
|
|
122
|
+
* text (`\bI'll\b` -> "I will" etc. have the same shape, but no other branch
|
|
123
|
+
* here depended on the contracted form surviving normalize()). Verified: a
|
|
124
|
+
* bare "I'm heading out." tested false pre-fix, true once the pattern
|
|
125
|
+
* accounts for the post-normalize "I am" form. Written to match either form
|
|
126
|
+
* defensively, in case normalize()'s rewrite list ever changes.
|
|
118
127
|
*/
|
|
119
128
|
const STOP_REQUEST =
|
|
120
|
-
/(\blet's (stop|pause|pick this up later)\b|\b(pause|stop) (here|for now|there)\b|\bhold (on|off)\b|\bthat's (enough|it) for (now|today)\b|\b(done|enough) for (now|today)\b|\bwrap (it |this )?up\b|\bcontinue (this )?later\b|\bpick (this|it) up (tomorrow|later)\b|\btake a break\b|\bI'
|
|
129
|
+
/(\blet's (stop|pause|pick this up later)\b|\b(pause|stop) (here|for now|there)\b|\bhold (on|off)\b|\bthat's (enough|it) for (now|today)\b|\b(done|enough) for (now|today)\b|\bwrap (it |this )?up\b|\bcontinue (this )?later\b|\bpick (this|it) up (tomorrow|later)\b|\btake a break\b|\bI(?:'m| am) (heading|going) (home|out)\b|\bgood ?night\b)/i;
|
|
121
130
|
|
|
122
131
|
export function isStopRequest(text) {
|
|
123
132
|
return STOP_REQUEST.test(normalize(text));
|
|
@@ -184,4 +193,15 @@ export const stopCorpus = [
|
|
|
184
193
|
[false, 'mentions stopping but is not one', 'Explain why the hook stops the turn.'],
|
|
185
194
|
[false, 'asks for work', 'Stop using the hardcoded list and walk the registry instead.'],
|
|
186
195
|
[false, 'ordinary instruction', 'Fix the detector and push it.'],
|
|
196
|
+
// 🔴 the "I'm heading (home|out)" branch above was dead code before this
|
|
197
|
+
// fix — normalize() rewrites "I'm" to "I am" before the pattern is tested,
|
|
198
|
+
// and the pattern required the contracted form. This case exercises it
|
|
199
|
+
// directly (no "let's pause"/"hold on" alongside it, unlike the two cases
|
|
200
|
+
// above that already passed for a different reason).
|
|
201
|
+
[true, "bare 'heading out', no other stop phrase nearby", "I'm heading out."],
|
|
202
|
+
[false, 'a leaving verb alone is an instruction to finish something BEFORE leaving, not a stop request', 'Before I head out, please finish the migration.'],
|
|
187
203
|
];
|
|
204
|
+
// Mutation check (verified by hand, then reverted — see commit/handback
|
|
205
|
+
// notes): reverting STOP_REQUEST's `I(?:'m| am)` back to the dead-code
|
|
206
|
+
// `I'?m`-only form turns the "bare 'heading out'" case above false, which
|
|
207
|
+
// fails `--self-test`.
|
|
@@ -93,15 +93,28 @@ export function isAsking(text) {
|
|
|
93
93
|
return ASKING.test(text);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// 🔴 2026-09-25 實測缺口:「我要出門了,等我回來再繼續」與「暫停,我要出門」
|
|
97
|
+
// 都沒有被下面任何一支既有樣式接住——前者當天真的誤擋了一次真實的 Claude
|
|
98
|
+
// Code 回合。R10 說叫停樣式放寬是危險方向(錯誤豁免會讓整個 session 的檢查
|
|
99
|
+
// 失效),所以這裡刻意寫窄:離開類詞必須跟「稍後再續」或裸的「暫停」同時
|
|
100
|
+
// 出現在同一小段裡,單獨出現的離開詞不算——「出門前把這三件做完」只有離開
|
|
101
|
+
// 詞、沒有稍後再續,仍必須判 false(見下方 stopCorpus 的反例)。
|
|
102
|
+
const LEAVING = '(出門|離開一下|先走)';
|
|
103
|
+
const RESUME_LATER = '(等我回來|回來再|明天再|晚點再|待會再|待会再)';
|
|
104
|
+
const LEAVE_THEN_LATER = `${LEAVING}[^。!?\\n]{0,12}${RESUME_LATER}|${RESUME_LATER}[^。!?\\n]{0,12}${LEAVING}`;
|
|
105
|
+
const LEAVE_WITH_PAUSE = `暫停[^。!?\\n]{0,12}${LEAVING}|${LEAVING}[^。!?\\n]{0,12}暫停`;
|
|
106
|
+
|
|
96
107
|
/**
|
|
97
108
|
* The human asking for the turn to end. Kept narrow on purpose: a false
|
|
98
109
|
* positive disables the check for the rest of the session.
|
|
99
110
|
*/
|
|
100
|
-
const STOP_REQUEST =
|
|
111
|
+
const STOP_REQUEST = new RegExp(
|
|
101
112
|
// 🔴 `先停` 曾寫成裸的,而語料當場抓到「先**停用**那份硬編碼清單」——
|
|
102
113
|
// 一句要求做事的指令被讀成叫我停。這個方向的誤判會把守衛整場關掉,
|
|
103
114
|
// 所以 `停` 後面接得出動詞的字一律排除。
|
|
104
|
-
|
|
115
|
+
'(先暫停|暫停一下|先停(?![用止掉住])|停一下|先不要(做|動)|不用繼續|今天(先)?到這|先這樣|收工|下班|我要回家|明天再(說|弄|做)|改天再|先擱著|睡了|晚安' +
|
|
116
|
+
`|${LEAVE_THEN_LATER}|${LEAVE_WITH_PAUSE})`
|
|
117
|
+
);
|
|
105
118
|
|
|
106
119
|
export function isStopRequest(text) {
|
|
107
120
|
return STOP_REQUEST.test(text);
|
|
@@ -191,4 +204,14 @@ export const stopCorpus = [
|
|
|
191
204
|
[false, '提到停止但不是叫停', '解釋一下這支 hook 為什麼會擋下回合。'],
|
|
192
205
|
[false, '要求做事而句中有停', '先停用那份硬編碼清單,改成走訪註冊表。'],
|
|
193
206
|
[false, '一般指令', '把偵測器修好然後推上去。'],
|
|
207
|
+
// 🔴 2026-09-25 真實使用者原話——當時真的誤擋了一次回合,見上方 STOP_REQUEST 的註解。
|
|
208
|
+
[true, '離開+稍後再續(真實原話)', '我要出門了,等我回來再繼續'],
|
|
209
|
+
[true, '暫停+離開(真實原話)', '暫停,我要出門'],
|
|
210
|
+
// 窄性反例:單獨的離開詞、或單獨的「稍後」詞,都不算叫停——那是要求
|
|
211
|
+
// 「在離開前」把事情做完,不是要收掉這一輪。
|
|
212
|
+
[false, '離開詞單獨出現:這是要求離開前做完,不是叫停', '出門前把這三件做完'],
|
|
213
|
+
[false, '稍後詞單獨出現,沒有離開詞:一樣是要求先做完', '等我回來前先把測試跑完'],
|
|
194
214
|
];
|
|
215
|
+
// 突變驗證(手動跑過、已還原——見 commit/交接紀錄):把 STOP_REQUEST 的
|
|
216
|
+
// `LEAVE_THEN_LATER`/`LEAVE_WITH_PAUSE` 兩個新分支拿掉,上面兩筆「真實原話」
|
|
217
|
+
// 會變 false,`--self-test` 隨之變紅,證明這兩筆真的在測新加的規則。
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../CHANGELOG.md
|
|
3
|
-
source_version: 6.13.0-beta.
|
|
4
|
-
translation_version: 6.13.0-beta.
|
|
5
|
-
last_synced: 2026-09-
|
|
3
|
+
source_version: 6.13.0-beta.2
|
|
4
|
+
translation_version: 6.13.0-beta.2
|
|
5
|
+
last_synced: 2026-09-26
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -17,6 +17,16 @@ status: current
|
|
|
17
17
|
|
|
18
18
|
## [Unreleased]
|
|
19
19
|
|
|
20
|
+
## [6.13.0-beta.2] - 2026-09-26
|
|
21
|
+
|
|
22
|
+
> **测试版** — 以 `npm install -g universal-dev-standards@beta` 安装。要测什么、已知限制、如何退回正式版:见 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
|
|
23
|
+
|
|
24
|
+
### 修复
|
|
25
|
+
|
|
26
|
+
- **`uds uninstall` 从未移除 `installHooks()`/`installCodexHooks()`/`installGeminiHooks()` 写入的关卡——不只是 Codex 与 Gemini CLI(6.13.0-beta.1 记载的已知限制),Claude Code 自己的 `.claude/settings.json` 也有一模一样的缺口,而且从未被记录过。** `hooks` 这个 uninstall 分类原本只处理 `.husky/pre-commit` 与 `.git/hooks/pre-commit`;三个安装函数实际写入的配置文件完全没有任何 uninstaller 在管,导致每一个关卡在 `uds uninstall` 之后仍持续运行。新增的 `uninstallClaudeCodeHooks`/`uninstallCodexHooks`/`uninstallGeminiHooks`(`src/uninstallers/hook-uninstaller.js`)现在会精准移除 `.claude/settings.json`、`.codex/hooks.json`、`.gemini/settings.json` 里 UDS 安装的条目——判断依据是命令路径**加上**一份 UDS 目前确实有发布的脚本文件名清单,不是只看路径,这样用户自己放进 UDS 同一个 `scripts/hooks/` 目录下的 hook 就不会被误删。移除后变空的事件数组会一并从配置里移除;配置文件若因此变成完全空的对象(代表整份都是 UDS 写入的)就直接删除文件,否则保留文件并写回其余内容。JSON 格式损坏时报告错误并保持原样,不会覆写。已接入 `uds uninstall` 既有的 `hooks` 分类、`--dry-run` 预览,以及交互菜单里该分类的说明文字。
|
|
27
|
+
- **Codex 适配层的 R9 豁免(「用户叫停就放行」)在真实 Codex 安装上从未真正生效过——`scripts/hooks/check-turn-completion-codex.mjs` 的 `bestEffortLastUserMessage()` 试过的两种记录形状都读不到字段。** 已对真实 codex-cli 0.156.1 的 `~/.codex/sessions/**/*.jsonl` 对话记录坐实:一条用户消息记录长这样——`{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":...}]}}`——消息位于 `payload` 之下,不在该条记录最外层、也不在 `message` 键下,所以该字段一直被读成不存在,R9 从未真正豁免过任何一轮 Codex 对话。现在优先读取 `payload.type === "message"`(其余记录类型若刚好用到原来那两种形状仍保留为后备),同时支持 `input_text` 内容项(与既有的 `text` 形状并存),且不把 `role: "developer"` 的记录当成用户消息。`core/turn-completion-integrity.md`「支持的执行环境」一节与 `docs/PRE-RELEASE.md` 已从「未对照真实安装验证过」更新为已坐实的真实形状。
|
|
28
|
+
- **R9 的 zh-TW 叫停检测漏掉了「要离开、稍后再继续」这一族——已实测:「我要出门了,等我回来再继续」在 2026-09-25 真的误挡了一次 Claude Code 的对话完成关卡。** 这句与「暂停,我要出门」都没有被既有的任何一个 `STOP_REQUEST` 模式接住。新增两个窄模式:离开类词(出门/离开一下/先走)必须跟「稍后再续」类词(等我回来/回来再/明天再/晚点再/待会再)或裸的「暂停」同时出现在同一小段里——单独的离开词(例如「出门前把这三件做完」,这是要求离开前做完,不是叫停)语料仍必须判 false。顺手查了 en 语料包有没有同样的缺口,发现 `\bI'?m (heading|going) (home|out)\b` 是永远打不中的死代码:`isStopRequest()` 一律先调用 `normalize()`,会把 "I'm" 改写成 "I am",只认缩写形式的模式因此永远匹配不到;已改为 `I(?:'m| am) (heading|going) (home|out)`,坐实裸的 "I'm heading out." 修复前为 false、修复后为 true。
|
|
29
|
+
|
|
20
30
|
## [6.13.0-beta.1] - 2026-09-26
|
|
21
31
|
|
|
22
32
|
> **测试版** — 以 `npm install -g universal-dev-standards@beta` 安装。要测什么、已知限制、如何退回正式版:见 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。已知限制:`uds uninstall` 尚不会移除 Codex/Gemini CLI 设置里的关卡。
|
|
@@ -15,7 +15,7 @@ status: current
|
|
|
15
15
|
|
|
16
16
|
> **语言**: [English](../../README.md) | [繁體中文](../zh-TW/README.md) | 简体中文
|
|
17
17
|
|
|
18
|
-
**版本**: 6.13.0-beta.
|
|
18
|
+
**版本**: 6.13.0-beta.2 (Pre-release) | **发布日期**: 2026-09-26 | **授权**: [双重授权](../../LICENSE) (CC BY 4.0 + MIT)
|
|
19
19
|
|
|
20
20
|
语言无关、框架无关的软件项目文档标准。通过 AI 原生工作流,确保不同技术栈之间的一致性、质量和可维护性。
|
|
21
21
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
source: ../../../core/turn-completion-integrity.md
|
|
3
3
|
source_version: 1.4.0
|
|
4
4
|
translation_version: 1.4.0
|
|
5
|
-
last_synced: 2026-09-
|
|
6
|
-
source_hash:
|
|
5
|
+
last_synced: 2026-09-26
|
|
6
|
+
source_hash: 401b74843abc
|
|
7
7
|
status: current
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -154,9 +154,16 @@ agent 写下「我接着做 X」,然后结束回合,而 X 没有做。
|
|
|
154
154
|
|
|
155
155
|
Codex 的 R9 豁免是尽力而为,不是静默失效:Codex 的 Stop payload 直接给出
|
|
156
156
|
agent 的最后一条消息,却不给出用户的;要拿到用户那一侧必须解析一份
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
对话记录文件。已对真实 codex-cli 0.156.1 安装坐实(2026-09-26):
|
|
158
|
+
`~/.codex/sessions/**/*.jsonl` 里的一条用户消息长这样——
|
|
159
|
+
`{"type":"response_item","payload":{"type":"message","role":"user",
|
|
160
|
+
"content":[{"type":"input_text","text":...}]}}`——消息位于 `payload`
|
|
161
|
+
之下,不在该行最外层、也不在 `message` 键下;同一种形状但
|
|
162
|
+
`role: "developer"` 的记录不算用户消息。6.13.0-beta.1 的适配层尝试过的
|
|
163
|
+
两种形状都不是这个真实形状,所以 R9 在 Codex 上从未真正豁免过任何一轮;
|
|
164
|
+
6.13.0-beta.2 已修复。解析失败(或遇到无法识别的记录形状)时仍只是让
|
|
165
|
+
用户那一侧变空、不会抛出异常——检测仍照样运行在 agent 消息上,只有那一轮
|
|
166
|
+
的 R9 豁免可能漏掉。
|
|
160
167
|
|
|
161
168
|
Cursor 已评估但不支持:截至撰写本文时,Cursor 的 stop hook 能不能真的
|
|
162
169
|
拦下一个回合仍未确定,若对着一个没人验证过的契约交付一份适配层,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
source: ../../CHANGELOG.md
|
|
3
|
-
source_version: 6.13.0-beta.
|
|
4
|
-
translation_version: 6.13.0-beta.
|
|
5
|
-
last_synced: 2026-09-
|
|
3
|
+
source_version: 6.13.0-beta.2
|
|
4
|
+
translation_version: 6.13.0-beta.2
|
|
5
|
+
last_synced: 2026-09-26
|
|
6
6
|
status: current
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -17,6 +17,16 @@ status: current
|
|
|
17
17
|
|
|
18
18
|
## [Unreleased]
|
|
19
19
|
|
|
20
|
+
## [6.13.0-beta.2] - 2026-09-26
|
|
21
|
+
|
|
22
|
+
> **測試版** — 以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、已知限制、如何退回正式版:見 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。
|
|
23
|
+
|
|
24
|
+
### 修正
|
|
25
|
+
|
|
26
|
+
- **`uds uninstall` 從未移除 `installHooks()`/`installCodexHooks()`/`installGeminiHooks()` 寫入的關卡——不只是 Codex 與 Gemini CLI(6.13.0-beta.1 記載的已知限制),Claude Code 自己的 `.claude/settings.json` 也有一模一樣的缺口,而且從未被記錄過。** `hooks` 這個 uninstall 分類原本只處理 `.husky/pre-commit` 與 `.git/hooks/pre-commit`;三支安裝函式實際寫入的設定檔完全沒有任何 uninstaller 在管,導致每一個關卡在 `uds uninstall` 之後仍持續執行。新增的 `uninstallClaudeCodeHooks`/`uninstallCodexHooks`/`uninstallGeminiHooks`(`src/uninstallers/hook-uninstaller.js`)現在會精準移除 `.claude/settings.json`、`.codex/hooks.json`、`.gemini/settings.json` 裡 UDS 安裝的項目——辨識依據是指令路徑**加上**一份 UDS 目前確實有出貨的腳本檔名清單,不是只看路徑,這樣使用者自己放進 UDS 同一個 `scripts/hooks/` 目錄底下的 hook 就不會被誤刪。移除後變空的事件陣列會一併從設定裡移除;設定檔若因此變成完全空的物件(代表整份都是 UDS 寫入的)就直接刪除檔案,否則保留檔案並寫回其餘內容。JSON 格式損壞時回報錯誤並保持原樣,不會覆寫。已接入 `uds uninstall` 既有的 `hooks` 分類、`--dry-run` 預覽,以及互動選單裡該分類的說明文字。
|
|
27
|
+
- **Codex 轉接層的 R9 豁免(「使用者叫停就放行」)在真實 Codex 安裝上從未真的生效過——`scripts/hooks/check-turn-completion-codex.mjs` 的 `bestEffortLastUserMessage()` 試過的兩種紀錄形狀都讀不到欄位。** 已對真實 codex-cli 0.156.1 的 `~/.codex/sessions/**/*.jsonl` 逐字稿坐實:一則使用者訊息紀錄長這樣——`{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":...}]}}`——訊息住在 `payload` 底下,不在該筆紀錄最外層、也不在 `message` 鍵底下,所以那個欄位一直被讀成不存在,R9 從未真的豁免過任何一輪 Codex 回合。現在優先讀 `payload.type === "message"`(其餘紀錄型別若剛好用到原本那兩種形狀仍保留為後備),一併支援 `input_text` 內容項目(與既有的 `text` 形狀並存),且不把 `role: "developer"` 的紀錄當成使用者訊息。`core/turn-completion-integrity.md`「支援的執行環境」一節與 `docs/PRE-RELEASE.md` 已從「未對照真實安裝驗證過」更新為已坐實的真實形狀。
|
|
28
|
+
- **R9 的 zh-TW 叫停偵測漏掉「要離開、稍後再續」這一族——已實測:「我要出門了,等我回來再繼續」在 2026-09-25 真的誤擋了一次 Claude Code 的回合完成關卡。** 這句與「暫停,我要出門」都沒有被既有的任何一支 `STOP_REQUEST` 樣式接住。新增兩支窄樣式:離開類詞(出門/離開一下/先走)必須跟「稍後再續」類詞(等我回來/回來再/明天再/晚點再/待會再)或裸的「暫停」同時出現在同一小段裡——單獨的離開詞(例如「出門前把這三件做完」,這是要求離開前做完,不是叫停)語料仍必須判 false。順手查了 en 語料包有沒有一樣的缺口,發現 `\bI'?m (heading|going) (home|out)\b` 是永遠打不中的死碼:`isStopRequest()` 一律先呼叫 `normalize()`,會把 "I'm" 改寫成 "I am",只認縮寫形的樣式因此永遠配不到;已修成 `I(?:'m| am) (heading|going) (home|out)`,坐實裸的 "I'm heading out." 修前為 false、修後為 true。
|
|
29
|
+
|
|
20
30
|
## [6.13.0-beta.1] - 2026-09-26
|
|
21
31
|
|
|
22
32
|
> **測試版** — 以 `npm install -g universal-dev-standards@beta` 安裝。要測什麼、已知限制、如何退回正式版:見 [docs/PRE-RELEASE.md](../../docs/PRE-RELEASE.md)。已知限制:`uds uninstall` 尚不會移除 Codex/Gemini CLI 設定裡的關卡。
|
|
@@ -15,7 +15,7 @@ status: current
|
|
|
15
15
|
|
|
16
16
|
> **語言**: [English](../../README.md) | 繁體中文 | [简体中文](../zh-CN/README.md)
|
|
17
17
|
|
|
18
|
-
**版本**: 6.13.0-beta.
|
|
18
|
+
**版本**: 6.13.0-beta.2 (Pre-release) | **發布日期**: 2026-09-26 | **授權**: [雙重授權](../../LICENSE) (CC BY 4.0 + MIT)
|
|
19
19
|
|
|
20
20
|
語言無關、框架無關的軟體專案文件標準。透過 AI 原生工作流,確保不同技術堆疊之間的一致性、品質和可維護性。
|
|
21
21
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
source: ../../../core/turn-completion-integrity.md
|
|
3
3
|
source_version: 1.4.0
|
|
4
4
|
translation_version: 1.4.0
|
|
5
|
-
last_synced: 2026-09-
|
|
6
|
-
source_hash:
|
|
5
|
+
last_synced: 2026-09-26
|
|
6
|
+
source_hash: 401b74843abc
|
|
7
7
|
status: current
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -154,9 +154,16 @@ agent 寫下「我接著做 X」,然後結束回合,而 X 沒有做。
|
|
|
154
154
|
|
|
155
155
|
Codex 的 R9 豁免是盡力而為,不是靜默失效:Codex 的 Stop payload 直接給
|
|
156
156
|
agent 的最後一則訊息,卻不給使用者的;要拿到使用者那一側必須解析一份
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
逐字稿檔案。已對真實 codex-cli 0.156.1 安裝坐實(2026-09-26):
|
|
158
|
+
`~/.codex/sessions/**/*.jsonl` 裡的一則使用者訊息長這樣——
|
|
159
|
+
`{"type":"response_item","payload":{"type":"message","role":"user",
|
|
160
|
+
"content":[{"type":"input_text","text":...}]}}`——訊息住在 `payload`
|
|
161
|
+
底下,不在該行的最外層、也不在 `message` 鍵底下;同一種形狀但
|
|
162
|
+
`role: "developer"` 的紀錄不算使用者訊息。6.13.0-beta.1 的轉接層試過的
|
|
163
|
+
兩種形狀都不是這個真實形狀,所以 R9 在 Codex 上從未真的豁免過任何一輪;
|
|
164
|
+
6.13.0-beta.2 已修正。解析失敗(或遇到辨識不出的紀錄形狀)時仍只是讓
|
|
165
|
+
使用者那一側變空、不會拋出例外——偵測仍照樣跑在 agent 訊息上,只有那一輪
|
|
166
|
+
的 R9 豁免可能漏掉。
|
|
160
167
|
|
|
161
168
|
Cursor 已評估但不支援:截至撰寫本文時,Cursor 的 stop hook 能不能真的
|
|
162
169
|
擋下一個回合仍未確定,若對著一個沒人驗證過的契約出一份轉接層,
|
package/package.json
CHANGED
|
@@ -56,7 +56,7 @@ export async function uninstallCommand(options) {
|
|
|
56
56
|
const categories = await checkbox({
|
|
57
57
|
message: msg.selectCategories,
|
|
58
58
|
choices: [
|
|
59
|
-
{ name: `${msg.categoryHooks} (.husky/pre-commit)`, value: 'hooks', checked: true },
|
|
59
|
+
{ name: `${msg.categoryHooks} (.husky/pre-commit, .claude/settings.json, .codex/hooks.json, .gemini/settings.json)`, value: 'hooks', checked: true },
|
|
60
60
|
{ name: `${msg.categorySkills} (skills, commands)`, value: 'skills', checked: true },
|
|
61
61
|
{ name: `${msg.categoryIntegrations} (CLAUDE.md, .cursorrules, ...)`, value: 'integrations', checked: true },
|
|
62
62
|
{ name: `${msg.categoryStandards} (.standards/)`, value: 'standards', checked: true }
|
|
@@ -231,8 +231,12 @@ export function installHooks(projectPath) {
|
|
|
231
231
|
*
|
|
232
232
|
* @see core/turn-completion-integrity.md
|
|
233
233
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
// Exported so the uninstaller (../uninstallers/hook-uninstaller.js) can
|
|
235
|
+
// recognize exactly these two script names as UDS's own, rather than
|
|
236
|
+
// guessing from the shared `scripts/hooks/` directory path alone — a path
|
|
237
|
+
// an adopter's own hook could just as easily live under.
|
|
238
|
+
export const CODEX_HOOK_SCRIPT = 'check-turn-completion-codex.mjs';
|
|
239
|
+
export const GEMINI_HOOK_SCRIPT = 'check-turn-completion-gemini.mjs';
|
|
236
240
|
|
|
237
241
|
/** Copy the shared hook scripts into the project, same as installHooks() does. */
|
|
238
242
|
function copyHookScripts(hookDir, hooksDir) {
|
|
@@ -1,8 +1,168 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
collectHookConfigs, standardsSourceDir, hooksSourceDir,
|
|
5
|
+
CODEX_HOOK_SCRIPT, GEMINI_HOOK_SCRIPT,
|
|
6
|
+
} from '../installers/hooks-installer.js';
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
9
|
+
* Every UDS-installed hook entry — in .claude/settings.json, .codex/hooks.json
|
|
10
|
+
* and .gemini/settings.json alike — runs a command of the shape
|
|
11
|
+
* `node scripts/hooks/<script>` (see installHooks/installCodexHooks/
|
|
12
|
+
* installGeminiHooks in ../installers/hooks-installer.js). That path alone is
|
|
13
|
+
* NOT a safe signature, though: `scripts/hooks/` is a directory UDS itself
|
|
14
|
+
* scaffolds inside the adopter's project, so an adopter's own hook script can
|
|
15
|
+
* live at the exact same path and be indistinguishable by path alone
|
|
16
|
+
* (verified: an earlier version of this file matched on path only, and a
|
|
17
|
+
* test placing a user hook at `scripts/hooks/my-own-hook.mjs` lost it).
|
|
18
|
+
* The safe signature is path *and* a script basename UDS is actually known to
|
|
19
|
+
* ship right now — the same set collectHookConfigs() derives for install,
|
|
20
|
+
* plus the two fixed Codex/Gemini script names.
|
|
21
|
+
*
|
|
22
|
+
* 🔴 Until this file added the three functions below, uninstall only ever
|
|
23
|
+
* touched .husky/pre-commit and .git/hooks/pre-commit — the settings.json /
|
|
24
|
+
* hooks.json files installHooks() etc. actually write were never cleaned up,
|
|
25
|
+
* so `uds uninstall` silently left every enforcement hook running (Claude
|
|
26
|
+
* Code included, not just Codex/Gemini).
|
|
27
|
+
*/
|
|
28
|
+
const UDS_HOOK_COMMAND_PATTERN = /^node scripts\/hooks\//;
|
|
29
|
+
|
|
30
|
+
/** The command string a hook entry runs, mirroring hooks-installer.js's own commandOf(). */
|
|
31
|
+
function commandOf(entry) {
|
|
32
|
+
const h = entry && entry.hooks && entry.hooks[0];
|
|
33
|
+
if (typeof h === 'string') return h;
|
|
34
|
+
return h && typeof h.command === 'string' ? h.command : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Script basenames UDS currently ships and would install a hook entry for. */
|
|
38
|
+
function knownUdsHookScripts() {
|
|
39
|
+
const { scripts } = collectHookConfigs(standardsSourceDir(), hooksSourceDir());
|
|
40
|
+
return new Set([...scripts.map((s) => basename(s)), CODEX_HOOK_SCRIPT, GEMINI_HOOK_SCRIPT]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isUdsHookEntry(entry, knownScripts) {
|
|
44
|
+
const cmd = commandOf(entry);
|
|
45
|
+
if (typeof cmd !== 'string' || !UDS_HOOK_COMMAND_PATTERN.test(cmd)) return false;
|
|
46
|
+
return knownScripts.has(basename(cmd));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Strip UDS-installed entries out of a `{ event: entry[] }` map, dropping any
|
|
51
|
+
* event key left with zero entries. An adopter's own hooks on the same event
|
|
52
|
+
* (or even the same array, or the same `scripts/hooks/` directory) are never
|
|
53
|
+
* touched — only entries whose command names a script UDS is known to ship
|
|
54
|
+
* are removed.
|
|
55
|
+
*/
|
|
56
|
+
function stripUdsEntries(hooksMap) {
|
|
57
|
+
const knownScripts = knownUdsHookScripts();
|
|
58
|
+
let removedCount = 0;
|
|
59
|
+
const nextMap = {};
|
|
60
|
+
for (const [event, entries] of Object.entries(hooksMap || {})) {
|
|
61
|
+
const kept = (entries || []).filter((entry) => {
|
|
62
|
+
if (isUdsHookEntry(entry, knownScripts)) {
|
|
63
|
+
removedCount += 1;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
});
|
|
68
|
+
if (kept.length > 0) nextMap[event] = kept;
|
|
69
|
+
}
|
|
70
|
+
return { nextMap, removedCount };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Put a stripped hooks map back onto a config object, dropping the `hooks` key entirely if empty. */
|
|
74
|
+
function applyHooksMap(config, nextMap) {
|
|
75
|
+
const updated = { ...config };
|
|
76
|
+
if (Object.keys(nextMap).length > 0) {
|
|
77
|
+
updated.hooks = nextMap;
|
|
78
|
+
} else {
|
|
79
|
+
delete updated.hooks;
|
|
80
|
+
}
|
|
81
|
+
return updated;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Shared machinery for the three "remove UDS entries from a hooks config
|
|
86
|
+
* file" functions below. Handles the missing-file, invalid-JSON, and
|
|
87
|
+
* nothing-to-remove cases identically for all three, so their only
|
|
88
|
+
* difference is which file and which key they look at.
|
|
89
|
+
*/
|
|
90
|
+
function uninstallHookConfigFile({ configPath, label, dryRun, deleteEmptyFile }) {
|
|
91
|
+
const result = { removed: [], skipped: [], errors: [] };
|
|
92
|
+
if (!existsSync(configPath)) return result; // nothing installed here — nothing to report
|
|
93
|
+
|
|
94
|
+
let config;
|
|
95
|
+
try {
|
|
96
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
result.errors.push(`${label} — could not parse (${error.message}); left untouched`);
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { nextMap, removedCount } = stripUdsEntries(config.hooks);
|
|
103
|
+
if (removedCount === 0) {
|
|
104
|
+
result.skipped.push(`${label} (no UDS hook entries found)`);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const entryLabel = `${label} (${removedCount} UDS hook ${removedCount === 1 ? 'entry' : 'entries'})`;
|
|
109
|
+
if (dryRun) {
|
|
110
|
+
result.removed.push(entryLabel);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const updatedConfig = applyHooksMap(config, nextMap);
|
|
115
|
+
const isNowEmpty = Object.keys(updatedConfig).length === 0;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
if (deleteEmptyFile && isNowEmpty) {
|
|
119
|
+
unlinkSync(configPath);
|
|
120
|
+
result.removed.push(`${entryLabel}, file removed — created by UDS, now empty`);
|
|
121
|
+
} else {
|
|
122
|
+
writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2) + '\n');
|
|
123
|
+
result.removed.push(entryLabel);
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
result.errors.push(`${label} — ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Remove UDS's Stop/PreToolUse/etc. entries from .claude/settings.json (installHooks()'s output). */
|
|
133
|
+
export function uninstallClaudeCodeHooks(projectPath, options = {}) {
|
|
134
|
+
return uninstallHookConfigFile({
|
|
135
|
+
configPath: join(projectPath, '.claude', 'settings.json'),
|
|
136
|
+
label: '.claude/settings.json',
|
|
137
|
+
dryRun: options.dryRun || false,
|
|
138
|
+
deleteEmptyFile: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Remove UDS's Stop entry from .codex/hooks.json (installCodexHooks()'s output). */
|
|
143
|
+
export function uninstallCodexHooks(projectPath, options = {}) {
|
|
144
|
+
return uninstallHookConfigFile({
|
|
145
|
+
configPath: join(projectPath, '.codex', 'hooks.json'),
|
|
146
|
+
label: '.codex/hooks.json',
|
|
147
|
+
dryRun: options.dryRun || false,
|
|
148
|
+
deleteEmptyFile: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Remove UDS's AfterAgent entry from .gemini/settings.json (installGeminiHooks()'s output). */
|
|
153
|
+
export function uninstallGeminiHooks(projectPath, options = {}) {
|
|
154
|
+
return uninstallHookConfigFile({
|
|
155
|
+
configPath: join(projectPath, '.gemini', 'settings.json'),
|
|
156
|
+
label: '.gemini/settings.json',
|
|
157
|
+
dryRun: options.dryRun || false,
|
|
158
|
+
deleteEmptyFile: true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Remove UDS-related lines from .husky/pre-commit, the native
|
|
164
|
+
* .git/hooks/pre-commit fallback, and the enforcement-hook entries UDS wrote
|
|
165
|
+
* into .claude/settings.json, .codex/hooks.json and .gemini/settings.json.
|
|
6
166
|
* @param {string} projectPath - Project root path
|
|
7
167
|
* @param {Object} options - { dryRun: boolean }
|
|
8
168
|
* @returns {Object} { removed: string[], skipped: string[], errors: string[] }
|
|
@@ -14,29 +174,24 @@ export function uninstallHook(projectPath, options = {}) {
|
|
|
14
174
|
|
|
15
175
|
if (!existsSync(hookPath)) {
|
|
16
176
|
result.skipped.push('.husky/pre-commit (not found)');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const udsPattern = /uds\s+check|checkin-standards/;
|
|
24
|
-
const filteredLines = lines.filter(line => !udsPattern.test(line));
|
|
25
|
-
|
|
26
|
-
if (filteredLines.length === lines.length) {
|
|
27
|
-
result.skipped.push('.husky/pre-commit (no UDS lines found)');
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
177
|
+
} else {
|
|
178
|
+
try {
|
|
179
|
+
const content = readFileSync(hookPath, 'utf-8');
|
|
180
|
+
const lines = content.split('\n');
|
|
181
|
+
const udsPattern = /uds\s+check|checkin-standards/;
|
|
182
|
+
const filteredLines = lines.filter(line => !udsPattern.test(line));
|
|
30
183
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
184
|
+
if (filteredLines.length === lines.length) {
|
|
185
|
+
result.skipped.push('.husky/pre-commit (no UDS lines found)');
|
|
186
|
+
} else if (dryRun) {
|
|
187
|
+
result.removed.push('.husky/pre-commit (UDS check lines)');
|
|
188
|
+
} else {
|
|
189
|
+
writeFileSync(hookPath, filteredLines.join('\n'), 'utf-8');
|
|
190
|
+
result.removed.push('.husky/pre-commit (UDS check lines)');
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
result.errors.push(`.husky/pre-commit — ${error.message}`);
|
|
34
194
|
}
|
|
35
|
-
|
|
36
|
-
writeFileSync(hookPath, filteredLines.join('\n'), 'utf-8');
|
|
37
|
-
result.removed.push('.husky/pre-commit (UDS check lines)');
|
|
38
|
-
} catch (error) {
|
|
39
|
-
result.errors.push(`.husky/pre-commit — ${error.message}`);
|
|
40
195
|
}
|
|
41
196
|
|
|
42
197
|
// Also handle native .git/hooks/pre-commit (installed by uds init for non-Node projects)
|
|
@@ -70,5 +225,17 @@ export function uninstallHook(projectPath, options = {}) {
|
|
|
70
225
|
}
|
|
71
226
|
}
|
|
72
227
|
|
|
228
|
+
// Enforcement hooks written by installHooks()/installCodexHooks()/installGeminiHooks()
|
|
229
|
+
// — the gap this function used to have entirely (see module doc comment above).
|
|
230
|
+
for (const sub of [
|
|
231
|
+
uninstallClaudeCodeHooks(projectPath, { dryRun }),
|
|
232
|
+
uninstallCodexHooks(projectPath, { dryRun }),
|
|
233
|
+
uninstallGeminiHooks(projectPath, { dryRun }),
|
|
234
|
+
]) {
|
|
235
|
+
result.removed.push(...sub.removed);
|
|
236
|
+
result.skipped.push(...sub.skipped);
|
|
237
|
+
result.errors.push(...sub.errors);
|
|
238
|
+
}
|
|
239
|
+
|
|
73
240
|
return result;
|
|
74
241
|
}
|
package/standards-registry.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"version": "6.13.0-beta.
|
|
3
|
+
"version": "6.13.0-beta.2",
|
|
4
4
|
"lastUpdated": "2026-05-13",
|
|
5
5
|
"description": "Standards registry for universal-dev-standards with integrated skills and AI-optimized formats",
|
|
6
6
|
"formats": {
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"standards": {
|
|
59
59
|
"name": "universal-dev-standards",
|
|
60
60
|
"url": "https://github.com/AsiaOstrich/universal-dev-standards",
|
|
61
|
-
"version": "6.13.0-beta.
|
|
61
|
+
"version": "6.13.0-beta.2"
|
|
62
62
|
},
|
|
63
63
|
"skills": {
|
|
64
64
|
"name": "universal-dev-standards",
|
|
65
65
|
"url": "https://github.com/AsiaOstrich/universal-dev-standards",
|
|
66
66
|
"localPath": "skills",
|
|
67
67
|
"rawUrl": "https://raw.githubusercontent.com/AsiaOstrich/universal-dev-standards/main/skills",
|
|
68
|
-
"version": "6.13.0-beta.
|
|
68
|
+
"version": "6.13.0-beta.2",
|
|
69
69
|
"note": "Skills are now included in the main repository under skills/"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
@@ -2295,7 +2295,7 @@
|
|
|
2295
2295
|
"id": "license-compliance",
|
|
2296
2296
|
"name": "License Compliance Standards",
|
|
2297
2297
|
"nameZh": "授權合規標準",
|
|
2298
|
-
"version": "6.13.0-beta.
|
|
2298
|
+
"version": "6.13.0-beta.2",
|
|
2299
2299
|
"source": {
|
|
2300
2300
|
"human": "core/license-compliance.md",
|
|
2301
2301
|
"ai": "ai/standards/license-compliance.ai.yaml"
|
|
@@ -2307,7 +2307,7 @@
|
|
|
2307
2307
|
"id": "verification-oracle",
|
|
2308
2308
|
"name": "Verification Oracle Standards",
|
|
2309
2309
|
"nameZh": "驗證 Oracle 標準",
|
|
2310
|
-
"version": "6.13.0-beta.
|
|
2310
|
+
"version": "6.13.0-beta.2",
|
|
2311
2311
|
"source": {
|
|
2312
2312
|
"human": "core/verification-oracle.md",
|
|
2313
2313
|
"ai": "ai/standards/verification-oracle.ai.yaml"
|
|
@@ -2319,7 +2319,7 @@
|
|
|
2319
2319
|
"id": "model-provenance",
|
|
2320
2320
|
"name": "Model Provenance Policy Standards",
|
|
2321
2321
|
"nameZh": "模型來源政策標準",
|
|
2322
|
-
"version": "6.13.0-beta.
|
|
2322
|
+
"version": "6.13.0-beta.2",
|
|
2323
2323
|
"source": {
|
|
2324
2324
|
"human": "core/model-provenance.md",
|
|
2325
2325
|
"ai": "ai/standards/model-provenance.ai.yaml"
|
|
@@ -2331,7 +2331,7 @@
|
|
|
2331
2331
|
"id": "resource-cost-boundary",
|
|
2332
2332
|
"name": "Resource / Cost Boundary Declaration Standards",
|
|
2333
2333
|
"nameZh": "資源/成本邊界宣告標準",
|
|
2334
|
-
"version": "6.13.0-beta.
|
|
2334
|
+
"version": "6.13.0-beta.2",
|
|
2335
2335
|
"source": {
|
|
2336
2336
|
"human": "core/resource-cost-boundary.md",
|
|
2337
2337
|
"ai": "ai/standards/resource-cost-boundary.ai.yaml"
|