universal-dev-standards 6.12.0 → 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/ai/standards/developer-memory.ai.yaml +24 -2
- package/bundled/ai/standards/turn-completion-integrity.ai.yaml +34 -2
- package/bundled/core/developer-memory.md +58 -2
- package/bundled/core/turn-completion-integrity.md +44 -2
- package/bundled/hooks/check-turn-completion-codex.mjs +143 -0
- package/bundled/hooks/check-turn-completion-gemini.mjs +81 -0
- package/bundled/hooks/check-turn-completion.mjs +24 -149
- package/bundled/hooks/turn-completion/engine.mjs +206 -0
- package/bundled/hooks/turn-completion/locales/en.mjs +50 -2
- package/bundled/hooks/turn-completion/locales/zh-TW.mjs +54 -3
- package/bundled/locales/zh-CN/CHANGELOG.md +27 -3
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-CN/SECURITY.md +1 -0
- package/bundled/locales/zh-CN/core/turn-completion-integrity.md +43 -6
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +24 -5
- package/bundled/locales/zh-TW/CHANGELOG.md +27 -3
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/SECURITY.md +1 -0
- package/bundled/locales/zh-TW/core/turn-completion-integrity.md +43 -6
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +24 -5
- package/package.json +1 -1
- package/src/commands/init.js +26 -0
- package/src/commands/uninstall.js +1 -1
- package/src/installers/hooks-installer.js +108 -0
- package/src/uninstallers/hook-uninstaller.js +190 -23
- package/standards-registry.json +7 -7
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|