auriga-cli 1.25.0 → 1.27.0

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/dist/preset.js ADDED
@@ -0,0 +1,84 @@
1
+ // src/preset.ts
2
+ //
3
+ // installPreset —— 「推荐预设安装」的单一编排入口。
4
+ //
5
+ // 预设由三部分组成,按下面的顺序安装:
6
+ // 1. workflow 文档 (CLAUDE.md + AGENTS.md)
7
+ // 2. 工作流 skill (WORKFLOW_SKILLS 全集 —— installSkills 自身已限定)
8
+ // 3. auriga-workflow 插件
9
+ //
10
+ // CLI 的 `--preset`、TUI 的「推荐预设」、Web UI 的一键按钮全部走这一个
11
+ // 函数,因此「预设由什么构成」只有这一处真相。
12
+ //
13
+ // installPreset 返回逐步成败摘要 (PresetStepResult[]):
14
+ // - CLI 用它计算分级退出码(全成功 0 / 部分或全部失败 2),与 runAll
15
+ // 的 graded-exit 语义对齐;
16
+ // - TUI / Web UI 忽略该摘要 —— 各自走 log-and-continue / 流式进度。
17
+ //
18
+ // 三个 installer 经动态 import 引入而非静态 import:这样 preset.ts 是一个
19
+ // 零重依赖的薄编排层 —— 只想读 PRESET_PLUGINS 常量的调用方(参数校验、
20
+ // 帮助文案)不会被迫拉入 plugins.ts 这张大依赖图;installer 模块也只在
21
+ // installPreset 真正被调用时才进入模块图。
22
+ /**
23
+ * 预设安装的插件成员 —— 固定只装 auriga-workflow。
24
+ * installPlugins 的 `selected` 过滤把安装面收敛到这一个插件。
25
+ * `as const` 冻结这个「单一真相」常量,调用方无法 .push() 篡改它。
26
+ */
27
+ export const PRESET_PLUGINS = ["auriga-workflow"];
28
+ /** 预设的安装顺序:文档 → skill → 插件。 */
29
+ const PRESET_STEPS = [
30
+ "workflow",
31
+ "skills",
32
+ "plugins",
33
+ ];
34
+ /**
35
+ * 按 workflow → skills → plugins 顺序执行预设安装。
36
+ *
37
+ * 每一步独立 try/catch:一步失败不阻断后续步骤(与 runAll 的
38
+ * log-and-continue 一致),逐步成败汇总后返回给调用方。
39
+ */
40
+ export async function installPreset(packageRoot, opts) {
41
+ const results = [];
42
+ for (const category of PRESET_STEPS) {
43
+ try {
44
+ await runPresetStep(category, packageRoot, opts);
45
+ results.push({ category, ok: true });
46
+ }
47
+ catch (e) {
48
+ results.push({ category, ok: false, err: e.message });
49
+ }
50
+ }
51
+ return results;
52
+ }
53
+ async function runPresetStep(category, packageRoot, opts) {
54
+ // scope / agent / lang 一并透传给每个 installer —— installer 各取所需
55
+ // (workflow 只用 lang/cwd,skills/plugins 只用 scope/agent),用不到的
56
+ // 字段被忽略。
57
+ const base = {
58
+ interactive: opts.interactive,
59
+ scope: opts.scope,
60
+ agent: opts.agent,
61
+ lang: opts.lang,
62
+ cwd: opts.cwd,
63
+ onLog: opts.onLog,
64
+ };
65
+ switch (category) {
66
+ case "workflow": {
67
+ const { installWorkflow } = await import("./workflow.js");
68
+ return installWorkflow(packageRoot, base);
69
+ }
70
+ case "skills": {
71
+ // installSkills 内部已把安装范围限定到 WORKFLOW_SKILLS 全集 ——
72
+ // 不传 selected 即安装这组工作流 skill,无需在此重复列举。
73
+ const { installSkills } = await import("./skills.js");
74
+ return installSkills(packageRoot, base);
75
+ }
76
+ case "plugins": {
77
+ const { installPlugins } = await import("./plugins.js");
78
+ return installPlugins(packageRoot, {
79
+ ...base,
80
+ selected: [...PRESET_PLUGINS],
81
+ });
82
+ }
83
+ }
84
+ }
@@ -7,7 +7,7 @@ import { loadCatalog } from "./catalog.js";
7
7
  export async function buildScanCatalog(packageRoot) {
8
8
  const dist = loadCatalog(packageRoot);
9
9
  // v1.19.0 dropped update-available status. The scanner is now presence-
10
- // only: skills / hooks / plugins / workflow all report installed iff their
10
+ // only: skills / plugins / workflow all report installed iff their
11
11
  // truth source exists, not-installed otherwise. No version / hash / event
12
12
  // comparison happens, so the build-time catalog is reduced to the bare
13
13
  // {description, agents?, external?} shape per entry.
@@ -35,9 +35,5 @@ export async function buildScanCatalog(packageRoot) {
35
35
  ...(entry.external === true ? { external: true } : {}),
36
36
  };
37
37
  }
38
- const hooks = {};
39
- for (const entry of dist.hooks) {
40
- hooks[entry.name] = { description: entry.description };
41
- }
42
- return { skills, recommendedSkills, plugins, hooks };
38
+ return { skills, recommendedSkills, plugins };
43
39
  }
package/dist/server.d.ts CHANGED
@@ -5,9 +5,12 @@ export interface ApplyHandlerOptions {
5
5
  * translate into the per-installer flag (`--scope project|user`). The
6
6
  * workflow handler ignores it (workflow has no scope concept). */
7
7
  scope?: "project" | "user";
8
- /** Workflow CLAUDE.md language variant. Only meaningful for the workflow
9
- * handler; other handlers ignore it. Omitted = "en". */
8
+ /** Workflow CLAUDE.md language variant. Meaningful for the workflow and
9
+ * preset handlers; other handlers ignore it. Omitted = "en". */
10
10
  lang?: "en" | "zh-CN";
11
+ /** Preset install runtime. Only meaningful for the preset handler;
12
+ * other handlers ignore it. Omitted = "both". */
13
+ agent?: "claude" | "codex" | "both";
11
14
  }
12
15
  export type ApplyHandler = (action: ApplyAction, name: string, opts: ApplyHandlerOptions) => Promise<void>;
13
16
  export interface ApplyHandlers {
@@ -15,14 +18,14 @@ export interface ApplyHandlers {
15
18
  skill: ApplyHandler;
16
19
  "recommended-skill": ApplyHandler;
17
20
  plugin: ApplyHandler;
18
- hook: ApplyHandler;
21
+ preset: ApplyHandler;
19
22
  }
20
23
  export interface ApplyCatalog {
21
24
  workflow: Set<string>;
22
25
  skill: Set<string>;
23
26
  "recommended-skill": Set<string>;
24
27
  plugin: Set<string>;
25
- hook: Set<string>;
28
+ preset: Set<string>;
26
29
  }
27
30
  export interface StartServerOptions {
28
31
  port?: number;
package/dist/server.js CHANGED
@@ -169,11 +169,12 @@ const VALID_CATEGORIES = new Set([
169
169
  "skill",
170
170
  "recommended-skill",
171
171
  "plugin",
172
- "hook",
172
+ "preset",
173
173
  ]);
174
174
  const VALID_ACTIONS = new Set(["install", "uninstall"]);
175
175
  const VALID_SCOPES = new Set(["project", "user"]);
176
176
  const VALID_LANGS = new Set(["en", "zh-CN"]);
177
+ const VALID_AGENTS = new Set(["claude", "codex", "both"]);
177
178
  function parseApplyRequest(raw) {
178
179
  let parsed;
179
180
  try {
@@ -190,7 +191,7 @@ function parseApplyRequest(raw) {
190
191
  for (const it of items) {
191
192
  if (!it || typeof it !== "object")
192
193
  return null;
193
- const { category, name, action, scope, lang } = it;
194
+ const { category, name, action, scope, lang, agent } = it;
194
195
  if (typeof category !== "string" || !VALID_CATEGORIES.has(category)) {
195
196
  return null;
196
197
  }
@@ -207,12 +208,21 @@ function parseApplyRequest(raw) {
207
208
  if (category === "workflow")
208
209
  return null;
209
210
  }
210
- // Lang is optional and only meaningful for category="workflow". Any
211
- // other pairing is a client bug and we reject loudly.
211
+ // Lang is optional and meaningful for category="workflow" and
212
+ // category="preset" (the preset installs the workflow doc). Any other
213
+ // pairing is a client bug and we reject loudly.
212
214
  if (lang !== undefined) {
213
215
  if (typeof lang !== "string" || !VALID_LANGS.has(lang))
214
216
  return null;
215
- if (category !== "workflow")
217
+ if (category !== "workflow" && category !== "preset")
218
+ return null;
219
+ }
220
+ // Agent is optional and only meaningful for category="preset" (the
221
+ // per-plugin agent is derived from the catalog, not client-supplied).
222
+ if (agent !== undefined) {
223
+ if (typeof agent !== "string" || !VALID_AGENTS.has(agent))
224
+ return null;
225
+ if (category !== "preset")
216
226
  return null;
217
227
  }
218
228
  }
@@ -375,6 +385,7 @@ export async function startServer(opts) {
375
385
  onLog: (line, level) => emit(job, { type: "item:log", index: i, line, level }),
376
386
  scope: item.scope,
377
387
  lang: item.lang,
388
+ agent: item.agent,
378
389
  });
379
390
  emit(job, { type: "item:done", index: i, success: true });
380
391
  }
@@ -752,7 +763,7 @@ function parseScopesParam(searchParams) {
752
763
  const raw = searchParams.get("scopes");
753
764
  if (!raw)
754
765
  return null;
755
- const allowedCategories = new Set(["workflow", "skills", "plugins", "hooks"]);
766
+ const allowedCategories = new Set(["workflow", "skills", "plugins"]);
756
767
  const allowedScopes = new Set(["user", "project"]);
757
768
  const out = {};
758
769
  for (const pair of raw.split(",")) {
@@ -801,5 +812,5 @@ const defaultHandlersNotConfigured = {
801
812
  skill: handlerNotConfigured,
802
813
  "recommended-skill": handlerNotConfigured,
803
814
  plugin: handlerNotConfigured,
804
- hook: handlerNotConfigured,
815
+ preset: handlerNotConfigured,
805
816
  };
package/dist/state.d.ts CHANGED
@@ -18,9 +18,6 @@ export interface Catalog {
18
18
  * reporting; that surface was removed). */
19
19
  external?: boolean;
20
20
  }>;
21
- hooks: Record<string, {
22
- description: string;
23
- }>;
24
21
  }
25
22
  export interface ScanOptions {
26
23
  /** Run `claude plugins list` for the given scope. The scope argument is
@@ -37,13 +34,11 @@ export interface ScanOptions {
37
34
  readCodexPluginsDir?: () => Promise<Map<string, string>>;
38
35
  /** Per-category scope picker. Each field is independently routed to the
39
36
  * right truth source. Defaults match the Web UI's per-column picker:
40
- * workflow = 'project', skills = 'project',
41
- * plugins = 'user', hooks = 'user'. */
37
+ * workflow = 'project', skills = 'project', plugins = 'user'. */
42
38
  scopes?: {
43
39
  workflow?: ScanScope;
44
40
  skills?: ScanScope;
45
41
  plugins?: ScanScope;
46
- hooks?: ScanScope;
47
42
  };
48
43
  /** Test-time HOME override. When unset the scanner reads os.homedir()
49
44
  * (which itself consults process.env.HOME / USERPROFILE), so tests that
package/dist/state.js CHANGED
@@ -8,7 +8,6 @@
8
8
  // <proj>/.claude/skills/<name>/SKILL.md (project scope)
9
9
  // Plugins(Claude): execPluginList(scope) + settings.json enabledPlugins
10
10
  // Plugins(Codex): ~/.codex/config.toml + ~/.codex/plugins/cache (user only)
11
- // Hooks: <scope>/.claude/settings.json `hooks` segment, matched by _marker
12
11
  //
13
12
  // Scanner is presence-only: states are `installed` / `not-installed` /
14
13
  // `partial-install` (dual-Agent half-install). v1.19.0 dropped
@@ -46,7 +45,6 @@ const DEFAULT_SCOPES = {
46
45
  workflow: "project",
47
46
  skills: "project",
48
47
  plugins: "user",
49
- hooks: "user",
50
48
  };
51
49
  export async function scanState(projectRoot, catalog, opts = {}) {
52
50
  const warnings = [];
@@ -56,7 +54,6 @@ export async function scanState(projectRoot, catalog, opts = {}) {
56
54
  const skills = scanSkills(scopes.skills, projectRoot, home, catalog.skills,
57
55
  /* recommended */ false, warnings);
58
56
  const recommendedSkills = scanRecommendedSkills(scopes.skills, projectRoot, home, catalog.recommendedSkills, warnings);
59
- const hooks = scanHooks(scopes.hooks, projectRoot, home, catalog.hooks, warnings);
60
57
  const claudePluginEntries = filterPluginsByAgent(catalog.plugins, "claude");
61
58
  const codexPluginEntries = filterPluginsByAgent(catalog.plugins, "codex");
62
59
  const claudePlugins = await scanClaudePlugins(scopes.plugins, claudePluginEntries, opts.execPluginList, warnings);
@@ -79,7 +76,6 @@ export async function scanState(projectRoot, catalog, opts = {}) {
79
76
  skills,
80
77
  recommendedSkills,
81
78
  plugins: mergePluginsById([...claudePlugins, ...codexPlugins]),
82
- hooks,
83
79
  warnings,
84
80
  };
85
81
  }
@@ -462,106 +458,6 @@ function parseCodexEnabledPluginIds(tomlContent) {
462
458
  return ids;
463
459
  }
464
460
  // ---------------------------------------------------------------------------
465
- // Hooks — read from <scope>/.claude/settings.json `hooks` segment, matched by
466
- // `_marker` sentinel against catalog hook names. Settings.json shape (Claude
467
- // Code convention):
468
- //
469
- // {
470
- // "hooks": {
471
- // "<EventName>": [
472
- // {
473
- // "matcher": "<pattern>",
474
- // "if": "<optional Claude-Code filter>",
475
- // "hooks": [
476
- // { "type": "command", "command": "...", "_marker": "<name>" }
477
- // ]
478
- // }
479
- // ]
480
- // }
481
- // }
482
- //
483
- // ---------------------------------------------------------------------------
484
- function settingsPathForScope(scope, projectRoot, home) {
485
- if (scope === "user")
486
- return path.join(home, ".claude", "settings.json");
487
- return path.join(projectRoot, ".claude", "settings.json");
488
- }
489
- /** Returns the set of `_marker` sentinel values present in the settings
490
- * `hooks` segment. Malformed sub-shapes are skipped silently. v1.19.0
491
- * reduced this from a full {event, matcher, if, command} record (used for
492
- * drift detection) to a presence-only Set — re-install is the update
493
- * path now, so the scanner doesn't need to compare entry shapes. */
494
- function indexSettingsMarkers(settings) {
495
- const out = new Set();
496
- if (!settings || typeof settings !== "object" || Array.isArray(settings))
497
- return out;
498
- const hooksSeg = settings.hooks;
499
- if (!hooksSeg || typeof hooksSeg !== "object" || Array.isArray(hooksSeg))
500
- return out;
501
- for (const blocks of Object.values(hooksSeg)) {
502
- if (!Array.isArray(blocks))
503
- continue;
504
- for (const block of blocks) {
505
- if (!block || typeof block !== "object" || Array.isArray(block))
506
- continue;
507
- const actions = block.hooks;
508
- if (!Array.isArray(actions))
509
- continue;
510
- for (const action of actions) {
511
- if (!action || typeof action !== "object" || Array.isArray(action))
512
- continue;
513
- const marker = action._marker;
514
- if (typeof marker === "string")
515
- out.add(marker);
516
- }
517
- }
518
- }
519
- return out;
520
- }
521
- function scanHooks(scope, projectRoot, home, catalogHooks, warnings) {
522
- const settingsPath = settingsPathForScope(scope, projectRoot, home);
523
- let settingsRaw = null;
524
- let settingsErr = null;
525
- try {
526
- settingsRaw = fs.readFileSync(settingsPath, "utf8");
527
- }
528
- catch (err) {
529
- if (err && err.code === "ENOENT") {
530
- settingsErr = "absent";
531
- }
532
- else {
533
- settingsErr = "unreadable";
534
- }
535
- }
536
- let parsed = null;
537
- if (settingsRaw !== null) {
538
- try {
539
- parsed = JSON.parse(settingsRaw);
540
- }
541
- catch {
542
- settingsErr = "unreadable";
543
- parsed = null;
544
- }
545
- }
546
- if (settingsErr === "unreadable") {
547
- warnings.push({
548
- code: "settings-unreadable",
549
- message: `Settings file unreadable or corrupt JSON: ${settingsPath}`,
550
- });
551
- }
552
- const markers = indexSettingsMarkers(parsed);
553
- const out = [];
554
- for (const [name, def] of Object.entries(catalogHooks)) {
555
- out.push({
556
- name,
557
- description: def.description,
558
- status: markers.has(name) ? "installed" : "not-installed",
559
- observedScope: scope,
560
- });
561
- }
562
- return out;
563
- }
564
- // ---------------------------------------------------------------------------
565
461
  // Default external-I/O implementations (used when ScanOptions are not
566
462
  // injected — server.ts wires these up in production).
567
463
  // ---------------------------------------------------------------------------
package/dist/types.d.ts CHANGED
@@ -4,5 +4,5 @@
4
4
  * forcing leaf renderers (help.ts, guide.ts) to depend on the CLI
5
5
  * entrypoint just to pull one union.
6
6
  */
7
- export type CategoryName = "workflow" | "skills" | "recommended" | "plugins" | "hooks";
7
+ export type CategoryName = "workflow" | "skills" | "recommended" | "plugins";
8
8
  export declare const CATEGORY_NAMES: readonly CategoryName[];
package/dist/types.js CHANGED
@@ -9,5 +9,4 @@ export const CATEGORY_NAMES = [
9
9
  "skills",
10
10
  "recommended",
11
11
  "plugins",
12
- "hooks",
13
12
  ];
package/dist/utils.d.ts CHANGED
@@ -33,10 +33,16 @@ export interface InstallOpts {
33
33
  lang?: string;
34
34
  /** workflow only — install target directory (absolute or cwd-relative). */
35
35
  cwd?: string;
36
- /** skills / recommended / plugins / hooks — `"user"` means install globally. */
36
+ /** skills / recommended / plugins — `"user"` means install globally. */
37
37
  scope?: "project" | "user";
38
38
  /** plugins only — runtime to install plugins for. Defaults to Claude Code. */
39
39
  agent?: PluginAgent;
40
+ /**
41
+ * plugins only — plugin names to drop from the interactive selection
42
+ * list. The TUI's「其他插件」item sets this to `["auriga-workflow"]`
43
+ * so the plugin already covered by the preset isn't offered twice.
44
+ */
45
+ excludePlugins?: string[];
40
46
  /**
41
47
  * sub-item filter. `undefined` = full set of this category.
42
48
  * Names are validated against the catalog by the CLI layer; installers
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auriga-cli",
3
- "version": "1.25.0",
4
- "description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins, Hooks)",
3
+ "version": "1.27.0",
4
+ "description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins)",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -25,14 +25,14 @@
25
25
  "dev": "tsc --watch",
26
26
  "start": "node dist/cli.js",
27
27
  "pretest": "npm run build",
28
- "test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/hooks.test.js dist-test/tests/hooks-uninstall.test.js dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
29
- "test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/hooks.test.js dist-test/tests/hooks-uninstall.test.js dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
28
+ "test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
29
+ "test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
30
30
  "pretest:e2e": "npm run build",
31
31
  "test:e2e": "tsc -p tsconfig.test.json && node --test dist-test/tests/e2e-install.test.js",
32
32
  "pretest:web-ui-e2e": "npm run build && npm --prefix ui ci && npm --prefix ui run build",
33
33
  "test:web-ui-e2e": "tsc -p tsconfig.test.json && node --test dist-test/tests/web-ui-e2e.test.js",
34
34
  "test:session-instructions-loader": "node tests/session-instructions-loader.test.mjs",
35
- "test:git-guards": "node tests/commit-reminder.test.mjs && node tests/pr-create-guard.test.mjs && node tests/pr-ready-guard.test.mjs"
35
+ "test:git-guards": "node tests/commit-reminder.test.mjs && node tests/pr-create-guard.test.mjs && node tests/pr-ready-guard.test.mjs && node tests/pr-merge-guard.test.mjs"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=18"