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.
Files changed (37) hide show
  1. package/bin/uds.js +37 -0
  2. package/bundled/ai/standards/open-work-tracking.ai.yaml +7 -2
  3. package/bundled/core/open-work-tracking.md +1 -1
  4. package/bundled/hooks/turn-completion/locales/en.mjs +54 -5
  5. package/bundled/hooks/turn-completion/locales/zh-TW.mjs +37 -6
  6. package/bundled/locales/zh-CN/CHANGELOG.md +38 -3
  7. package/bundled/locales/zh-CN/README.md +2 -2
  8. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  9. package/bundled/locales/zh-CN/docs/CHEATSHEET.md +2 -0
  10. package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +24 -0
  11. package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +11 -4
  12. package/bundled/locales/zh-TW/CHANGELOG.md +38 -3
  13. package/bundled/locales/zh-TW/README.md +2 -2
  14. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  15. package/bundled/locales/zh-TW/core/open-work-tracking.md +4 -4
  16. package/bundled/locales/zh-TW/docs/CHEATSHEET.md +2 -0
  17. package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +24 -0
  18. package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +11 -4
  19. package/package.json +1 -1
  20. package/src/commands/check.js +9 -0
  21. package/src/commands/init.js +73 -18
  22. package/src/commands/open-work.js +60 -0
  23. package/src/commands/uninstall.js +144 -30
  24. package/src/commands/update.js +149 -0
  25. package/src/core/install-records.js +191 -0
  26. package/src/i18n/messages.js +42 -6
  27. package/src/installers/hooks-installer.js +236 -27
  28. package/src/installers/integration-installer.js +5 -1
  29. package/src/uninstallers/hook-uninstaller.js +216 -32
  30. package/src/uninstallers/integration-uninstaller.js +35 -5
  31. package/src/utils/detector.js +46 -1
  32. package/src/utils/git-hooks.js +135 -7
  33. package/src/utils/hasher.js +36 -0
  34. package/src/utils/integration-generator.js +16 -6
  35. package/src/utils/legacy-hook-migration.js +112 -0
  36. package/src/utils/open-work-tracking.mjs +794 -0
  37. 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) (23)
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) (62)
18
+ 7. [腳本](#scripts) (63)
19
19
 
20
- **Total Features: 354**
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-open-work-tracking.mjs` | Open-work-tracking reference checks for OWT-017 / OWT-018 / OWT-019. |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-dev-standards",
3
- "version": "6.14.0-beta.1",
3
+ "version": "6.14.0-beta.3",
4
4
  "description": "CLI tool for adopting Universal Development Standards",
5
5
  "keywords": [
6
6
  "documentation",
@@ -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
@@ -1,7 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { createSpinner } from '../utils/spinner.js';
3
- import { execSync } from 'child_process';
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 { wireGitHooksPath, getLocalHooksPathConfig, stripLegacyHuskyShLine, ensureShebang } from '../utils/git-hooks.js';
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
- mkdirSync(huskyDir, { recursive: true });
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.includes('uds check');
517
+ const needsAppend = !hookRunsUdsCheck(content);
481
518
  if (needsAppend) {
482
519
  const sep = content && !content.endsWith('\n') ? '\n' : '';
483
- content = `${content}${sep}\n# UDS Standard Check\n${udsCmd}\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
- try {
492
- execSync(`chmod +x ${preCommitPath}`);
493
- } catch {
494
- // Ignore chmod failures on systems that don't support it
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.includes('uds check')) {
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
+ }