universal-dev-standards 6.14.0-beta.1 → 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/bin/uds.js +37 -0
- package/bundled/ai/standards/open-work-tracking.ai.yaml +7 -2
- package/bundled/core/open-work-tracking.md +1 -1
- package/bundled/hooks/turn-completion/locales/en.mjs +54 -5
- package/bundled/hooks/turn-completion/locales/zh-TW.mjs +37 -6
- package/bundled/locales/zh-CN/CHANGELOG.md +38 -3
- package/bundled/locales/zh-CN/README.md +2 -2
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +2 -0
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +24 -0
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +11 -4
- package/bundled/locales/zh-TW/CHANGELOG.md +38 -3
- package/bundled/locales/zh-TW/README.md +2 -2
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/open-work-tracking.md +4 -4
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +2 -0
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +24 -0
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +11 -4
- package/package.json +1 -1
- package/src/commands/check.js +9 -0
- package/src/commands/init.js +73 -18
- package/src/commands/open-work.js +60 -0
- package/src/commands/uninstall.js +144 -30
- package/src/commands/update.js +149 -0
- package/src/core/install-records.js +191 -0
- package/src/i18n/messages.js +42 -6
- package/src/installers/hooks-installer.js +236 -27
- 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/detector.js +46 -1
- 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 +794 -0
- package/standards-registry.json +7 -7
|
@@ -886,6 +886,30 @@ UDS 的專案——並在 `[pre-commit]` 底下回報同樣的修復方式;此
|
|
|
886
886
|
(已評估、不支援),見
|
|
887
887
|
[支援的執行環境](../../../core/turn-completion-integrity.md#supported-harnesses)。
|
|
888
888
|
|
|
889
|
+
### 為已初始化的專案補裝 Hooks(`uds update --with-hooks`)
|
|
890
|
+
|
|
891
|
+
`uds init` 不能跑第二次,所以 `--with-hooks` 到不了已經初始化的專案——包括在 UDS 支援某個工具**之前**
|
|
892
|
+
(或在偵測認得它之前)就初始化的專案。`uds update --with-hooks` 就是那扇門:
|
|
893
|
+
|
|
894
|
+
```bash
|
|
895
|
+
uds update --with-hooks --plan # 列出會裝什麼;不寫任何檔
|
|
896
|
+
uds update --with-hooks # 補裝缺少的 hooks
|
|
897
|
+
uds update --with-hooks --ai-tool antigravity # 直接指定工具,不做偵測
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
- **裝給哪些工具。** `.standards/manifest.json` 裡的工具,加上專案檔案現在看得出來的(Claude Code:
|
|
901
|
+
`.claude/` 或 `CLAUDE.md`;Codex:根目錄 `AGENTS.md`;Gemini CLI:`GEMINI.md`;Antigravity:
|
|
902
|
+
`.agents/AGENTS.md`、`.agents/rules/`、`.agents/workflows/`、`.agents/plugins/` 或
|
|
903
|
+
`.agents/hooks.json`)。**`.agents/skills/` 不算 Antigravity 的標記**——Codex 也從同一個目錄讀專案技能,
|
|
904
|
+
所以它分不出是哪個工具。`--ai-tool <清單>`(`claude-code`、`codex`、`gemini-cli`、`antigravity`,
|
|
905
|
+
以逗號分隔)會取代偵測。找不到任何工具時,它會說明、印出如何指定,並以 1 結束。
|
|
906
|
+
- **不會動什麼。** 已經裝好的 hook 不會被重寫。你自己的 hooks 不會被移除或重排:Claude、Codex、Gemini 的
|
|
907
|
+
項目是合併進去;Antigravity 的 `.agents/hooks.json` 只寫在 `uds-turn-completion-integrity` 這個鍵底下
|
|
908
|
+
(不是合法 JSON 的 `hooks.json` 會原樣保留並回報)。`scripts/hooks/` 裡與隨附版本不同的 hook 腳本會被保留並回報;
|
|
909
|
+
加上 `--force` 才會覆寫。
|
|
910
|
+
- **它不做什麼。** 不更新標準、技能或整合檔(那是一般的 `uds update`),也不會把工具加進 manifest。
|
|
911
|
+
它不能與 `--skills`、`--commands` 等其他 update 模式合用,合用時會明說。
|
|
912
|
+
|
|
889
913
|
### Claude Code 整合目標檔(`--claude-target`)
|
|
890
914
|
|
|
891
915
|
UDS 預設把 Claude Code 內容寫進 `CLAUDE.md`——團隊共用、會進版控的那個檔案。
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
|
|
10
10
|
## 目錄
|
|
11
11
|
|
|
12
|
-
1. [CLI 指令](#cli-commands) (
|
|
12
|
+
1. [CLI 指令](#cli-commands) (24)
|
|
13
13
|
2. [斜線命令](#slash-commands) (51)
|
|
14
14
|
3. [技能](#skills) (55)
|
|
15
15
|
4. [代理](#agents) (5)
|
|
16
16
|
5. [工作流程](#workflows) (5)
|
|
17
17
|
6. [核心規範](#core-standards) (153)
|
|
18
|
-
7. [腳本](#scripts) (
|
|
18
|
+
7. [腳本](#scripts) (63)
|
|
19
19
|
|
|
20
|
-
**Total Features:
|
|
20
|
+
**Total Features: 356**
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -158,6 +158,8 @@
|
|
|
158
158
|
| `--rollback` | Rollback to the most recent backup |
|
|
159
159
|
| `--claude-target` | Switch an existing install to a different Claude Code integration target: project (CLAUDE.md) or local (CLAUDE.local.md) — moves the UDS block, keeps your content, no reinstall |
|
|
160
160
|
| `--locale` | Override locale for skills install (zh-tw, zh-cn, en); also reads .uds/install.yaml + UDS_LOCALE env |
|
|
161
|
+
| `--with-hooks` | Install the enforcement hooks that are missing from this already-initialized project (re-detects tools; hooks already there and your own hooks are not touched; with --plan, writes nothing; --force also overwrites edited hook scripts) |
|
|
162
|
+
| `--ai-tool` | With --with-hooks: comma-separated tools to install hooks for (claude-code, codex, gemini-cli, antigravity) instead of detecting them |
|
|
161
163
|
|
|
162
164
|
### `uds skills`
|
|
163
165
|
|
|
@@ -276,6 +278,10 @@
|
|
|
276
278
|
|
|
277
279
|
**說明**: MCP server commands for AI tool integration
|
|
278
280
|
|
|
281
|
+
### `uds open-work`
|
|
282
|
+
|
|
283
|
+
**說明**: Reference checks for open-work-tracking (OWT-017/018/019). Exit 0 no violation, 1 violation, 2 cannot decide (not a pass)
|
|
284
|
+
|
|
279
285
|
### `uds run`
|
|
280
286
|
|
|
281
287
|
**說明**: Run a project command by intent (test/lint/build/security) via uds.project.yaml
|
|
@@ -610,7 +616,8 @@
|
|
|
610
616
|
| `check-docs-sync.sh` | Documentation Sync Checker |
|
|
611
617
|
| `check-error-exit.mjs` | 🔴 沒填就是沒設定,而沒設定會 exit 2, |
|
|
612
618
|
| `check-external-references.mjs` | External Reference Checker (SPEC-SELFDIAG-001 REQ-5, AC-7) |
|
|
613
|
-
| `check-
|
|
619
|
+
| `check-home-untouched.mjs` | check-home-untouched — did this run write anywhere UDS writes under HOME? |
|
|
620
|
+
| `check-open-work-tracking.mjs` | Open-work-tracking reference checks for OWT-017 / OWT-018 / OWT-019 — repo entry point. |
|
|
614
621
|
| `check-orphan-specs.ps1` | Check Orphan Specs |
|
|
615
622
|
| `check-orphan-specs.sh` | Orphan Spec Detection Script |
|
|
616
623
|
| `check-prompt-footprint.mjs` | Prompt Footprint Ratchet — DEC-117 D2/L2 |
|
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
|
|
@@ -52,6 +53,8 @@ export async function initCommand(options) {
|
|
|
52
53
|
if (isInitialized(projectPath)) {
|
|
53
54
|
console.log(chalk.yellow(msg.alreadyInitialized));
|
|
54
55
|
console.log(chalk.gray(` ${msg.useUpdateOrDelete}`));
|
|
56
|
+
// `init` cannot run twice, so `--with-hooks` has no effect here; say where the door is.
|
|
57
|
+
if (options.withHooks) console.log(chalk.yellow(` ${msg.useUpdateWithHooks}`));
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -211,7 +214,11 @@ export async function initCommand(options) {
|
|
|
211
214
|
skillHashes: skillsResults.skillHashes,
|
|
212
215
|
commandHashes: skillsResults.commandHashes,
|
|
213
216
|
integrationBlockHashes: integrationResults.integrationBlockHashes,
|
|
214
|
-
manifestIntegrationConfigs: integrationResults.manifestIntegrationConfigs
|
|
217
|
+
manifestIntegrationConfigs: integrationResults.manifestIntegrationConfigs,
|
|
218
|
+
createdIntegrationFiles: [
|
|
219
|
+
...(integrationResults.createdFiles ?? []),
|
|
220
|
+
...(agentsMdResult.created ? [agentsMdResult.path] : [])
|
|
221
|
+
]
|
|
215
222
|
};
|
|
216
223
|
},
|
|
217
224
|
// A half-installed project must never be committed: if any installer
|
|
@@ -256,9 +263,13 @@ export async function initCommand(options) {
|
|
|
256
263
|
}
|
|
257
264
|
|
|
258
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();
|
|
259
270
|
if (config.withHooks) {
|
|
260
271
|
const { installHooks } = await import('../installers/hooks-installer.js');
|
|
261
|
-
const hookResult = installHooks(projectPath);
|
|
272
|
+
const hookResult = installHooks(projectPath, { recorder: installRecorder });
|
|
262
273
|
if (hookResult.installed) {
|
|
263
274
|
console.log(chalk.green(
|
|
264
275
|
` ✓ Enforcement hooks installed (${hookResult.scriptsCount} scripts, `
|
|
@@ -290,7 +301,7 @@ export async function initCommand(options) {
|
|
|
290
301
|
const selectedTools = config.integrations || config.aiTools || [];
|
|
291
302
|
if (selectedTools.includes('codex')) {
|
|
292
303
|
const { installCodexHooks } = await import('../installers/hooks-installer.js');
|
|
293
|
-
const codexResult = installCodexHooks(projectPath);
|
|
304
|
+
const codexResult = installCodexHooks(projectPath, { recorder: installRecorder });
|
|
294
305
|
if (codexResult.installed) {
|
|
295
306
|
console.log(chalk.green(' ✓ Codex Stop hook installed (turn-completion-integrity)'));
|
|
296
307
|
// Measured 2026-09-28 (codex-cli 0.155.1): Codex skips a project hook
|
|
@@ -304,7 +315,7 @@ export async function initCommand(options) {
|
|
|
304
315
|
}
|
|
305
316
|
if (selectedTools.includes('gemini-cli')) {
|
|
306
317
|
const { installGeminiHooks } = await import('../installers/hooks-installer.js');
|
|
307
|
-
const geminiResult = installGeminiHooks(projectPath);
|
|
318
|
+
const geminiResult = installGeminiHooks(projectPath, { recorder: installRecorder });
|
|
308
319
|
if (geminiResult.installed) {
|
|
309
320
|
console.log(chalk.green(' ✓ Gemini CLI AfterAgent hook installed (turn-completion-integrity; Gemini CLI is legacy — see the standard)'));
|
|
310
321
|
} else {
|
|
@@ -313,7 +324,7 @@ export async function initCommand(options) {
|
|
|
313
324
|
}
|
|
314
325
|
if (selectedTools.includes('antigravity')) {
|
|
315
326
|
const { installAgyHooks } = await import('../installers/hooks-installer.js');
|
|
316
|
-
const agyResult = installAgyHooks(projectPath);
|
|
327
|
+
const agyResult = installAgyHooks(projectPath, { recorder: installRecorder });
|
|
317
328
|
if (agyResult.installed) {
|
|
318
329
|
console.log(chalk.green(' ✓ Antigravity CLI Stop hook installed (turn-completion-integrity; .agents/hooks.json)'));
|
|
319
330
|
// Verified 2026-09-29 (agy 1.2.12): single turn, no tool calls, `agy -p`.
|
|
@@ -326,6 +337,15 @@ export async function initCommand(options) {
|
|
|
326
337
|
}
|
|
327
338
|
}
|
|
328
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
|
+
|
|
329
349
|
// 5. Setup Pre-commit Hook
|
|
330
350
|
await setupHuskyHook(projectPath);
|
|
331
351
|
|
|
@@ -337,7 +357,7 @@ export async function initCommand(options) {
|
|
|
337
357
|
* - Node.js projects: use husky
|
|
338
358
|
* - Non-Node.js projects: write native .git/hooks/pre-commit
|
|
339
359
|
*/
|
|
340
|
-
export async function setupHuskyHook(projectPath, { allowInTest = false } = {}) {
|
|
360
|
+
export async function setupHuskyHook(projectPath, { allowInTest = false, platform = process.platform } = {}) {
|
|
341
361
|
// 2026-07-30:本函式在跑測試時改寫了 universal-dev-standards 自己
|
|
342
362
|
// ——`npm install --save-dev husky`、改 package.json、建 .husky/pre-commit。
|
|
343
363
|
//
|
|
@@ -357,6 +377,9 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
357
377
|
|
|
358
378
|
const isNodeProject = existsSync(join(projectPath, 'package.json'));
|
|
359
379
|
|
|
380
|
+
// What this function creates, for `uds uninstall` (see core/install-records.js).
|
|
381
|
+
const recorder = newRecorder();
|
|
382
|
+
|
|
360
383
|
if (isNodeProject) {
|
|
361
384
|
console.log(chalk.cyan('Configuring Pre-commit Hook...'));
|
|
362
385
|
|
|
@@ -434,7 +457,7 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
434
457
|
// 3. Ensure .husky directory exists
|
|
435
458
|
if (!existsSync(huskyDir)) {
|
|
436
459
|
try {
|
|
437
|
-
|
|
460
|
+
mkdirTracked(recorder, projectPath, huskyDir);
|
|
438
461
|
} catch (e) {
|
|
439
462
|
console.log(chalk.red(` ✗ Failed to create .husky directory: ${e.message}`));
|
|
440
463
|
return;
|
|
@@ -443,8 +466,10 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
443
466
|
|
|
444
467
|
// 4. Add pre-commit hook content
|
|
445
468
|
const preCommitPath = join(huskyDir, 'pre-commit');
|
|
446
|
-
const udsCmd = 'npx uds check';
|
|
447
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);
|
|
448
473
|
try {
|
|
449
474
|
// husky v9's own templates carry no shebang, no `_/husky.sh` sourcing
|
|
450
475
|
// (that is v8 syntax, deprecated in v9 and removed in v10). Verified
|
|
@@ -462,6 +487,18 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
462
487
|
// — cheap, harmless on POSIX, and required on Windows. Existing files
|
|
463
488
|
// are appended to, never rewritten — their contents are the adopter's,
|
|
464
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
|
+
|
|
465
502
|
let content = existsSync(preCommitPath) ? readFileSync(preCommitPath, 'utf-8') : '';
|
|
466
503
|
|
|
467
504
|
// A pre-existing file may still carry husky v8's `_/husky.sh` sourcing
|
|
@@ -477,10 +514,10 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
477
514
|
const { content: destripped, removed: hadLegacyLine } = stripLegacyHuskyShLine(content);
|
|
478
515
|
content = destripped;
|
|
479
516
|
|
|
480
|
-
const needsAppend = !content
|
|
517
|
+
const needsAppend = !hookRunsUdsCheck(content);
|
|
481
518
|
if (needsAppend) {
|
|
482
519
|
const sep = content && !content.endsWith('\n') ? '\n' : '';
|
|
483
|
-
content = `${content}${sep}\n
|
|
520
|
+
content = `${content}${sep}\n${buildPreCommitBlock()}`;
|
|
484
521
|
}
|
|
485
522
|
|
|
486
523
|
const { content: shebanged, added: addedShebang } = ensureShebang(content);
|
|
@@ -488,10 +525,22 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
488
525
|
|
|
489
526
|
if (hadLegacyLine || needsAppend || addedShebang) {
|
|
490
527
|
writeFileSync(preCommitPath, content, 'utf-8');
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
+
}
|
|
495
544
|
}
|
|
496
545
|
}
|
|
497
546
|
|
|
@@ -510,6 +559,8 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
510
559
|
console.log(chalk.red(` ✗ Failed to configure pre-commit hook: ${e.message}`));
|
|
511
560
|
}
|
|
512
561
|
|
|
562
|
+
persistRecorder(projectPath, recorder);
|
|
563
|
+
|
|
513
564
|
// 5. Wire git so the hook ACTUALLY runs — this is the fix. Setting
|
|
514
565
|
// `core.hooksPath` ourselves is what husky's own bootstrap does internally
|
|
515
566
|
// (verified against husky ^9.1.7's source: `git config core.hooksPath
|
|
@@ -565,7 +616,7 @@ export async function setupHuskyHook(projectPath, { allowInTest = false } = {})
|
|
|
565
616
|
|
|
566
617
|
if (existsSync(hookPath)) {
|
|
567
618
|
const existingContent = readFileSync(hookPath, 'utf-8');
|
|
568
|
-
if (existingContent
|
|
619
|
+
if (hookRunsUdsCheck(existingContent)) {
|
|
569
620
|
console.log(chalk.gray(' ✓ Pre-commit hook already configured'));
|
|
570
621
|
} else {
|
|
571
622
|
// Never clobber an adopter's own hook (this fix — it used to be
|
|
@@ -595,6 +646,10 @@ uds check 2>/dev/null || true
|
|
|
595
646
|
echo "Pre-commit checks passed"
|
|
596
647
|
`;
|
|
597
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);
|
|
598
653
|
console.log(chalk.green(' ✓ Installed .git/hooks/pre-commit (native git hook)'));
|
|
599
654
|
}
|
|
600
655
|
} catch (e) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `uds open-work` — the open-work-tracking reference checks (OWT-017 / 018 / 019)
|
|
3
|
+
* as a command, so an adopter can run them from the npm package.
|
|
4
|
+
*
|
|
5
|
+
* This file holds no rules. The one body of them is
|
|
6
|
+
* `../utils/open-work-tracking.mjs`; this only turns commander's parsed
|
|
7
|
+
* arguments back into the argv that module's `main()` reads, so the command and
|
|
8
|
+
* `node scripts/check-open-work-tracking.mjs` cannot disagree.
|
|
9
|
+
*
|
|
10
|
+
* Exit codes are the module's and are not translated: 0 no violation, 1
|
|
11
|
+
* violation, 2 cannot decide (no structure found, git failed, or the checker's
|
|
12
|
+
* own self-test arms failed). 2 is NOT a pass. `process.exitCode` is set rather
|
|
13
|
+
* than calling `process.exit()`, so commander's post-action hook still runs.
|
|
14
|
+
*
|
|
15
|
+
* It is a reference decision procedure offered as evidence (OWT-015), not a
|
|
16
|
+
* gate, and its coverage is unknown and uncalibrated (OWT-011, OWT-016); the
|
|
17
|
+
* module prints both statements on every run.
|
|
18
|
+
*
|
|
19
|
+
* @module commands/open-work
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { main } from '../utils/open-work-tracking.mjs';
|
|
23
|
+
|
|
24
|
+
/** Append `--name value` when the option was given. */
|
|
25
|
+
function withOption(argv, name, value) {
|
|
26
|
+
if (value !== undefined && value !== null) argv.push(name, String(value));
|
|
27
|
+
return argv;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function run(argv) {
|
|
31
|
+
process.exitCode = main(argv);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** uds open-work next-action [files...] [--root DIR] [--id-pattern RE] — OWT-019 */
|
|
35
|
+
export function openWorkNextActionCommand(files = [], options = {}) {
|
|
36
|
+
const argv = ['next-action'];
|
|
37
|
+
withOption(argv, '--root', options.root);
|
|
38
|
+
withOption(argv, '--id-pattern', options.idPattern);
|
|
39
|
+
run([...argv, ...files]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** uds open-work revision (--file PATH --base REV | --before FILE --after FILE) — OWT-018 */
|
|
43
|
+
export function openWorkRevisionCommand(options = {}) {
|
|
44
|
+
const argv = ['revision'];
|
|
45
|
+
withOption(argv, '--file', options.file);
|
|
46
|
+
withOption(argv, '--base', options.base);
|
|
47
|
+
withOption(argv, '--before', options.before);
|
|
48
|
+
withOption(argv, '--after', options.after);
|
|
49
|
+
run(argv);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** uds open-work separation [files...] — OWT-017 */
|
|
53
|
+
export function openWorkSeparationCommand(files = []) {
|
|
54
|
+
run(['separation', ...files]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** uds open-work self-test — run the checker's own arms and nothing else */
|
|
58
|
+
export function openWorkSelfTestCommand() {
|
|
59
|
+
run(['--self-test']);
|
|
60
|
+
}
|