@devrik-tools/claude-gates 0.7.2 → 0.8.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/.claude-plugin/marketplace.json +2 -2
- package/README.es.md +69 -9
- package/README.md +68 -7
- package/cli/doctor.mjs +132 -0
- package/cli/evidence.mjs +78 -0
- package/cli/hooks-manifest.mjs +89 -0
- package/cli/index.mjs +124 -6
- package/cli/init.mjs +97 -3
- package/cli/log.mjs +70 -0
- package/cli/materialize.mjs +36 -2
- package/cli/registry.mjs +11 -1
- package/cli/selection.mjs +18 -0
- package/cli/smoke-fixtures.json +114 -22
- package/cli/task.mjs +36 -8
- package/cli/toggle.mjs +125 -0
- package/package.json +1 -1
- package/plugins/gates/.claude-plugin/plugin.json +1 -1
- package/plugins/gates/hooks/doctor.mjs +47 -1
- package/plugins/gates/hooks/gates/atomic-commit/index.mjs +92 -119
- package/plugins/gates/hooks/gates/audit-before-build/index.mjs +101 -66
- package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +8 -8
- package/plugins/gates/hooks/gates/autonomous-mode/stop.mjs +13 -64
- package/plugins/gates/hooks/gates/bash-commands/index.mjs +186 -163
- package/plugins/gates/hooks/gates/block-remote-publish/index.mjs +100 -101
- package/plugins/gates/hooks/gates/brief-approved/index.mjs +71 -140
- package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +56 -137
- package/plugins/gates/hooks/gates/capability-map/index.mjs +280 -506
- package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +98 -212
- package/plugins/gates/hooks/gates/dependency-skills/index.mjs +127 -46
- package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +81 -19
- package/plugins/gates/hooks/gates/engram-first/index.mjs +41 -0
- package/plugins/gates/hooks/gates/engram-first/session-start.mjs +64 -0
- package/plugins/gates/hooks/gates/engram-first/shared.mjs +90 -0
- package/plugins/gates/hooks/gates/engram-first/stop.mjs +23 -0
- package/plugins/gates/hooks/gates/engram-first/track.mjs +97 -0
- package/plugins/gates/hooks/gates/feature-catalog/index.mjs +156 -50
- package/plugins/gates/hooks/gates/force-parallel/index.mjs +51 -92
- package/plugins/gates/hooks/gates/forge-flow/index.mjs +51 -83
- package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +40 -80
- package/plugins/gates/hooks/gates/intent-flow/index.mjs +23 -149
- package/plugins/gates/hooks/gates/library-docs/index.mjs +262 -0
- package/plugins/gates/hooks/gates/library-docs/track.mjs +95 -0
- package/plugins/gates/hooks/gates/lint-commit/index.mjs +88 -97
- package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +48 -95
- package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +69 -31
- package/plugins/gates/hooks/gates/never-assume/index.mjs +23 -18
- package/plugins/gates/hooks/gates/no-blocking/index.mjs +127 -86
- package/plugins/gates/hooks/gates/no-coauthor/index.mjs +108 -88
- package/plugins/gates/hooks/gates/no-explanatory-comments/index.mjs +281 -0
- package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +170 -110
- package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +46 -77
- package/plugins/gates/hooks/gates/no-reconfirm/index.mjs +85 -47
- package/plugins/gates/hooks/gates/protected-paths/index.mjs +137 -90
- package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +95 -51
- package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +72 -216
- package/plugins/gates/hooks/gates/risk-level/index.mjs +51 -204
- package/plugins/gates/hooks/gates/root-cause-first/index.mjs +34 -17
- package/plugins/gates/hooks/gates/root-whitelist/index.mjs +160 -95
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +196 -97
- package/plugins/gates/hooks/gates/sdd-specs/index.mjs +90 -143
- package/plugins/gates/hooks/gates/staged-lint/index.mjs +98 -106
- package/plugins/gates/hooks/gates/stop-pending/index.mjs +47 -148
- package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +78 -68
- package/plugins/gates/hooks/gates/test-matrix/index.mjs +53 -88
- package/plugins/gates/hooks/gates/tool-map/index.mjs +80 -110
- package/plugins/gates/hooks/hooks.json +112 -42
- package/plugins/gates/hooks/lib/config.mjs +6 -3
- package/plugins/gates/hooks/lib/delegation.mjs +352 -0
- package/plugins/gates/hooks/lib/gate-log.mjs +141 -0
- package/plugins/gates/hooks/lib/git.mjs +262 -0
- package/plugins/gates/hooks/lib/hook-io.mjs +448 -83
- package/plugins/gates/hooks/lib/session-state.mjs +150 -0
- package/plugins/gates/hooks/lib/signals.mjs +18 -13
- package/plugins/gates/hooks/lib/testing.mjs +137 -0
- package/plugins/gates/hooks/lib/tools.mjs +144 -0
- package/plugins/tasks/.claude-plugin/plugin.json +1 -1
- package/plugins/tasks/hooks/lib/task-store.mjs +6 -6
- package/registry.json +204 -36
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
// tool-map —
|
|
2
|
-
//
|
|
3
|
-
// tool
|
|
4
|
-
// .
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// it could not record, so a silent write failure does not go unnoticed.
|
|
11
|
-
//
|
|
12
|
-
// ── What a project can configure (params) ───────────────────────────────────────────
|
|
13
|
-
// toolMapFile path to the per-project tool map, relative to the project root.
|
|
14
|
-
// Default .ai/tool-map.json.
|
|
15
|
-
|
|
16
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
-
import { dirname, extname, join } from 'node:path';
|
|
1
|
+
// tool-map — the write half of the discovery pair (reuse-before-build reads). When a tool
|
|
2
|
+
// is created WITH its audit declared, the tool and that audit line are recorded in the
|
|
3
|
+
// project's tool map so the discovery is never repeated. Never denies; warns once when it
|
|
4
|
+
// cannot record. What counts as a tool and as an audit line is the same lib definition
|
|
5
|
+
// reuse-before-build uses, so the map records exactly what the read half accepts.
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
9
|
+
import { projectRootOf, readJsonOrNull } from '../../lib/config.mjs';
|
|
18
10
|
import {
|
|
19
11
|
runGate,
|
|
20
12
|
warn,
|
|
@@ -22,6 +14,13 @@ import {
|
|
|
22
14
|
writtenContentOf,
|
|
23
15
|
writtenPathOf,
|
|
24
16
|
} from '../../lib/hook-io.mjs';
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_TOOL_EXTENSIONS,
|
|
19
|
+
DEFAULT_TOOL_FOLDERS,
|
|
20
|
+
DEFAULT_TOOL_NAME_PATTERNS,
|
|
21
|
+
hasAuditEvidence,
|
|
22
|
+
isToolPath,
|
|
23
|
+
} from '../../lib/tools.mjs';
|
|
25
24
|
|
|
26
25
|
const GATE_ID = 'tool-map';
|
|
27
26
|
const CONFIG_KEY = 'maintainToolMap';
|
|
@@ -30,107 +29,73 @@ const DEFAULT_TOOL_MAP_FILE = join('.ai', 'tool-map.json');
|
|
|
30
29
|
const JSON_INDENT = 2;
|
|
31
30
|
const MAX_AUDIT_LENGTH = 300;
|
|
32
31
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'.jsx',
|
|
44
|
-
'.vue',
|
|
45
|
-
'.py',
|
|
46
|
-
'.sh',
|
|
47
|
-
'.ps1',
|
|
48
|
-
'.rb',
|
|
49
|
-
'.go',
|
|
50
|
-
]);
|
|
51
|
-
const TOOL_FOLDERS = [
|
|
52
|
-
'scripts/',
|
|
53
|
-
'hooks/',
|
|
54
|
-
'tools/',
|
|
55
|
-
'gates/',
|
|
56
|
-
'lib/',
|
|
57
|
-
'libs/',
|
|
58
|
-
'utils/',
|
|
59
|
-
'util/',
|
|
60
|
-
'helpers/',
|
|
61
|
-
'helper/',
|
|
62
|
-
'composables/',
|
|
63
|
-
'components/',
|
|
64
|
-
'services/',
|
|
65
|
-
'shared/',
|
|
66
|
-
'common/',
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
// Phrases that mark a line as the author's audit statement. Matched against one line at a
|
|
70
|
-
// time (no wrapping `.*`, which backtracks): the matching line is captured whole.
|
|
71
|
-
const AUDIT_PHRASE_PATTERN =
|
|
72
|
-
/(already exists|no existing tool|there is no plugin|audited|nothing does this|justification:)/i;
|
|
32
|
+
// Same rule as reuse-before-build: a map path outside the project root is ignored.
|
|
33
|
+
function toolMapPathFor(root, toolMapFile) {
|
|
34
|
+
const resolved = resolve(root, String(toolMapFile || DEFAULT_TOOL_MAP_FILE));
|
|
35
|
+
const relativePath = relative(root, resolved);
|
|
36
|
+
const inside =
|
|
37
|
+
relativePath !== '' &&
|
|
38
|
+
!relativePath.startsWith('..') &&
|
|
39
|
+
!isAbsolute(relativePath);
|
|
40
|
+
return inside ? resolved : join(root, DEFAULT_TOOL_MAP_FILE);
|
|
41
|
+
}
|
|
73
42
|
|
|
74
|
-
/** The first line of `content` that states an audit, trimmed — or null when none does. */
|
|
75
43
|
function auditLineOf(content) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
44
|
+
return content
|
|
45
|
+
.split('\n')
|
|
46
|
+
.map((line) => line.trim())
|
|
47
|
+
.find((line) => hasAuditEvidence(line));
|
|
80
48
|
}
|
|
81
49
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const parent = dirname(current);
|
|
87
|
-
if (parent === current) return null;
|
|
88
|
-
current = parent;
|
|
89
|
-
}
|
|
50
|
+
function normalizePath(path) {
|
|
51
|
+
return String(path ?? '')
|
|
52
|
+
.replace(/\\/g, '/')
|
|
53
|
+
.replace(/^\.\//, '');
|
|
90
54
|
}
|
|
91
55
|
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
);
|
|
97
|
-
return (
|
|
98
|
-
inToolFolder && EXECUTABLE_EXTENSIONS.has(extname(filePath).toLowerCase())
|
|
99
|
-
);
|
|
56
|
+
function rootRelativePath(root, rawPath) {
|
|
57
|
+
const absolute = isAbsolute(rawPath) ? rawPath : join(root, rawPath);
|
|
58
|
+
const relativePath = normalizePath(relative(root, absolute));
|
|
59
|
+
return relativePath.startsWith('..') ? normalizePath(rawPath) : relativePath;
|
|
100
60
|
}
|
|
101
61
|
|
|
102
62
|
function readMap(path) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
106
|
-
return Array.isArray(parsed.tools) ? parsed : { tools: [] };
|
|
107
|
-
} catch {
|
|
108
|
-
return { tools: [] };
|
|
109
|
-
}
|
|
63
|
+
const parsed = readJsonOrNull(path);
|
|
64
|
+
return Array.isArray(parsed?.tools) ? parsed : { tools: [] };
|
|
110
65
|
}
|
|
111
66
|
|
|
112
|
-
|
|
113
|
-
|
|
67
|
+
// The file being recorded does not exist yet at PreToolUse time, so it is exempt from
|
|
68
|
+
// the prune; every other entry must still point at a real file.
|
|
69
|
+
function recordTool(root, mapPath, toolPath, auditLine) {
|
|
114
70
|
const map = readMap(mapPath);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
path: normalizedPath,
|
|
119
|
-
audit: auditLine.slice(0, MAX_AUDIT_LENGTH),
|
|
120
|
-
recordedAt: new Date().toISOString(),
|
|
71
|
+
const kept = map.tools.filter((tool) => {
|
|
72
|
+
const recorded = normalizePath(tool?.path);
|
|
73
|
+
return recorded !== toolPath && existsSync(join(root, recorded));
|
|
121
74
|
});
|
|
122
|
-
|
|
75
|
+
const unchanged =
|
|
76
|
+
kept.length === map.tools.length &&
|
|
77
|
+
map.tools.some((tool) => normalizePath(tool?.path) === toolPath);
|
|
78
|
+
if (unchanged) return;
|
|
79
|
+
|
|
80
|
+
const tools = [
|
|
81
|
+
...kept,
|
|
82
|
+
{
|
|
83
|
+
path: toolPath,
|
|
84
|
+
audit: auditLine.slice(0, MAX_AUDIT_LENGTH),
|
|
85
|
+
recordedAt: new Date().toISOString(),
|
|
86
|
+
},
|
|
87
|
+
];
|
|
123
88
|
try {
|
|
124
89
|
mkdirSync(dirname(mapPath), { recursive: true });
|
|
125
90
|
writeFileSync(
|
|
126
91
|
mapPath,
|
|
127
|
-
`${JSON.stringify(map, null, JSON_INDENT)}\n`,
|
|
92
|
+
`${JSON.stringify({ ...map, tools }, null, JSON_INDENT)}\n`,
|
|
128
93
|
'utf8',
|
|
129
94
|
);
|
|
130
95
|
} catch (error) {
|
|
131
96
|
warn(
|
|
132
97
|
CONFIG_KEY,
|
|
133
|
-
`Could not record this tool in ${
|
|
98
|
+
`Could not record this tool in ${mapPath}: ${error?.message ?? error}. ` +
|
|
134
99
|
'The build proceeds, but the discovery was not remembered.',
|
|
135
100
|
);
|
|
136
101
|
}
|
|
@@ -141,28 +106,33 @@ runGate(
|
|
|
141
106
|
id: GATE_ID,
|
|
142
107
|
configKey: CONFIG_KEY,
|
|
143
108
|
enabledByDefault: false,
|
|
144
|
-
defaultParams: {
|
|
109
|
+
defaultParams: {
|
|
110
|
+
toolMapFile: DEFAULT_TOOL_MAP_FILE,
|
|
111
|
+
toolFolders: DEFAULT_TOOL_FOLDERS,
|
|
112
|
+
toolExtensions: DEFAULT_TOOL_EXTENSIONS,
|
|
113
|
+
toolNamePatterns: DEFAULT_TOOL_NAME_PATTERNS,
|
|
114
|
+
},
|
|
145
115
|
},
|
|
146
|
-
({ toolName, toolInput, parameters }) => {
|
|
116
|
+
({ toolName, toolInput, parameters, cwd }) => {
|
|
147
117
|
if (!toolInGroups(toolName, ['write'])) return;
|
|
148
118
|
|
|
149
|
-
const
|
|
150
|
-
|
|
119
|
+
const rawPath = writtenPathOf(toolInput);
|
|
120
|
+
const isTool = isToolPath(rawPath, {
|
|
121
|
+
folders: parameters.toolFolders,
|
|
122
|
+
extensions: parameters.toolExtensions,
|
|
123
|
+
namePatterns: parameters.toolNamePatterns,
|
|
124
|
+
});
|
|
125
|
+
if (!isTool) return;
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
// this tool exists and what it was checked against.
|
|
154
|
-
const content = writtenContentOf(toolInput);
|
|
155
|
-
const auditLine = auditLineOf(content);
|
|
127
|
+
const auditLine = auditLineOf(writtenContentOf(toolInput));
|
|
156
128
|
if (!auditLine) return;
|
|
157
129
|
|
|
158
|
-
const root = projectRootOf(
|
|
159
|
-
if (!root) return;
|
|
160
|
-
const toolMapFile = parameters.toolMapFile ?? DEFAULT_TOOL_MAP_FILE;
|
|
130
|
+
const root = projectRootOf(cwd) ?? cwd;
|
|
161
131
|
recordTool(
|
|
162
|
-
|
|
163
|
-
|
|
132
|
+
root,
|
|
133
|
+
toolMapPathFor(root, parameters.toolMapFile),
|
|
134
|
+
rootRelativePath(root, rawPath),
|
|
164
135
|
auditLine,
|
|
165
|
-
toolMapFile,
|
|
166
136
|
);
|
|
167
137
|
},
|
|
168
138
|
);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"PreToolUse": [
|
|
4
4
|
{
|
|
5
|
-
"matcher": "Bash|run_command|Agent|Task|invoke_subagent|mcp__.*",
|
|
5
|
+
"matcher": "Bash|run_command|PowerShell|Agent|Task|invoke_subagent|mcp__.*",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
"matcher": "Bash|run_command|Agent|Task|invoke_subagent|mcp__.*",
|
|
15
|
+
"matcher": "Bash|run_command|PowerShell|Agent|Task|invoke_subagent|mcp__.*",
|
|
16
16
|
"hooks": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|mcp__.*",
|
|
25
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__.*",
|
|
26
26
|
"hooks": [
|
|
27
27
|
{
|
|
28
28
|
"type": "command",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|mcp__.*",
|
|
35
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__.*",
|
|
36
36
|
"hooks": [
|
|
37
37
|
{
|
|
38
38
|
"type": "command",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
"matcher": "Bash|run_command|Agent|Task|invoke_subagent|mcp__.*",
|
|
45
|
+
"matcher": "Bash|run_command|PowerShell|Agent|Task|invoke_subagent|mcp__.*",
|
|
46
46
|
"hooks": [
|
|
47
47
|
{
|
|
48
48
|
"type": "command",
|
|
@@ -102,7 +102,17 @@
|
|
|
102
102
|
]
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
|
-
"matcher": "
|
|
105
|
+
"matcher": "Agent|Task|invoke_subagent|mcp__.*",
|
|
106
|
+
"hooks": [
|
|
107
|
+
{
|
|
108
|
+
"type": "command",
|
|
109
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/force-parallel/index.mjs\"",
|
|
110
|
+
"timeout": 30
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__.*",
|
|
106
116
|
"hooks": [
|
|
107
117
|
{
|
|
108
118
|
"type": "command",
|
|
@@ -112,7 +122,7 @@
|
|
|
112
122
|
]
|
|
113
123
|
},
|
|
114
124
|
{
|
|
115
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
125
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
116
126
|
"hooks": [
|
|
117
127
|
{
|
|
118
128
|
"type": "command",
|
|
@@ -162,7 +172,7 @@
|
|
|
162
172
|
]
|
|
163
173
|
},
|
|
164
174
|
{
|
|
165
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
175
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__.*",
|
|
166
176
|
"hooks": [
|
|
167
177
|
{
|
|
168
178
|
"type": "command",
|
|
@@ -172,7 +182,7 @@
|
|
|
172
182
|
]
|
|
173
183
|
},
|
|
174
184
|
{
|
|
175
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
185
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
176
186
|
"hooks": [
|
|
177
187
|
{
|
|
178
188
|
"type": "command",
|
|
@@ -182,7 +192,7 @@
|
|
|
182
192
|
]
|
|
183
193
|
},
|
|
184
194
|
{
|
|
185
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
195
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
186
196
|
"hooks": [
|
|
187
197
|
{
|
|
188
198
|
"type": "command",
|
|
@@ -192,7 +202,7 @@
|
|
|
192
202
|
]
|
|
193
203
|
},
|
|
194
204
|
{
|
|
195
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
205
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
196
206
|
"hooks": [
|
|
197
207
|
{
|
|
198
208
|
"type": "command",
|
|
@@ -202,7 +212,7 @@
|
|
|
202
212
|
]
|
|
203
213
|
},
|
|
204
214
|
{
|
|
205
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|mcp__ide__executeCode|Agent|Task|invoke_subagent|mcp__.*",
|
|
215
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__ide__executeCode|Agent|Task|invoke_subagent|mcp__.*",
|
|
206
216
|
"hooks": [
|
|
207
217
|
{
|
|
208
218
|
"type": "command",
|
|
@@ -212,7 +222,7 @@
|
|
|
212
222
|
]
|
|
213
223
|
},
|
|
214
224
|
{
|
|
215
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|mcp__ide__executeCode|Agent|Task|invoke_subagent|mcp__.*",
|
|
225
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__ide__executeCode|Agent|Task|invoke_subagent|mcp__.*",
|
|
216
226
|
"hooks": [
|
|
217
227
|
{
|
|
218
228
|
"type": "command",
|
|
@@ -222,7 +232,7 @@
|
|
|
222
232
|
]
|
|
223
233
|
},
|
|
224
234
|
{
|
|
225
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
235
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
226
236
|
"hooks": [
|
|
227
237
|
{
|
|
228
238
|
"type": "command",
|
|
@@ -242,7 +252,7 @@
|
|
|
242
252
|
]
|
|
243
253
|
},
|
|
244
254
|
{
|
|
245
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
255
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
246
256
|
"hooks": [
|
|
247
257
|
{
|
|
248
258
|
"type": "command",
|
|
@@ -252,7 +262,7 @@
|
|
|
252
262
|
]
|
|
253
263
|
},
|
|
254
264
|
{
|
|
255
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
265
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
256
266
|
"hooks": [
|
|
257
267
|
{
|
|
258
268
|
"type": "command",
|
|
@@ -262,113 +272,144 @@
|
|
|
262
272
|
]
|
|
263
273
|
},
|
|
264
274
|
{
|
|
265
|
-
"matcher": "
|
|
275
|
+
"matcher": "Bash|run_command|PowerShell|mcp__.*",
|
|
266
276
|
"hooks": [
|
|
267
277
|
{
|
|
268
278
|
"type": "command",
|
|
269
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
279
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/lint-commit/index.mjs\"",
|
|
280
|
+
"timeout": 65
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"matcher": "Bash|run_command|PowerShell|mcp__.*",
|
|
286
|
+
"hooks": [
|
|
287
|
+
{
|
|
288
|
+
"type": "command",
|
|
289
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/staged-lint/index.mjs\"",
|
|
290
|
+
"timeout": 65
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"matcher": "Bash|run_command|PowerShell|mcp__.*",
|
|
296
|
+
"hooks": [
|
|
297
|
+
{
|
|
298
|
+
"type": "command",
|
|
299
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/atomic-commit/index.mjs\"",
|
|
270
300
|
"timeout": 30
|
|
271
301
|
}
|
|
272
302
|
]
|
|
273
303
|
},
|
|
274
304
|
{
|
|
275
|
-
"matcher": "
|
|
305
|
+
"matcher": "Bash|run_command|PowerShell|Agent|Task|invoke_subagent|mcp__.*",
|
|
276
306
|
"hooks": [
|
|
277
307
|
{
|
|
278
308
|
"type": "command",
|
|
279
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
309
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/no-coauthor/index.mjs\"",
|
|
280
310
|
"timeout": 30
|
|
281
311
|
}
|
|
282
312
|
]
|
|
283
313
|
},
|
|
284
314
|
{
|
|
285
|
-
"matcher": "Write|Edit|NotebookEdit|write_to_file|replace_file_content|
|
|
315
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
286
316
|
"hooks": [
|
|
287
317
|
{
|
|
288
318
|
"type": "command",
|
|
289
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
319
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/no-lint-suppression/index.mjs\"",
|
|
290
320
|
"timeout": 30
|
|
291
321
|
}
|
|
292
322
|
]
|
|
293
323
|
},
|
|
294
324
|
{
|
|
295
|
-
"matcher": "
|
|
325
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
296
326
|
"hooks": [
|
|
297
327
|
{
|
|
298
328
|
"type": "command",
|
|
299
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
329
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/no-explanatory-comments/index.mjs\"",
|
|
300
330
|
"timeout": 30
|
|
301
331
|
}
|
|
302
332
|
]
|
|
303
333
|
},
|
|
304
334
|
{
|
|
305
|
-
"matcher": "
|
|
335
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Agent|Task|invoke_subagent|mcp__.*",
|
|
306
336
|
"hooks": [
|
|
307
337
|
{
|
|
308
338
|
"type": "command",
|
|
309
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
310
|
-
"timeout":
|
|
339
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/reuse-before-build/index.mjs\"",
|
|
340
|
+
"timeout": 30
|
|
311
341
|
}
|
|
312
342
|
]
|
|
313
343
|
},
|
|
314
344
|
{
|
|
315
|
-
"matcher": "
|
|
345
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
316
346
|
"hooks": [
|
|
317
347
|
{
|
|
318
348
|
"type": "command",
|
|
319
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
320
|
-
"timeout":
|
|
349
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/tool-map/index.mjs\"",
|
|
350
|
+
"timeout": 30
|
|
321
351
|
}
|
|
322
352
|
]
|
|
323
353
|
},
|
|
324
354
|
{
|
|
325
|
-
"matcher": "
|
|
355
|
+
"matcher": "WebSearch|WebFetch|mcp__.*",
|
|
326
356
|
"hooks": [
|
|
327
357
|
{
|
|
328
358
|
"type": "command",
|
|
329
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
359
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/engram-first/index.mjs\"",
|
|
330
360
|
"timeout": 30
|
|
331
361
|
}
|
|
332
362
|
]
|
|
333
363
|
},
|
|
334
364
|
{
|
|
335
|
-
"matcher": "
|
|
365
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|mcp__.*",
|
|
336
366
|
"hooks": [
|
|
337
367
|
{
|
|
338
368
|
"type": "command",
|
|
339
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
369
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/library-docs/index.mjs\"",
|
|
340
370
|
"timeout": 30
|
|
341
371
|
}
|
|
342
372
|
]
|
|
343
373
|
},
|
|
344
374
|
{
|
|
345
|
-
"matcher": "Bash|run_command|mcp__.*",
|
|
375
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit|write_to_file|replace_file_content|Bash|run_command|PowerShell|mcp__.*",
|
|
346
376
|
"hooks": [
|
|
347
377
|
{
|
|
348
378
|
"type": "command",
|
|
349
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
379
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/forge-flow/index.mjs\"",
|
|
350
380
|
"timeout": 30
|
|
351
381
|
}
|
|
352
382
|
]
|
|
353
383
|
},
|
|
354
384
|
{
|
|
355
|
-
"matcher": "
|
|
385
|
+
"matcher": "AskUserQuestion|mcp__.*",
|
|
356
386
|
"hooks": [
|
|
357
387
|
{
|
|
358
388
|
"type": "command",
|
|
359
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
389
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/autonomous-mode/index.mjs\"",
|
|
360
390
|
"timeout": 30
|
|
361
391
|
}
|
|
362
392
|
]
|
|
363
393
|
}
|
|
364
394
|
],
|
|
365
|
-
"
|
|
395
|
+
"PostToolUse": [
|
|
366
396
|
{
|
|
397
|
+
"matcher": "WebSearch|WebFetch|mcp__.*",
|
|
367
398
|
"hooks": [
|
|
368
399
|
{
|
|
369
400
|
"type": "command",
|
|
370
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
371
|
-
"timeout":
|
|
401
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/engram-first/track.mjs\"",
|
|
402
|
+
"timeout": 30
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"matcher": "WebSearch|WebFetch|mcp__.*",
|
|
408
|
+
"hooks": [
|
|
409
|
+
{
|
|
410
|
+
"type": "command",
|
|
411
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/library-docs/track.mjs\"",
|
|
412
|
+
"timeout": 30
|
|
372
413
|
}
|
|
373
414
|
]
|
|
374
415
|
}
|
|
@@ -378,7 +419,7 @@
|
|
|
378
419
|
"hooks": [
|
|
379
420
|
{
|
|
380
421
|
"type": "command",
|
|
381
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/
|
|
422
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/engram-first/stop.mjs\"",
|
|
382
423
|
"timeout": 30
|
|
383
424
|
}
|
|
384
425
|
]
|
|
@@ -391,9 +432,27 @@
|
|
|
391
432
|
"timeout": 30
|
|
392
433
|
}
|
|
393
434
|
]
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"hooks": [
|
|
438
|
+
{
|
|
439
|
+
"type": "command",
|
|
440
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/stop-pending/index.mjs\"",
|
|
441
|
+
"timeout": 30
|
|
442
|
+
}
|
|
443
|
+
]
|
|
394
444
|
}
|
|
395
445
|
],
|
|
396
446
|
"SessionStart": [
|
|
447
|
+
{
|
|
448
|
+
"hooks": [
|
|
449
|
+
{
|
|
450
|
+
"type": "command",
|
|
451
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/engram-first/session-start.mjs\"",
|
|
452
|
+
"timeout": 30
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
},
|
|
397
456
|
{
|
|
398
457
|
"hooks": [
|
|
399
458
|
{
|
|
@@ -421,6 +480,17 @@
|
|
|
421
480
|
}
|
|
422
481
|
]
|
|
423
482
|
}
|
|
483
|
+
],
|
|
484
|
+
"UserPromptSubmit": [
|
|
485
|
+
{
|
|
486
|
+
"hooks": [
|
|
487
|
+
{
|
|
488
|
+
"type": "command",
|
|
489
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/capability-map/index.mjs\"",
|
|
490
|
+
"timeout": 10
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
}
|
|
424
494
|
]
|
|
425
495
|
}
|
|
426
496
|
}
|
|
@@ -33,6 +33,9 @@ const CONFIG_FILE = 'config.json';
|
|
|
33
33
|
// read. Anchoring on `.git` alone silently dropped the project layer in such repos.
|
|
34
34
|
const PROJECT_ROOT_MARKERS = ['.git', PROJECT_STATE_DIRECTORY];
|
|
35
35
|
|
|
36
|
+
export const PROJECT_ROOT_MARKERS_LIST = PROJECT_ROOT_MARKERS;
|
|
37
|
+
export { PROJECT_STATE_DIRECTORY, CONFIG_FILE };
|
|
38
|
+
|
|
36
39
|
const BOM_CODE_POINT = 0xfeff;
|
|
37
40
|
|
|
38
41
|
// Strip a leading UTF-8 BOM (U+FEFF) before parsing. `readFileSync(path,'utf8')` does NOT
|
|
@@ -43,11 +46,11 @@ const BOM_CODE_POINT = 0xfeff;
|
|
|
43
46
|
// symmetric case) means a project can't be trusted to have been read at all. Removing the
|
|
44
47
|
// BOM makes the common Windows round-trip parse correctly. Compared by char code (not a regex
|
|
45
48
|
// literal) so the BOM never appears as literal irregular whitespace in the source.
|
|
46
|
-
function stripBom(text) {
|
|
49
|
+
export function stripBom(text) {
|
|
47
50
|
return text.charCodeAt(0) === BOM_CODE_POINT ? text.slice(1) : text;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function readJsonOrNull(path) {
|
|
53
|
+
export function readJsonOrNull(path) {
|
|
51
54
|
if (!existsSync(path)) return null;
|
|
52
55
|
try {
|
|
53
56
|
return JSON.parse(stripBom(readFileSync(path, 'utf8')));
|
|
@@ -59,7 +62,7 @@ function readJsonOrNull(path) {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
/** Climbs to the nearest project root (a dir holding `.git` or `.ai/`); null when none. */
|
|
62
|
-
function projectRootOf(startDirectory) {
|
|
65
|
+
export function projectRootOf(startDirectory) {
|
|
63
66
|
let current = startDirectory;
|
|
64
67
|
while (true) {
|
|
65
68
|
if (
|