@lousy-agents/cli 5.19.0 → 5.19.1
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.
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@biomejs/biome": "2.5.3",
|
|
21
|
-
"@lousy-agents/mcp": "5.
|
|
21
|
+
"@lousy-agents/mcp": "5.19.0",
|
|
22
22
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
23
23
|
"@testcontainers/postgresql": "12.0.4",
|
|
24
24
|
"@types/node": "24.13.3",
|
|
25
25
|
"@upstash/context7-mcp": "3.2.5",
|
|
26
26
|
"chance": "1.1.13",
|
|
27
27
|
"testcontainers": "12.0.4",
|
|
28
|
-
"tsx": "4.23.
|
|
28
|
+
"tsx": "4.23.5",
|
|
29
29
|
"typescript": "7.0.2",
|
|
30
30
|
"vitest": "4.1.10"
|
|
31
31
|
}
|
|
@@ -972,9 +972,9 @@
|
|
|
972
972
|
}
|
|
973
973
|
},
|
|
974
974
|
"node_modules/@lousy-agents/mcp": {
|
|
975
|
-
"version": "5.
|
|
976
|
-
"resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.
|
|
977
|
-
"integrity": "sha512-
|
|
975
|
+
"version": "5.19.0",
|
|
976
|
+
"resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.19.0.tgz",
|
|
977
|
+
"integrity": "sha512-3Hm79pTx2Gpis3vd1sJWfJ+CSIYUAqF6AuU0VCNwhGqfO9V34IskwDsmp/WPTy6F6Io1FUhm7be7G3x4/MWI/g==",
|
|
978
978
|
"dev": true,
|
|
979
979
|
"license": "BSD-2-Clause-Patent",
|
|
980
980
|
"dependencies": {
|
|
@@ -5851,9 +5851,9 @@
|
|
|
5851
5851
|
"optional": true
|
|
5852
5852
|
},
|
|
5853
5853
|
"node_modules/tsx": {
|
|
5854
|
-
"version": "4.23.
|
|
5855
|
-
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.
|
|
5856
|
-
"integrity": "sha512-
|
|
5854
|
+
"version": "4.23.5",
|
|
5855
|
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.5.tgz",
|
|
5856
|
+
"integrity": "sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==",
|
|
5857
5857
|
"dev": true,
|
|
5858
5858
|
"license": "MIT",
|
|
5859
5859
|
"dependencies": {
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@biomejs/biome": "2.5.3",
|
|
32
|
-
"@lousy-agents/mcp": "5.
|
|
32
|
+
"@lousy-agents/mcp": "5.19.0",
|
|
33
33
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
34
34
|
"@testcontainers/postgresql": "12.0.4",
|
|
35
35
|
"@types/node": "24.13.3",
|
|
36
36
|
"@upstash/context7-mcp": "3.2.5",
|
|
37
37
|
"chance": "1.1.13",
|
|
38
38
|
"testcontainers": "12.0.4",
|
|
39
|
-
"tsx": "4.23.
|
|
39
|
+
"tsx": "4.23.5",
|
|
40
40
|
"typescript": "7.0.2",
|
|
41
41
|
"vitest": "4.1.10"
|
|
42
42
|
}
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@biomejs/biome": "2.5.3",
|
|
23
|
-
"@lousy-agents/mcp": "5.
|
|
23
|
+
"@lousy-agents/mcp": "5.19.0",
|
|
24
24
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
25
25
|
"@types/node": "24.13.3",
|
|
26
26
|
"@upstash/context7-mcp": "3.2.5",
|
|
27
27
|
"chance": "1.1.13",
|
|
28
|
-
"tsx": "4.23.
|
|
28
|
+
"tsx": "4.23.5",
|
|
29
29
|
"typescript": "7.0.2",
|
|
30
30
|
"vitest": "4.1.10"
|
|
31
31
|
}
|
package/dist/index.js
CHANGED
|
@@ -3032,7 +3032,7 @@ exports.basename = (path, { windows } = {}) => {
|
|
|
3032
3032
|
|
|
3033
3033
|
|
|
3034
3034
|
},
|
|
3035
|
-
|
|
3035
|
+
6567(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
|
|
3036
3036
|
// NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
|
|
3037
3037
|
var constructs_namespaceObject = {};
|
|
3038
3038
|
__webpack_require__.r(constructs_namespaceObject);
|
|
@@ -11689,11 +11689,11 @@ const $ZodXor = /*@__PURE__*/ (/* unused pure expression or super */ null && (co
|
|
|
11689
11689
|
})));
|
|
11690
11690
|
const $ZodDiscriminatedUnion =
|
|
11691
11691
|
/*@__PURE__*/
|
|
11692
|
-
|
|
11692
|
+
$constructor("$ZodDiscriminatedUnion", (inst, def) => {
|
|
11693
11693
|
def.inclusive = false;
|
|
11694
11694
|
$ZodUnion.init(inst, def);
|
|
11695
11695
|
const _super = inst._zod.parse;
|
|
11696
|
-
|
|
11696
|
+
defineLazy(inst._zod, "propValues", () => {
|
|
11697
11697
|
const propValues = {};
|
|
11698
11698
|
for (const option of def.options) {
|
|
11699
11699
|
const pv = option._zod.propValues;
|
|
@@ -11709,7 +11709,7 @@ const $ZodDiscriminatedUnion =
|
|
|
11709
11709
|
}
|
|
11710
11710
|
return propValues;
|
|
11711
11711
|
});
|
|
11712
|
-
const disc =
|
|
11712
|
+
const disc = util_cached(() => {
|
|
11713
11713
|
const opts = def.options;
|
|
11714
11714
|
const map = new Map();
|
|
11715
11715
|
for (const o of opts) {
|
|
@@ -11727,7 +11727,7 @@ const $ZodDiscriminatedUnion =
|
|
|
11727
11727
|
});
|
|
11728
11728
|
inst._zod.parse = (payload, ctx) => {
|
|
11729
11729
|
const input = payload.value;
|
|
11730
|
-
if (!
|
|
11730
|
+
if (!util_isObject(input)) {
|
|
11731
11731
|
payload.issues.push({
|
|
11732
11732
|
code: "invalid_type",
|
|
11733
11733
|
expected: "object",
|
|
@@ -11760,7 +11760,7 @@ const $ZodDiscriminatedUnion =
|
|
|
11760
11760
|
});
|
|
11761
11761
|
return payload;
|
|
11762
11762
|
};
|
|
11763
|
-
})
|
|
11763
|
+
});
|
|
11764
11764
|
const $ZodIntersection = /*@__PURE__*/ $constructor("$ZodIntersection", (inst, def) => {
|
|
11765
11765
|
$ZodType.init(inst, def);
|
|
11766
11766
|
inst._zod.parse = (payload, ctx) => {
|
|
@@ -15937,17 +15937,17 @@ function xor(options, params) {
|
|
|
15937
15937
|
...util.normalizeParams(params),
|
|
15938
15938
|
});
|
|
15939
15939
|
}
|
|
15940
|
-
const ZodDiscriminatedUnion = /*@__PURE__*/
|
|
15940
|
+
const ZodDiscriminatedUnion = /*@__PURE__*/ $constructor("ZodDiscriminatedUnion", (inst, def) => {
|
|
15941
15941
|
ZodUnion.init(inst, def);
|
|
15942
|
-
|
|
15943
|
-
})
|
|
15942
|
+
$ZodDiscriminatedUnion.init(inst, def);
|
|
15943
|
+
});
|
|
15944
15944
|
function discriminatedUnion(discriminator, options, params) {
|
|
15945
15945
|
// const [options, params] = args;
|
|
15946
15946
|
return new ZodDiscriminatedUnion({
|
|
15947
15947
|
type: "union",
|
|
15948
15948
|
options,
|
|
15949
15949
|
discriminator,
|
|
15950
|
-
...
|
|
15950
|
+
...normalizeParams(params),
|
|
15951
15951
|
});
|
|
15952
15952
|
}
|
|
15953
15953
|
const ZodIntersection = /*@__PURE__*/ $constructor("ZodIntersection", (inst, def) => {
|
|
@@ -33047,11 +33047,28 @@ async function walkMarkdownFiles(targetDir, resolvedRootDir, dir, depth, deriveN
|
|
|
33047
33047
|
|
|
33048
33048
|
/** Maximum hook config file size: 1 MB */ const MAX_CONFIG_FILE_BYTES = 1_048_576;
|
|
33049
33049
|
/** Matches the Copilot hook key `"preToolUse":` to detect hook section presence */ const COPILOT_HOOK_PATTERN = /"preToolUse"\s*:/;
|
|
33050
|
-
/** Matches the Claude hook key `"
|
|
33050
|
+
/** Matches the Claude hook key `"hooks":` to detect hook section presence */ const CLAUDE_HOOK_PATTERN = /"hooks"\s*:/;
|
|
33051
33051
|
/** Catalog hook config paths converted to OS-native relative paths. */ const HOOK_CONFIG_PATHS = hookConfigPaths().map((entry)=>({
|
|
33052
33052
|
relativePath: (0,external_node_path_.join)(...entry.relativePath.split("/")),
|
|
33053
33053
|
platform: entry.platform
|
|
33054
33054
|
}));
|
|
33055
|
+
/**
|
|
33056
|
+
* Structural check for a `hooks` object on a parsed settings file.
|
|
33057
|
+
*
|
|
33058
|
+
* Uses `Object.hasOwn` so a `hooks` key inherited from the prototype chain
|
|
33059
|
+
* cannot make an unrelated file look like a hook config. Arrays are rejected
|
|
33060
|
+
* even though `typeof [] === "object"`, because a hooks section is keyed by
|
|
33061
|
+
* event name.
|
|
33062
|
+
*/ function hasHooksSection(parsed) {
|
|
33063
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33064
|
+
return false;
|
|
33065
|
+
}
|
|
33066
|
+
if (!Object.hasOwn(parsed, "hooks")) {
|
|
33067
|
+
return false;
|
|
33068
|
+
}
|
|
33069
|
+
const hooks = parsed.hooks;
|
|
33070
|
+
return hooks !== null && typeof hooks === "object" && !Array.isArray(hooks);
|
|
33071
|
+
}
|
|
33055
33072
|
/**
|
|
33056
33073
|
* File system implementation of the hook config lint gateway.
|
|
33057
33074
|
*/ class FileSystemHookConfigGateway {
|
|
@@ -33098,14 +33115,24 @@ async function walkMarkdownFiles(targetDir, resolvedRootDir, dir, depth, deriveN
|
|
|
33098
33115
|
return readFileNoFollow(filePath, MAX_CONFIG_FILE_BYTES);
|
|
33099
33116
|
}
|
|
33100
33117
|
/**
|
|
33101
|
-
*
|
|
33102
|
-
*
|
|
33103
|
-
*
|
|
33118
|
+
* Checks whether a file may contain a hooks section.
|
|
33119
|
+
*
|
|
33120
|
+
* Claude Code settings files carry much more than hooks, and the `hooks`
|
|
33121
|
+
* key is generic enough to appear inside unrelated string values, so a
|
|
33122
|
+
* parsed structural check is used when the JSON is well-formed. Malformed
|
|
33123
|
+
* JSON falls back to a substring search so the file is still discovered
|
|
33124
|
+
* and surfaced as a `hook/invalid-json` diagnostic by the use case.
|
|
33104
33125
|
*/ mayContainHookSection(content, platform) {
|
|
33105
33126
|
if (platform === "copilot") {
|
|
33106
33127
|
return COPILOT_HOOK_PATTERN.test(content);
|
|
33107
33128
|
}
|
|
33108
|
-
|
|
33129
|
+
let parsed;
|
|
33130
|
+
try {
|
|
33131
|
+
parsed = JSON.parse(content);
|
|
33132
|
+
} catch {
|
|
33133
|
+
return CLAUDE_HOOK_PATTERN.test(content);
|
|
33134
|
+
}
|
|
33135
|
+
return hasHooksSection(parsed);
|
|
33109
33136
|
}
|
|
33110
33137
|
}
|
|
33111
33138
|
/**
|
|
@@ -52849,6 +52876,7 @@ async function resolveConfigPathOrNull(targetDir, relativePath) {
|
|
|
52849
52876
|
"hook/invalid-json": "error",
|
|
52850
52877
|
"hook/invalid-config": "error",
|
|
52851
52878
|
"hook/missing-command": "error",
|
|
52879
|
+
"hook/unknown-event": "error",
|
|
52852
52880
|
"hook/missing-matcher": "warn",
|
|
52853
52881
|
"hook/missing-timeout": "warn"
|
|
52854
52882
|
},
|
|
@@ -53153,6 +53181,167 @@ async function resolveConfigPathOrNull(targetDir, relativePath) {
|
|
|
53153
53181
|
return lines[0]?.trim() === "---";
|
|
53154
53182
|
}
|
|
53155
53183
|
|
|
53184
|
+
;// CONCATENATED MODULE: ../core/src/entities/claude-hook-schema.ts
|
|
53185
|
+
// biome-ignore-all lint/style/useNamingConvention: Claude Code API uses PascalCase hook event names (PreToolUse) and snake_case handler types (mcp_tool)
|
|
53186
|
+
/**
|
|
53187
|
+
* Zod schemas for the Claude Code hooks configuration format.
|
|
53188
|
+
*
|
|
53189
|
+
* Lives in entities (Layer 1) so that use cases can import it without
|
|
53190
|
+
* violating the dependency rule, mirroring `copilot-hook-schema.ts`.
|
|
53191
|
+
*
|
|
53192
|
+
* Modelled on https://code.claude.com/docs/en/hooks. Claude Code settings
|
|
53193
|
+
* files carry far more than hooks, so the top level is permissive
|
|
53194
|
+
* (`.passthrough()`); the `hooks` section itself is strict so that a
|
|
53195
|
+
* misspelled event name — which silently never fires — is reported.
|
|
53196
|
+
*/
|
|
53197
|
+
const MAX_HOOKS_PER_ENTRY = 100;
|
|
53198
|
+
/**
|
|
53199
|
+
* Events that accept a `matcher`. The remainder fire unconditionally, so a
|
|
53200
|
+
* missing `matcher` is only worth reporting for the events listed here.
|
|
53201
|
+
*/ const MATCHER_SUPPORTING_EVENTS = new Set([
|
|
53202
|
+
"PreToolUse",
|
|
53203
|
+
"PostToolUse",
|
|
53204
|
+
"PostToolUseFailure",
|
|
53205
|
+
"PermissionRequest",
|
|
53206
|
+
"PermissionDenied",
|
|
53207
|
+
"SessionStart",
|
|
53208
|
+
"Setup",
|
|
53209
|
+
"SessionEnd",
|
|
53210
|
+
"Notification",
|
|
53211
|
+
"SubagentStart",
|
|
53212
|
+
"SubagentStop",
|
|
53213
|
+
"PreCompact",
|
|
53214
|
+
"PostCompact",
|
|
53215
|
+
"ConfigChange",
|
|
53216
|
+
"DirectoryAdded",
|
|
53217
|
+
"FileChanged",
|
|
53218
|
+
"StopFailure",
|
|
53219
|
+
"InstructionsLoaded",
|
|
53220
|
+
"UserPromptExpansion",
|
|
53221
|
+
"Elicitation",
|
|
53222
|
+
"ElicitationResult"
|
|
53223
|
+
]);
|
|
53224
|
+
/** Fields every handler type accepts regardless of `type`. */ const sharedHandlerFields = {
|
|
53225
|
+
if: schemas_string().optional(),
|
|
53226
|
+
timeout: schemas_number().positive().optional(),
|
|
53227
|
+
statusMessage: schemas_string().optional(),
|
|
53228
|
+
once: schemas_boolean().optional()
|
|
53229
|
+
};
|
|
53230
|
+
/** Regex that allows standard env var names and rejects __proto__ (the prototype-polluting key). */ const ENV_KEY_PATTERN = /^(?!__proto__$)[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
53231
|
+
const ClaudeCommandHandlerSchema = schemas_object({
|
|
53232
|
+
...sharedHandlerFields,
|
|
53233
|
+
type: schemas_literal("command"),
|
|
53234
|
+
command: schemas_string().min(1, "Hook command must not be empty"),
|
|
53235
|
+
args: schemas_array(schemas_string()).optional(),
|
|
53236
|
+
shell: schemas_enum([
|
|
53237
|
+
"bash",
|
|
53238
|
+
"powershell"
|
|
53239
|
+
]).optional(),
|
|
53240
|
+
async: schemas_boolean().optional(),
|
|
53241
|
+
asyncRewake: schemas_boolean().optional()
|
|
53242
|
+
}).strict();
|
|
53243
|
+
const ClaudeHttpHandlerSchema = schemas_object({
|
|
53244
|
+
...sharedHandlerFields,
|
|
53245
|
+
type: schemas_literal("http"),
|
|
53246
|
+
url: schemas_string().min(1, "Hook url must not be empty"),
|
|
53247
|
+
headers: schemas_record(schemas_string(), schemas_string()).optional(),
|
|
53248
|
+
allowedEnvVars: schemas_array(schemas_string().regex(ENV_KEY_PATTERN, "Hook allowedEnvVars entry must be a valid identifier (no prototype-polluting keys)")).optional()
|
|
53249
|
+
}).strict();
|
|
53250
|
+
const ClaudeMcpToolHandlerSchema = schemas_object({
|
|
53251
|
+
...sharedHandlerFields,
|
|
53252
|
+
type: schemas_literal("mcp_tool"),
|
|
53253
|
+
server: schemas_string().min(1, "Hook server must not be empty"),
|
|
53254
|
+
tool: schemas_string().min(1, "Hook tool must not be empty"),
|
|
53255
|
+
input: schemas_record(schemas_string(), unknown()).optional()
|
|
53256
|
+
}).strict();
|
|
53257
|
+
const ClaudePromptHandlerSchema = schemas_object({
|
|
53258
|
+
...sharedHandlerFields,
|
|
53259
|
+
type: schemas_literal("prompt"),
|
|
53260
|
+
prompt: schemas_string().min(1, "Hook prompt must not be empty"),
|
|
53261
|
+
model: schemas_string().optional()
|
|
53262
|
+
}).strict();
|
|
53263
|
+
const ClaudeAgentHandlerSchema = schemas_object({
|
|
53264
|
+
...sharedHandlerFields,
|
|
53265
|
+
type: schemas_literal("agent"),
|
|
53266
|
+
prompt: schemas_string().min(1, "Hook prompt must not be empty"),
|
|
53267
|
+
model: schemas_string().optional()
|
|
53268
|
+
}).strict();
|
|
53269
|
+
/**
|
|
53270
|
+
* A single hook handler. Discriminated on `type` so that each handler is
|
|
53271
|
+
* validated against its own required fields — an `http` handler is not
|
|
53272
|
+
* expected to carry a `command`, and vice versa.
|
|
53273
|
+
*/ const ClaudeHookHandlerSchema = discriminatedUnion("type", [
|
|
53274
|
+
ClaudeCommandHandlerSchema,
|
|
53275
|
+
ClaudeHttpHandlerSchema,
|
|
53276
|
+
ClaudeMcpToolHandlerSchema,
|
|
53277
|
+
ClaudePromptHandlerSchema,
|
|
53278
|
+
ClaudeAgentHandlerSchema
|
|
53279
|
+
]);
|
|
53280
|
+
/**
|
|
53281
|
+
* A single entry within a hook event array.
|
|
53282
|
+
*
|
|
53283
|
+
* `matcher` is optional for every event: Claude Code treats an omitted
|
|
53284
|
+
* matcher as "match all", and many events accept no matcher at all.
|
|
53285
|
+
*/ const ClaudeHookEntrySchema = schemas_object({
|
|
53286
|
+
matcher: schemas_string().optional(),
|
|
53287
|
+
hooks: schemas_array(ClaudeHookHandlerSchema).min(1, "Hook entry must include at least one handler").max(MAX_HOOKS_PER_ENTRY)
|
|
53288
|
+
}).strict();
|
|
53289
|
+
const hookEventArray = schemas_array(ClaudeHookEntrySchema);
|
|
53290
|
+
/**
|
|
53291
|
+
* Every hook event Claude Code dispatches, each optional so that configs may
|
|
53292
|
+
* use any combination.
|
|
53293
|
+
*
|
|
53294
|
+
* Spelled out rather than generated from a list so the schema keeps precise
|
|
53295
|
+
* inferred types without a type assertion.
|
|
53296
|
+
*/ const claudeHookEventShape = {
|
|
53297
|
+
SessionStart: hookEventArray.optional(),
|
|
53298
|
+
Setup: hookEventArray.optional(),
|
|
53299
|
+
UserPromptSubmit: hookEventArray.optional(),
|
|
53300
|
+
UserPromptExpansion: hookEventArray.optional(),
|
|
53301
|
+
PreToolUse: hookEventArray.optional(),
|
|
53302
|
+
PermissionRequest: hookEventArray.optional(),
|
|
53303
|
+
PermissionDenied: hookEventArray.optional(),
|
|
53304
|
+
PostToolUse: hookEventArray.optional(),
|
|
53305
|
+
PostToolUseFailure: hookEventArray.optional(),
|
|
53306
|
+
PostToolBatch: hookEventArray.optional(),
|
|
53307
|
+
Notification: hookEventArray.optional(),
|
|
53308
|
+
MessageDisplay: hookEventArray.optional(),
|
|
53309
|
+
SubagentStart: hookEventArray.optional(),
|
|
53310
|
+
SubagentStop: hookEventArray.optional(),
|
|
53311
|
+
TaskCreated: hookEventArray.optional(),
|
|
53312
|
+
TaskCompleted: hookEventArray.optional(),
|
|
53313
|
+
Stop: hookEventArray.optional(),
|
|
53314
|
+
StopFailure: hookEventArray.optional(),
|
|
53315
|
+
TeammateIdle: hookEventArray.optional(),
|
|
53316
|
+
InstructionsLoaded: hookEventArray.optional(),
|
|
53317
|
+
ConfigChange: hookEventArray.optional(),
|
|
53318
|
+
CwdChanged: hookEventArray.optional(),
|
|
53319
|
+
DirectoryAdded: hookEventArray.optional(),
|
|
53320
|
+
FileChanged: hookEventArray.optional(),
|
|
53321
|
+
WorktreeCreate: hookEventArray.optional(),
|
|
53322
|
+
WorktreeRemove: hookEventArray.optional(),
|
|
53323
|
+
PreCompact: hookEventArray.optional(),
|
|
53324
|
+
PostCompact: hookEventArray.optional(),
|
|
53325
|
+
Elicitation: hookEventArray.optional(),
|
|
53326
|
+
ElicitationResult: hookEventArray.optional(),
|
|
53327
|
+
SessionEnd: hookEventArray.optional()
|
|
53328
|
+
};
|
|
53329
|
+
/**
|
|
53330
|
+
* Every known hook event name.
|
|
53331
|
+
*
|
|
53332
|
+
* Derived from the schema shape so the two can never drift apart, and used to
|
|
53333
|
+
* report the valid set when an unknown event name is encountered.
|
|
53334
|
+
*/ const CLAUDE_HOOK_EVENTS = Object.keys(claudeHookEventShape);
|
|
53335
|
+
/**
|
|
53336
|
+
* Zod schema for the hooks section of a Claude Code settings file.
|
|
53337
|
+
*
|
|
53338
|
+
* Every event is optional — configs may use any combination — but unknown
|
|
53339
|
+
* event names are rejected so that typos surface instead of silently
|
|
53340
|
+
* never firing.
|
|
53341
|
+
*/ const ClaudeHooksConfigSchema = schemas_object({
|
|
53342
|
+
hooks: schemas_object(claudeHookEventShape).strict()
|
|
53343
|
+
}).passthrough();
|
|
53344
|
+
|
|
53156
53345
|
;// CONCATENATED MODULE: ../core/src/entities/copilot-hook-schema.ts
|
|
53157
53346
|
/**
|
|
53158
53347
|
* Zod schemas for the GitHub Copilot hooks configuration format.
|
|
@@ -53164,7 +53353,7 @@ async function resolveConfigPathOrNull(targetDir, relativePath) {
|
|
|
53164
53353
|
* depend on @lousy-agents/core.
|
|
53165
53354
|
*/
|
|
53166
53355
|
const MAX_HOOKS_PER_EVENT = 100;
|
|
53167
|
-
/** Regex that allows standard env var names and rejects __proto__ (the prototype-polluting key). */ const
|
|
53356
|
+
/** Regex that allows standard env var names and rejects __proto__ (the prototype-polluting key). */ const copilot_hook_schema_ENV_KEY_PATTERN = /^(?!__proto__$)[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
53168
53357
|
/**
|
|
53169
53358
|
* Zod schema for a single GitHub Copilot hook command entry.
|
|
53170
53359
|
*/ const CopilotHookCommandSchema = schemas_object({
|
|
@@ -53173,7 +53362,7 @@ const MAX_HOOKS_PER_EVENT = 100;
|
|
|
53173
53362
|
powershell: schemas_string().min(1, "Hook PowerShell command must not be empty").optional(),
|
|
53174
53363
|
cwd: schemas_string().optional(),
|
|
53175
53364
|
timeoutSec: schemas_number().positive().optional(),
|
|
53176
|
-
env: schemas_record(schemas_string().regex(
|
|
53365
|
+
env: schemas_record(schemas_string().regex(copilot_hook_schema_ENV_KEY_PATTERN, "Hook env key must be a valid identifier (no prototype-polluting keys)"), schemas_string()).optional()
|
|
53177
53366
|
}).strict().refine((data)=>Boolean(data.bash) || Boolean(data.powershell), {
|
|
53178
53367
|
message: "At least one of 'bash' or 'powershell' must be provided and non-empty"
|
|
53179
53368
|
});
|
|
@@ -53193,33 +53382,78 @@ const hookArray = schemas_array(CopilotHookCommandSchema).max(MAX_HOOKS_PER_EVEN
|
|
|
53193
53382
|
}).strict();
|
|
53194
53383
|
|
|
53195
53384
|
;// CONCATENATED MODULE: ../core/src/use-cases/lint-hook-config.ts
|
|
53196
|
-
// biome-ignore-all lint/style/useNamingConvention: Claude Code API uses PascalCase hook event names (PreToolUse)
|
|
53197
53385
|
/**
|
|
53198
|
-
* Use case for linting
|
|
53386
|
+
* Use case for linting hook configurations.
|
|
53199
53387
|
* Validates GitHub Copilot and Claude Code hook config files.
|
|
53200
53388
|
*/
|
|
53201
53389
|
|
|
53202
53390
|
|
|
53203
53391
|
const INVALID_JSON_MESSAGE_PREFIX = "Invalid JSON in hook configuration file";
|
|
53392
|
+
/** Joins a Zod issue path into the dotted `field` shown on a diagnostic. */ function toFieldPath(path) {
|
|
53393
|
+
return path.length > 0 ? path.join(".") : undefined;
|
|
53394
|
+
}
|
|
53204
53395
|
/**
|
|
53205
|
-
*
|
|
53206
|
-
|
|
53207
|
-
|
|
53208
|
-
|
|
53209
|
-
|
|
53210
|
-
|
|
53211
|
-
*
|
|
53212
|
-
*/
|
|
53213
|
-
|
|
53214
|
-
|
|
53215
|
-
|
|
53396
|
+
* Expands a single Zod issue into diagnostics.
|
|
53397
|
+
*
|
|
53398
|
+
* `unrecognized_keys` issues carry every offending key on one issue whose path
|
|
53399
|
+
* points at the *containing* object, so they are expanded into one diagnostic
|
|
53400
|
+
* per key. A stray key directly under `hooks` is a misspelled event name —
|
|
53401
|
+
* the most valuable defect this linter can catch, since Claude Code silently
|
|
53402
|
+
* never fires such a hook.
|
|
53403
|
+
*/ function toClaudeDiagnostics(issue) {
|
|
53404
|
+
if (issue.code === "unrecognized_keys") {
|
|
53405
|
+
const isEventName = issue.path.length === 1 && issue.path[0] === "hooks";
|
|
53406
|
+
return issue.keys.map((key)=>({
|
|
53407
|
+
line: 1,
|
|
53408
|
+
severity: "error",
|
|
53409
|
+
message: isEventName ? `Unknown hook event '${key}'. A misspelled event never fires. Valid events: ${CLAUDE_HOOK_EVENTS.join(", ")}` : `Unrecognized key: "${key}"`,
|
|
53410
|
+
field: [
|
|
53411
|
+
...issue.path,
|
|
53412
|
+
key
|
|
53413
|
+
].join("."),
|
|
53414
|
+
ruleId: isEventName ? "hook/unknown-event" : "hook/invalid-config"
|
|
53415
|
+
}));
|
|
53416
|
+
}
|
|
53417
|
+
const lastPathSegment = issue.path.at(-1);
|
|
53418
|
+
const isMissingCommand = lastPathSegment === "command" && (issue.code === "too_small" || issue.code === "invalid_type");
|
|
53419
|
+
return [
|
|
53420
|
+
{
|
|
53421
|
+
line: 1,
|
|
53422
|
+
severity: "error",
|
|
53423
|
+
message: issue.message,
|
|
53424
|
+
field: toFieldPath([
|
|
53425
|
+
...issue.path
|
|
53426
|
+
]),
|
|
53427
|
+
ruleId: isMissingCommand ? "hook/missing-command" : "hook/invalid-config"
|
|
53428
|
+
}
|
|
53429
|
+
];
|
|
53430
|
+
}
|
|
53216
53431
|
/**
|
|
53217
|
-
*
|
|
53218
|
-
|
|
53219
|
-
|
|
53220
|
-
|
|
53221
|
-
|
|
53222
|
-
|
|
53432
|
+
* Warns when an entry omits `matcher` for an event that supports one.
|
|
53433
|
+
*
|
|
53434
|
+
* Events that accept no matcher (`Stop`, `UserPromptSubmit`, …) are skipped:
|
|
53435
|
+
* an omitted matcher there is the only valid spelling, not an oversight.
|
|
53436
|
+
*/ function collectMissingMatcherWarnings(hooks) {
|
|
53437
|
+
const diagnostics = [];
|
|
53438
|
+
for (const event of CLAUDE_HOOK_EVENTS){
|
|
53439
|
+
if (!MATCHER_SUPPORTING_EVENTS.has(event)) {
|
|
53440
|
+
continue;
|
|
53441
|
+
}
|
|
53442
|
+
const entries = hooks[event] ?? [];
|
|
53443
|
+
entries.forEach((entry, index)=>{
|
|
53444
|
+
if (entry.matcher === undefined) {
|
|
53445
|
+
diagnostics.push({
|
|
53446
|
+
line: 1,
|
|
53447
|
+
severity: "warning",
|
|
53448
|
+
message: `Recommended field 'matcher' is missing from ${event} hook entry. Without a matcher, the hook runs for every occurrence.`,
|
|
53449
|
+
field: `hooks.${event}[${index}].matcher`,
|
|
53450
|
+
ruleId: "hook/missing-matcher"
|
|
53451
|
+
});
|
|
53452
|
+
}
|
|
53453
|
+
});
|
|
53454
|
+
}
|
|
53455
|
+
return diagnostics;
|
|
53456
|
+
}
|
|
53223
53457
|
/**
|
|
53224
53458
|
* Use case for linting hook configuration files across a repository.
|
|
53225
53459
|
*/ class LintHookConfigUseCase {
|
|
@@ -53321,34 +53555,11 @@ const INVALID_JSON_MESSAGE_PREFIX = "Invalid JSON in hook configuration file";
|
|
|
53321
53555
|
return diagnostics;
|
|
53322
53556
|
}
|
|
53323
53557
|
validateClaudeConfig(parsed) {
|
|
53324
|
-
const diagnostics = [];
|
|
53325
53558
|
const result = ClaudeHooksConfigSchema.safeParse(parsed);
|
|
53326
53559
|
if (!result.success) {
|
|
53327
|
-
|
|
53328
|
-
const lastPathSegment = issue.path.length > 0 ? issue.path[issue.path.length - 1] : undefined;
|
|
53329
|
-
const isMissingCommand = lastPathSegment === "command" && (issue.code === "too_small" || issue.code === "invalid_type");
|
|
53330
|
-
diagnostics.push({
|
|
53331
|
-
line: 1,
|
|
53332
|
-
severity: "error",
|
|
53333
|
-
message: issue.message,
|
|
53334
|
-
field: issue.path.length > 0 ? issue.path.join(".") : undefined,
|
|
53335
|
-
ruleId: isMissingCommand ? "hook/missing-command" : "hook/invalid-config"
|
|
53336
|
-
});
|
|
53337
|
-
}
|
|
53338
|
-
return diagnostics;
|
|
53560
|
+
return result.error.issues.flatMap((issue)=>toClaudeDiagnostics(issue));
|
|
53339
53561
|
}
|
|
53340
|
-
|
|
53341
|
-
if (entry.matcher === undefined) {
|
|
53342
|
-
diagnostics.push({
|
|
53343
|
-
line: 1,
|
|
53344
|
-
severity: "warning",
|
|
53345
|
-
message: "Recommended field 'matcher' is missing from PreToolUse hook entry. Without a matcher, the hook runs for all tools.",
|
|
53346
|
-
field: `hooks.PreToolUse[${index}].matcher`,
|
|
53347
|
-
ruleId: "hook/missing-matcher"
|
|
53348
|
-
});
|
|
53349
|
-
}
|
|
53350
|
-
}
|
|
53351
|
-
return diagnostics;
|
|
53562
|
+
return collectMissingMatcherWarnings(result.data.hooks);
|
|
53352
53563
|
}
|
|
53353
53564
|
}
|
|
53354
53565
|
|
|
@@ -63711,4 +63922,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
63711
63922
|
// module factories are used so entry inlining is disabled
|
|
63712
63923
|
// startup
|
|
63713
63924
|
// Load entry module and return exports
|
|
63714
|
-
var __webpack_exports__ = __webpack_require__(
|
|
63925
|
+
var __webpack_exports__ = __webpack_require__(6567);
|
package/package.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@biomejs/biome": "2.5.3",
|
|
25
25
|
"@eslint/eslintrc": "3.3.6",
|
|
26
|
-
"@lousy-agents/mcp": "5.
|
|
26
|
+
"@lousy-agents/mcp": "5.19.0",
|
|
27
27
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
28
28
|
"@testing-library/jest-dom": "7.0.0",
|
|
29
29
|
"@testing-library/react": "16.3.2",
|