opencode-command-hooks 0.1.8 → 0.4.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🪝 OpenCode Command Hooks 🪝
2
2
 
3
- Use simple configs to declaratively define shell command hooks on tool/subagent invocations. With a single line of configuration, you can inject a hook's output directly into context for your agent to read.
3
+ Use simple configs to declaratively define shell command hooks on tool/subagent invocations. With a single line of config, you can inject a hook's output directly into context for your agent to read.
4
4
 
5
5
  ## Markdown Frontmatter Hooks
6
6
 
@@ -12,7 +12,7 @@ description: Analyzes the codebase and implements code changes.
12
12
  mode: subagent
13
13
  hooks:
14
14
  after:
15
- - run: "npm run test"
15
+ - run: "npm test"
16
16
  inject: "Test Output:\n{stdout}\n{stderr}"
17
17
  ---
18
18
  ```
@@ -75,11 +75,12 @@ hooks:
75
75
 
76
76
  ### Hook Configuration Options
77
77
 
78
- | Option | Type | Description |
79
- | -------- | ---------------------- | --------------------------------- |
80
- | `run` | `string` \| `string[]` | Command(s) to execute |
81
- | `inject` | `string` | Message injected into the session |
82
- | `toast` | `object` | Toast notification configuration |
78
+ | Option | Type | Description |
79
+ | ---------------- | ---------------------- | ------------------------------------------------------------------------ |
80
+ | `run` | `string` \| `string[]` | Command(s) to execute |
81
+ | `inject` | `string` | Message injected into the session |
82
+ | `toast` | `object` | Toast notification configuration |
83
+ | `overrideGlobal` | `boolean` | When `true`, suppresses global hooks matching the same event/phase+tool. Must be a JSON boolean (`true`/`false`), not a string. |
83
84
 
84
85
  ### Toast Configuration
85
86
 
@@ -173,13 +174,21 @@ Add to your `opencode.json`:
173
174
 
174
175
  ## Configuration
175
176
 
176
- ### JSON Config
177
+ ### Config Locations
178
+
179
+ The plugin loads hooks from two locations:
177
180
 
178
- Create `.opencode/command-hooks.jsonc` in your project (the plugin searches upward from the current working directory):
181
+ 1. **User global**: `~/.config/opencode/command-hooks.jsonc` hooks that apply to all projects
182
+ 2. **Project**: `.opencode/command-hooks.jsonc` — project-specific hooks (searches upward from cwd)
183
+
184
+ Both are merged by default. See [Configuration Precedence](#configuration-precedence) for details.
185
+
186
+ ### JSON Config
179
187
 
180
188
  ```jsonc
181
189
  {
182
190
  "truncationLimit": 30000,
191
+ "ignoreGlobalConfig": false,
183
192
  "tool": [
184
193
  // Tool hooks
185
194
  ],
@@ -191,11 +200,12 @@ Create `.opencode/command-hooks.jsonc` in your project (the plugin searches upwa
191
200
 
192
201
  #### JSON Config Options
193
202
 
194
- | Option | Type | Description |
195
- | ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
196
- | `truncationLimit` | `number` | Maximum characters to capture from command output. Defaults to 30,000 (matching OpenCode's bash tool). Must be a positive integer. |
197
- | `tool` | `ToolHook[]` | Array of tool execution hooks |
198
- | `session` | `SessionHook[]` | Array of session lifecycle hooks |
203
+ | Option | Type | Description |
204
+ | ------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
205
+ | `truncationLimit` | `number` | Maximum characters to capture from command output. Defaults to 30,000 (matching OpenCode's bash tool). Must be a positive integer. Project config overrides global when both are set. |
206
+ | `ignoreGlobalConfig`| `boolean` | When `true`, skip loading `~/.config/opencode/command-hooks.jsonc`. Defaults to `false`. Must be a JSON boolean (`true`/`false`), not a string. |
207
+ | `tool` | `ToolHook[]` | Array of tool execution hooks |
208
+ | `session` | `SessionHook[]` | Array of session lifecycle hooks |
199
209
 
200
210
  ### Markdown Frontmatter
201
211
 
@@ -216,11 +226,53 @@ hooks:
216
226
 
217
227
  ### Configuration Precedence
218
228
 
219
- 1. Hooks are loaded from `.opencode/command-hooks.jsonc`
220
- 2. Markdown hooks are converted to normal hooks with auto-generated IDs
221
- 3. If a markdown hook and a global hook share the same `id`, the markdown hook wins
222
- 4. Duplicate IDs within the same source are errors
223
- 5. Global config is cached to avoid repeated file reads
229
+ Hooks are loaded from two locations and merged:
230
+
231
+ 1. **User global config**: `~/.config/opencode/command-hooks.jsonc`
232
+ 2. **Project config**: `.opencode/command-hooks.jsonc` (searches upward from cwd)
233
+
234
+ **Merge behavior:**
235
+
236
+ | Scenario | Result |
237
+ |----------|--------|
238
+ | Different hook IDs | Both run (concatenation) |
239
+ | Same hook ID | Project replaces global |
240
+ | `overrideGlobal: true` on hook | Suppresses all global hooks for same event/phase+tool |
241
+ | `ignoreGlobalConfig: true` in project | Skips global config entirely |
242
+ | Both set `truncationLimit` | Project value wins |
243
+
244
+ **Example: Override all global hooks for an event**
245
+
246
+ ```jsonc
247
+ {
248
+ "session": [
249
+ {
250
+ "id": "my-session-idle",
251
+ "when": { "event": "session.idle" },
252
+ "run": "echo only this runs",
253
+ "overrideGlobal": true
254
+ }
255
+ ]
256
+ }
257
+ ```
258
+
259
+ **Example: Ignore global config entirely**
260
+
261
+ ```jsonc
262
+ {
263
+ "ignoreGlobalConfig": true,
264
+ "tool": [
265
+ // Only these hooks will run
266
+ ]
267
+ }
268
+ ```
269
+
270
+ Additional precedence rules:
271
+ - Markdown hooks are converted to normal hooks with auto-generated IDs
272
+ - If a markdown hook and a config hook share the same `id`, the markdown hook wins
273
+ - Duplicate IDs within the same source are errors
274
+ - Tool override matching uses canonical keys, so `"bash"` and `["bash"]` are treated as equivalent
275
+ - Config files are schema-validated; invalid value types (for example `"false"` for a boolean field) make that source invalid
224
276
 
225
277
  ---
226
278
 
@@ -1,24 +1,36 @@
1
1
  /**
2
2
  * Global configuration parser for loading hooks from .opencode/command-hooks.jsonc
3
3
  *
4
- * Searches for .opencode/command-hooks.jsonc starting from the current working
5
- * directory and walking up the directory tree. Parses JSONC format as CommandHooksConfig.
4
+ * Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
5
+ * project config (.opencode/command-hooks.jsonc), merging them with project taking precedence.
6
+ *
7
+ * Supports:
8
+ * - `ignoreGlobalConfig: true` in project config to skip user global entirely
9
+ * - `overrideGlobal: true` on individual hooks to suppress matching global hooks
6
10
  */
7
11
  import type { CommandHooksConfig } from "../types/hooks.js";
8
12
  export type GlobalConfigResult = {
9
13
  config: CommandHooksConfig;
10
14
  error: string | null;
11
15
  };
16
+ export declare const clearGlobalConfigCacheForTests: () => void;
12
17
  /**
13
- * Load and parse global command hooks configuration
18
+ * Load and merge command hooks configuration from both sources
19
+ *
20
+ * Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
21
+ * project config (.opencode/command-hooks.jsonc), then merges them.
14
22
  *
15
- * Searches for .opencode/command-hooks.jsonc starting from the current working
16
- * directory and walking up. Parses the entire file as CommandHooksConfig.
23
+ * Merge behavior:
24
+ * - If project config has `ignoreGlobalConfig: true`, skip user global entirely
25
+ * - Otherwise, merge with project hooks taking precedence:
26
+ * - Same hook `id` → project version wins
27
+ * - Hook with `overrideGlobal: true` → suppresses matching global hooks
28
+ * - Different `id` without override → both run (concatenation)
17
29
  *
18
30
  * Error handling:
19
- * - If no config file found: returns empty config (not an error)
20
- * - If config file is malformed: logs warning, returns empty config
21
- * - If file is not a valid CommandHooksConfig: logs warning, returns empty config
31
+ * - If no config files found: returns empty config (not an error)
32
+ * - If user global has parse error: warns and uses project config only
33
+ * - If project has parse error: returns error
22
34
  * - Never throws errors - always returns a valid config
23
35
  *
24
36
  * @returns Promise resolving to GlobalConfigResult
@@ -1 +1 @@
1
- {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK5D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAuIF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,kBAAkB,CA2EnE,CAAA"}
1
+ {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAkB5D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AA0KF,eAAO,MAAM,8BAA8B,QAAO,IAGjD,CAAA;AAqGD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,kBAAkB,CAkEnE,CAAA"}
@@ -1,12 +1,31 @@
1
1
  /**
2
2
  * Global configuration parser for loading hooks from .opencode/command-hooks.jsonc
3
3
  *
4
- * Searches for .opencode/command-hooks.jsonc starting from the current working
5
- * directory and walking up the directory tree. Parses JSONC format as CommandHooksConfig.
4
+ * Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
5
+ * project config (.opencode/command-hooks.jsonc), merging them with project taking precedence.
6
+ *
7
+ * Supports:
8
+ * - `ignoreGlobalConfig: true` in project config to skip user global entirely
9
+ * - `overrideGlobal: true` on individual hooks to suppress matching global hooks
6
10
  */
7
- import { isValidCommandHooksConfig } from "../schemas.js";
11
+ import { ConfigSchema } from "../schemas.js";
12
+ import { mergeConfigs } from "./merge.js";
8
13
  import { join, dirname } from "path";
14
+ import { homedir } from "os";
15
+ import { stat } from "fs/promises";
9
16
  import { logger } from "../logging.js";
17
+ /**
18
+ * Get the user's global config directory path
19
+ * Uses ~/.config/opencode/ following XDG convention
20
+ */
21
+ const getUserConfigPath = () => {
22
+ const envHome = process.env.HOME || process.env.USERPROFILE;
23
+ const baseHome = envHome && envHome.length > 0 ? envHome : homedir();
24
+ return join(baseHome, ".config", "opencode", "command-hooks.jsonc");
25
+ };
26
+ const CONFIG_CACHE_TTL_MS = 250;
27
+ const projectConfigPathCache = new Map();
28
+ const configBlobCache = new Map();
10
29
  /**
11
30
  * Strip comments from JSONC content
12
31
  * Handles both line comments and block comments
@@ -90,27 +109,45 @@ const parseJson = (content) => {
90
109
  throw new Error(`Failed to parse JSON: ${message}`);
91
110
  }
92
111
  };
112
+ const getFileMtimeMs = async (path) => {
113
+ try {
114
+ const stats = await stat(path);
115
+ if (!stats.isFile()) {
116
+ return null;
117
+ }
118
+ return stats.mtimeMs;
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ };
93
124
  /**
94
- * Find command hooks config file by walking up directory tree
95
- * Looks for .opencode/command-hooks.jsonc
125
+ * Find project config file by walking up directory tree
126
+ * Looks for .opencode/command-hooks.jsonc in project directories
96
127
  */
97
- const findConfigFile = async (startDir) => {
128
+ const findProjectConfigFile = async (startDir) => {
129
+ const now = Date.now();
130
+ const cached = projectConfigPathCache.get(startDir);
131
+ if (cached && now - cached.cachedAt < CONFIG_CACHE_TTL_MS) {
132
+ if (cached.path === null) {
133
+ return null;
134
+ }
135
+ const cachedPathStillExists = (await getFileMtimeMs(cached.path)) !== null;
136
+ if (cachedPathStillExists) {
137
+ return cached.path;
138
+ }
139
+ }
98
140
  let currentDir = startDir;
99
141
  // Limit search depth to avoid infinite loops
100
142
  const maxDepth = 20;
101
143
  let depth = 0;
102
144
  while (depth < maxDepth) {
103
- // Try .opencode/command-hooks.jsonc
104
145
  const configPath = join(currentDir, ".opencode", "command-hooks.jsonc");
105
- try {
106
- const file = Bun.file(configPath);
107
- if (await file.exists()) {
108
- logger.debug(`Found config file: ${configPath}`);
109
- return configPath;
110
- }
111
- }
112
- catch {
113
- // Continue searching
146
+ const mtimeMs = await getFileMtimeMs(configPath);
147
+ if (mtimeMs !== null) {
148
+ logger.debug(`Found project config file: ${configPath}`);
149
+ projectConfigPathCache.set(startDir, { path: configPath, cachedAt: now });
150
+ return configPath;
114
151
  }
115
152
  // Move up one directory
116
153
  const parentDir = dirname(currentDir);
@@ -121,84 +158,165 @@ const findConfigFile = async (startDir) => {
121
158
  currentDir = parentDir;
122
159
  depth++;
123
160
  }
124
- logger.debug(`No config file found after searching ${depth} directories`);
161
+ logger.debug(`No project config file found after searching ${depth} directories`);
162
+ projectConfigPathCache.set(startDir, { path: null, cachedAt: now });
125
163
  return null;
126
164
  };
165
+ const emptyConfig = () => ({ tool: [], session: [] });
166
+ export const clearGlobalConfigCacheForTests = () => {
167
+ projectConfigPathCache.clear();
168
+ configBlobCache.clear();
169
+ };
127
170
  /**
128
- * Load and parse global command hooks configuration
171
+ * Load and parse config from a specific file path
129
172
  *
130
- * Searches for .opencode/command-hooks.jsonc starting from the current working
131
- * directory and walking up. Parses the entire file as CommandHooksConfig.
173
+ * @param configPath - Path to the config file
174
+ * @param source - Source identifier for logging ("project" or "user global")
175
+ * @returns GlobalConfigResult with parsed config or error
176
+ */
177
+ const loadConfigFromPath = async (configPath, source) => {
178
+ const now = Date.now();
179
+ const mtimeMs = await getFileMtimeMs(configPath);
180
+ const cached = configBlobCache.get(configPath);
181
+ if (cached) {
182
+ if (mtimeMs === null && cached.mtimeMs === null && now - cached.cachedAt < CONFIG_CACHE_TTL_MS) {
183
+ return { config: cached.config, error: cached.error };
184
+ }
185
+ if (mtimeMs !== null && cached.mtimeMs === mtimeMs) {
186
+ return { config: cached.config, error: cached.error };
187
+ }
188
+ }
189
+ if (mtimeMs === null) {
190
+ const missingResult = { config: emptyConfig(), error: null };
191
+ configBlobCache.set(configPath, { ...missingResult, mtimeMs: null, cachedAt: now });
192
+ return missingResult;
193
+ }
194
+ // Read file
195
+ let content;
196
+ try {
197
+ content = await Bun.file(configPath).text();
198
+ }
199
+ catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ logger.info(`Failed to read ${source} config file ${configPath}: ${message}`);
202
+ const readErrorResult = {
203
+ config: emptyConfig(),
204
+ error: `Failed to read ${source} config file ${configPath}: ${message}`,
205
+ };
206
+ configBlobCache.set(configPath, { ...readErrorResult, mtimeMs, cachedAt: now });
207
+ return readErrorResult;
208
+ }
209
+ // Parse JSONC
210
+ let parsed;
211
+ try {
212
+ const stripped = stripJsoncComments(content);
213
+ parsed = parseJson(stripped);
214
+ }
215
+ catch (error) {
216
+ const message = error instanceof Error ? error.message : String(error);
217
+ logger.info(`Failed to parse ${source} config file ${configPath}: ${message}`);
218
+ const parseErrorResult = {
219
+ config: emptyConfig(),
220
+ error: `Failed to parse ${source} config file ${configPath}: ${message}`,
221
+ };
222
+ configBlobCache.set(configPath, { ...parseErrorResult, mtimeMs, cachedAt: now });
223
+ return parseErrorResult;
224
+ }
225
+ // Validate and parse with the full schema
226
+ const parseResult = ConfigSchema.safeParse(parsed);
227
+ if (!parseResult.success) {
228
+ const issueSummary = parseResult.error.issues
229
+ .map(issue => {
230
+ const path = issue.path.length > 0 ? issue.path.join(".") : "root";
231
+ return `${path}: ${issue.message}`;
232
+ })
233
+ .join("; ");
234
+ logger.info(`${source} config file failed schema validation, using empty config: ${issueSummary}`);
235
+ const validationErrorResult = {
236
+ config: emptyConfig(),
237
+ error: `${source} config file failed schema validation`,
238
+ };
239
+ configBlobCache.set(configPath, { ...validationErrorResult, mtimeMs, cachedAt: now });
240
+ return validationErrorResult;
241
+ }
242
+ // Return with defaults for missing arrays
243
+ const result = {
244
+ truncationLimit: parseResult.data.truncationLimit,
245
+ ignoreGlobalConfig: parseResult.data.ignoreGlobalConfig,
246
+ tool: parseResult.data.tool ?? [],
247
+ session: parseResult.data.session ?? [],
248
+ };
249
+ logger.debug(`Loaded ${source} config: truncationLimit=${result.truncationLimit}, ${result.tool?.length ?? 0} tool hooks, ${result.session?.length ?? 0} session hooks`);
250
+ const successResult = { config: result, error: null };
251
+ configBlobCache.set(configPath, { ...successResult, mtimeMs, cachedAt: now });
252
+ return successResult;
253
+ };
254
+ /**
255
+ * Load and merge command hooks configuration from both sources
256
+ *
257
+ * Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
258
+ * project config (.opencode/command-hooks.jsonc), then merges them.
259
+ *
260
+ * Merge behavior:
261
+ * - If project config has `ignoreGlobalConfig: true`, skip user global entirely
262
+ * - Otherwise, merge with project hooks taking precedence:
263
+ * - Same hook `id` → project version wins
264
+ * - Hook with `overrideGlobal: true` → suppresses matching global hooks
265
+ * - Different `id` without override → both run (concatenation)
132
266
  *
133
267
  * Error handling:
134
- * - If no config file found: returns empty config (not an error)
135
- * - If config file is malformed: logs warning, returns empty config
136
- * - If file is not a valid CommandHooksConfig: logs warning, returns empty config
268
+ * - If no config files found: returns empty config (not an error)
269
+ * - If user global has parse error: warns and uses project config only
270
+ * - If project has parse error: returns error
137
271
  * - Never throws errors - always returns a valid config
138
272
  *
139
273
  * @returns Promise resolving to GlobalConfigResult
140
274
  */
141
275
  export const loadGlobalConfig = async () => {
142
- let configPath = null;
143
276
  try {
144
- // Find config file
145
277
  logger.debug(`loadGlobalConfig: starting search from: ${process.cwd()}`);
146
- configPath = await findConfigFile(process.cwd());
147
- if (!configPath) {
148
- logger.debug("No .opencode/command-hooks.jsonc file found, using empty config");
149
- return { config: { tool: [], session: [] }, error: null };
150
- }
151
- // Read file
152
- let content;
153
- try {
154
- const file = Bun.file(configPath);
155
- content = await file.text();
278
+ // Step 1: Load project config first (to check ignoreGlobalConfig flag)
279
+ const projectConfigPath = await findProjectConfigFile(process.cwd());
280
+ const projectResult = projectConfigPath
281
+ ? await loadConfigFromPath(projectConfigPath, "project")
282
+ : { config: emptyConfig(), error: null };
283
+ // If project config had an error, return it
284
+ if (projectResult.error) {
285
+ return projectResult;
156
286
  }
157
- catch (error) {
158
- const message = error instanceof Error ? error.message : String(error);
159
- logger.info(`Failed to read config file ${configPath}: ${message}`);
160
- return {
161
- config: { tool: [], session: [] },
162
- error: `Failed to read config file ${configPath}: ${message}`,
163
- };
287
+ // Step 2: If project says ignore global, return project only
288
+ if (projectResult.config.ignoreGlobalConfig) {
289
+ logger.debug("Project config has ignoreGlobalConfig: true, skipping user global");
290
+ return projectResult;
164
291
  }
165
- // Parse JSONC
166
- let parsed;
167
- try {
168
- const stripped = stripJsoncComments(content);
169
- parsed = parseJson(stripped);
292
+ // Step 3: Load user global config
293
+ const userGlobalPath = getUserConfigPath();
294
+ const userGlobalResult = await loadConfigFromPath(userGlobalPath, "user global");
295
+ // Step 4: If user global had parse error, log and use project only
296
+ if (userGlobalResult.error) {
297
+ logger.info(`Failed to load user global config (${userGlobalPath}): ${userGlobalResult.error}. Using project config only.`);
298
+ return projectResult;
170
299
  }
171
- catch (error) {
172
- const message = error instanceof Error ? error.message : String(error);
173
- logger.info(`Failed to parse config file ${configPath}: ${message}`);
174
- return {
175
- config: { tool: [], session: [] },
176
- error: `Failed to parse config file ${configPath}: ${message}`,
177
- };
300
+ // Step 5: If neither config has hooks, return empty
301
+ const hasUserGlobalHooks = (userGlobalResult.config.tool?.length ?? 0) > 0 ||
302
+ (userGlobalResult.config.session?.length ?? 0) > 0;
303
+ const hasProjectHooks = (projectResult.config.tool?.length ?? 0) > 0 ||
304
+ (projectResult.config.session?.length ?? 0) > 0;
305
+ if (!hasUserGlobalHooks && !hasProjectHooks) {
306
+ logger.debug("No hooks found in either config, using empty config");
307
+ return { config: emptyConfig(), error: null };
178
308
  }
179
- // Validate entire file as CommandHooksConfig
180
- if (!isValidCommandHooksConfig(parsed)) {
181
- logger.info("Config file is not a valid CommandHooksConfig (expected { tool?: [], session?: [] }), using empty config");
182
- return {
183
- config: { tool: [], session: [] },
184
- error: "Config file is not a valid CommandHooksConfig (expected { tool?: [], session?: [] })",
185
- };
186
- }
187
- // Return with defaults for missing arrays
188
- const result = {
189
- truncationLimit: parsed.truncationLimit,
190
- tool: parsed.tool ?? [],
191
- session: parsed.session ?? [],
192
- };
193
- logger.debug(`Loaded global config: truncationLimit=${result.truncationLimit}, ${result.tool?.length ?? 0} tool hooks, ${result.session?.length ?? 0} session hooks`);
194
- return { config: result, error: null };
309
+ // Step 6: Merge configs - user global as base, project as override
310
+ const { config: mergedConfig } = mergeConfigs(userGlobalResult.config, projectResult.config);
311
+ logger.debug(`Merged configs: ${mergedConfig.tool?.length ?? 0} tool hooks, ${mergedConfig.session?.length ?? 0} session hooks`);
312
+ return { config: mergedConfig, error: null };
195
313
  }
196
314
  catch (error) {
197
315
  // Catch-all for unexpected errors
198
316
  const message = error instanceof Error ? error.message : String(error);
199
317
  logger.info(`Unexpected error loading global config: ${message}`);
200
318
  return {
201
- config: { tool: [], session: [] },
319
+ config: emptyConfig(),
202
320
  error: `Unexpected error loading global config: ${message}`,
203
321
  };
204
322
  }
@@ -1 +1 @@
1
- {"version":3,"file":"global.js","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"global.js","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/config/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,kBAAkB,EAAY,MAAM,mBAAmB,CAAC;AAOlG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IAmBjE,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,OAStD,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,MAAM,KAClB,UAAU,GAAG,IA8Cf,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,2BAA2B,GACtC,YAAY,UAAU,EACtB,WAAW,MAAM,KAChB,kBAqCF,CAAA;AAqDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,GAC3B,UAAU,MAAM,KACjB,OAAO,CAAC,kBAAkB,CAmG5B,CAAA"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/config/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,kBAAkB,EAAY,MAAM,mBAAmB,CAAC;AAOlG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IA2BjE,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,OAStD,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,MAAM,KAClB,UAAU,GAAG,IA0Ef,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,2BAA2B,GACtC,YAAY,UAAU,EACtB,WAAW,MAAM,KAChB,kBAqCF,CAAA;AAqDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,GAC3B,UAAU,MAAM,KACjB,OAAO,CAAC,kBAAkB,CAmG5B,CAAA"}
@@ -5,7 +5,7 @@
5
5
  * Supports both agent markdown files (typically in .opencode/agents/) and slash-command
6
6
  * markdown files (typically in .opencode/commands/).
7
7
  */
8
- import { isValidCommandHooksConfig } from "../schemas.js";
8
+ import { isValidCommandHooksConfig, SimplifiedHookEntrySchema } from "../schemas.js";
9
9
  import { load as parseYaml } from "js-yaml";
10
10
  import { logger } from "../logging.js";
11
11
  /**
@@ -30,19 +30,26 @@ import { logger } from "../logging.js";
30
30
  * Returns the YAML between the delimiters
31
31
  */
32
32
  export const extractYamlFrontmatter = (content) => {
33
- // Check if content starts with ---
34
- if (!content.startsWith("---")) {
33
+ const normalized = content.replace(/\r\n/g, "\n");
34
+ const lines = normalized.split("\n");
35
+ const firstLine = (lines[0] ?? "").replace(/^\uFEFF/, "");
36
+ // Opening delimiter must be the first line
37
+ if (firstLine !== "---") {
35
38
  return null;
36
39
  }
37
- // Find the second --- delimiter
38
- // Start searching from position 3 (after the first ---)
39
- const secondDelimiterIndex = content.indexOf("---", 3);
40
- if (secondDelimiterIndex === -1) {
40
+ let closingDelimiterLine = -1;
41
+ for (let i = 1; i < lines.length; i += 1) {
42
+ if (/^\s*---\s*$/.test(lines[i])) {
43
+ closingDelimiterLine = i;
44
+ break;
45
+ }
46
+ }
47
+ if (closingDelimiterLine === -1) {
41
48
  // No closing delimiter found
42
49
  return null;
43
50
  }
44
51
  // Extract YAML between the delimiters
45
- const yamlContent = content.substring(3, secondDelimiterIndex).trim();
52
+ const yamlContent = lines.slice(1, closingDelimiterLine).join("\n").trim();
46
53
  return yamlContent;
47
54
  };
48
55
  /**
@@ -101,13 +108,31 @@ export const parseAgentHooks = (yamlContent) => {
101
108
  }
102
109
  const agentHooks = hooks;
103
110
  const result = {};
111
+ const parseHookEntries = (phase, entries) => {
112
+ const validEntries = [];
113
+ entries.forEach((entry, index) => {
114
+ const parsedEntry = SimplifiedHookEntrySchema.safeParse(entry);
115
+ if (!parsedEntry.success) {
116
+ const issues = parsedEntry.error.issues
117
+ .map((issue) => {
118
+ const issuePath = issue.path.length > 0 ? issue.path.join(".") : "root";
119
+ return `${issuePath}: ${issue.message}`;
120
+ })
121
+ .join("; ");
122
+ logger.info(`Skipping invalid hooks.${phase}[${index}] entry: ${issues}`);
123
+ return;
124
+ }
125
+ validEntries.push(parsedEntry.data);
126
+ });
127
+ return validEntries;
128
+ };
104
129
  // Parse before array
105
130
  if (agentHooks.before !== undefined) {
106
131
  if (!Array.isArray(agentHooks.before)) {
107
132
  logger.debug("hooks.before is not an array");
108
133
  return null;
109
134
  }
110
- result.before = agentHooks.before;
135
+ result.before = parseHookEntries("before", agentHooks.before);
111
136
  }
112
137
  // Parse after array
113
138
  if (agentHooks.after !== undefined) {
@@ -115,7 +140,7 @@ export const parseAgentHooks = (yamlContent) => {
115
140
  logger.debug("hooks.after is not an array");
116
141
  return null;
117
142
  }
118
- result.after = agentHooks.after;
143
+ result.after = parseHookEntries("after", agentHooks.after);
119
144
  }
120
145
  // Return null if both are empty/undefined
121
146
  if (!result.before?.length && !result.after?.length) {