@devrik-tools/claude-gates 0.3.1 → 0.6.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 +25 -4
- package/README.md +75 -43
- package/cli/config.mjs +126 -124
- package/cli/index.mjs +154 -104
- package/cli/init.mjs +303 -276
- package/cli/install.mjs +281 -175
- package/cli/materialize.mjs +103 -102
- package/cli/registry.mjs +139 -136
- package/cli/smoke-fixtures.json +441 -0
- package/cli/smoke.mjs +129 -0
- package/cli/task.mjs +140 -140
- package/package.json +1 -1
- package/plugins/gates/.claude-plugin/plugin.json +1 -1
- package/plugins/gates/hooks/ask-adoption.mjs +147 -147
- package/plugins/gates/hooks/doctor.mjs +207 -207
- package/plugins/gates/hooks/gates/atomic-commit/index.mjs +229 -0
- package/plugins/gates/hooks/gates/audit-before-build/index.mjs +110 -88
- package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +50 -50
- package/plugins/gates/hooks/gates/bash-commands/index.mjs +215 -215
- package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +269 -265
- package/plugins/gates/hooks/gates/capability-map/index.mjs +377 -0
- package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +527 -501
- package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +48 -43
- package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -83
- package/plugins/gates/hooks/gates/force-parallel/index.mjs +134 -119
- package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -134
- package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -187
- package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -260
- package/plugins/gates/hooks/gates/lint-commit/index.mjs +152 -149
- package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -180
- package/plugins/gates/hooks/gates/never-assume/index.mjs +59 -58
- package/plugins/gates/hooks/gates/no-blocking/index.mjs +163 -148
- package/plugins/gates/hooks/gates/no-coauthor/index.mjs +127 -0
- package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +183 -0
- package/plugins/gates/hooks/gates/protected-paths/index.mjs +149 -144
- package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +91 -89
- package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +263 -159
- package/plugins/gates/hooks/gates/risk-level/index.mjs +265 -263
- package/plugins/gates/hooks/gates/root-cause-first/index.mjs +57 -56
- package/plugins/gates/hooks/gates/root-whitelist/index.mjs +211 -131
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +181 -184
- package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -256
- package/plugins/gates/hooks/gates/staged-lint/index.mjs +187 -0
- package/plugins/gates/hooks/gates/stop-pending/index.mjs +169 -164
- package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -187
- package/plugins/gates/hooks/gates/tool-map/index.mjs +168 -143
- package/plugins/gates/hooks/hooks.json +51 -0
- package/plugins/gates/hooks/lib/config.mjs +179 -172
- package/plugins/gates/hooks/lib/hook-io.mjs +367 -357
- package/plugins/gates/hooks/lib/signals.mjs +172 -127
- package/plugins/gates/hooks/wiring-check.mjs +227 -227
- package/plugins/tasks/.claude-plugin/plugin.json +1 -1
- package/plugins/tasks/hooks/hooks.json +26 -26
- package/plugins/tasks/hooks/lib/task-store.mjs +217 -197
- package/plugins/tasks/hooks/register-requests.mjs +145 -145
- package/plugins/tasks/hooks/session-tasks.mjs +108 -108
- package/registry.json +171 -1
package/cli/index.mjs
CHANGED
|
@@ -1,104 +1,154 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Entry point (commander). Commands:
|
|
3
|
-
// init interactive (or flag-driven) selection of gates, per project or globally
|
|
4
|
-
// registry --check validates registry.json; --list prints the catalog
|
|
5
|
-
|
|
6
|
-
import { readFileSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { Command } from 'commander';
|
|
9
|
-
import {
|
|
10
|
-
EXIT_CODE,
|
|
11
|
-
LIST_SEPARATOR,
|
|
12
|
-
REGISTRY_PATH,
|
|
13
|
-
REPOSITORY_ROOT,
|
|
14
|
-
} from './constants.mjs';
|
|
15
|
-
import { loadRegistry, validateRegistry } from './registry.mjs';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
process.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Entry point (commander). Commands:
|
|
3
|
+
// init interactive (or flag-driven) selection of gates, per project or globally
|
|
4
|
+
// registry --check validates registry.json; --list prints the catalog
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import {
|
|
10
|
+
EXIT_CODE,
|
|
11
|
+
LIST_SEPARATOR,
|
|
12
|
+
REGISTRY_PATH,
|
|
13
|
+
REPOSITORY_ROOT,
|
|
14
|
+
} from './constants.mjs';
|
|
15
|
+
import { loadRegistry, validateRegistry } from './registry.mjs';
|
|
16
|
+
import { runSmoke, OUTCOMES } from './smoke.mjs';
|
|
17
|
+
import { registerTaskCommand } from './task.mjs';
|
|
18
|
+
|
|
19
|
+
const packageManifest = JSON.parse(
|
|
20
|
+
readFileSync(join(REPOSITORY_ROOT, 'package.json'), 'utf8'),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function fail(message) {
|
|
24
|
+
process.stderr.write(`${message}\n`);
|
|
25
|
+
process.exit(EXIT_CODE.FAILURE);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function splitList(value) {
|
|
29
|
+
return value
|
|
30
|
+
.split(LIST_SEPARATOR)
|
|
31
|
+
.map((item) => item.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function registryCheck() {
|
|
36
|
+
const problems = validateRegistry(
|
|
37
|
+
JSON.parse(readFileSync(REGISTRY_PATH, 'utf8')),
|
|
38
|
+
);
|
|
39
|
+
if (problems.length > 0) {
|
|
40
|
+
fail(`registry.json is invalid:\n- ${problems.join('\n- ')}`);
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write('registry.json OK\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function registryList() {
|
|
46
|
+
const registry = loadRegistry();
|
|
47
|
+
for (const family of registry.families) {
|
|
48
|
+
process.stdout.write(`${family.name} (${family.id})\n`);
|
|
49
|
+
for (const gate of family.gates) {
|
|
50
|
+
const marker = gate.default ? '*' : ' ';
|
|
51
|
+
process.stdout.write(` ${marker} ${gate.id} ${gate.configKey}\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
process.stdout.write('\n* = recommended default\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SMOKE_FIXTURES_PATH = join(REPOSITORY_ROOT, 'cli', 'smoke-fixtures.json');
|
|
58
|
+
// Width the gate id is padded to so the expected/got columns line up in the report.
|
|
59
|
+
const GATE_ID_COLUMN_WIDTH = 28;
|
|
60
|
+
const OUTCOME_MARK = {
|
|
61
|
+
[OUTCOMES.REACTED]: 'ok ',
|
|
62
|
+
[OUTCOMES.NO_REACTION]: 'FAIL',
|
|
63
|
+
[OUTCOMES.SKIPPED]: 'skip',
|
|
64
|
+
[OUTCOMES.ERROR]: 'ERR ',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Runs each gate against a known violation and reports whether it actually reacts. Unlike
|
|
68
|
+
// `registry --check` (structure) and the doctor hook (files exist), this confirms behavior:
|
|
69
|
+
// it is the check that catches a gate silently allowing its own known violation. Exits
|
|
70
|
+
// non-zero if any gate did not react (a real defect) — so CI or a post-install step can gate
|
|
71
|
+
// on it. Gates whose violation needs seeded state are reported `skip`, never counted as pass.
|
|
72
|
+
function smokeGates() {
|
|
73
|
+
const manifest = JSON.parse(
|
|
74
|
+
readFileSync(SMOKE_FIXTURES_PATH, 'utf8'),
|
|
75
|
+
).fixtures;
|
|
76
|
+
const { results, tally } = runSmoke(manifest);
|
|
77
|
+
|
|
78
|
+
for (const result of results) {
|
|
79
|
+
const detail = result.reason ? ` (${result.reason})` : '';
|
|
80
|
+
process.stdout.write(
|
|
81
|
+
`${OUTCOME_MARK[result.outcome]} ${result.id.padEnd(GATE_ID_COLUMN_WIDTH)} ` +
|
|
82
|
+
`expected=${result.expected} got=${result.got ?? '-'}${detail}\n`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write(
|
|
86
|
+
`\nreacted ${tally.reacted} · no-reaction ${tally['no-reaction']} · ` +
|
|
87
|
+
`skipped ${tally.skipped} · error ${tally.error}\n`,
|
|
88
|
+
);
|
|
89
|
+
if (tally['no-reaction'] > 0 || tally.error > 0) {
|
|
90
|
+
process.stdout.write(
|
|
91
|
+
'\nSome gates did not react to their own known violation. This is a defect: ' +
|
|
92
|
+
'the gate is wired but does not block/warn as declared.\n',
|
|
93
|
+
);
|
|
94
|
+
process.exit(EXIT_CODE.FAILURE);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const program = new Command()
|
|
99
|
+
.name(packageManifest.name)
|
|
100
|
+
.description(packageManifest.description)
|
|
101
|
+
.version(packageManifest.version);
|
|
102
|
+
|
|
103
|
+
program
|
|
104
|
+
.command('init')
|
|
105
|
+
.description('Choose which gates to adopt and write the config.')
|
|
106
|
+
.option('--project', 'apply to this project (<root>/.ai/config.json)')
|
|
107
|
+
.option('--global', 'apply globally (~/.claude/claude-gates/config.json)')
|
|
108
|
+
.option('--defaults', 'enable the recommended defaults')
|
|
109
|
+
.option('--all', 'enable every gate')
|
|
110
|
+
.option('--none', "record a 'no' so you are not asked again")
|
|
111
|
+
.option(
|
|
112
|
+
'--families <ids>',
|
|
113
|
+
'enable whole families, comma-separated',
|
|
114
|
+
splitList,
|
|
115
|
+
)
|
|
116
|
+
.option(
|
|
117
|
+
'--gates <ids>',
|
|
118
|
+
'enable individual gates, comma-separated',
|
|
119
|
+
splitList,
|
|
120
|
+
)
|
|
121
|
+
.option('-y, --yes', 'never prompt; use flags and defaults')
|
|
122
|
+
.option('--no-install', 'write the config but do not install the plugin')
|
|
123
|
+
.option(
|
|
124
|
+
'--no-remove-previous',
|
|
125
|
+
'keep any previously installed plugin version instead of removing it first',
|
|
126
|
+
)
|
|
127
|
+
.option('--dry-run', 'show the selection without writing')
|
|
128
|
+
.action(async (options) => {
|
|
129
|
+
const { runInit } = await import('./init.mjs');
|
|
130
|
+
await runInit(options);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command('registry')
|
|
135
|
+
.description('Inspect registry.json.')
|
|
136
|
+
.option('--check', 'validate the registry')
|
|
137
|
+
.option('--list', 'print families and gates')
|
|
138
|
+
.action((options) => {
|
|
139
|
+
if (options.check) return registryCheck();
|
|
140
|
+
if (options.list) return registryList();
|
|
141
|
+
return fail('registry needs --check or --list');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
program
|
|
145
|
+
.command('smoke')
|
|
146
|
+
.description(
|
|
147
|
+
'Feed each gate a known violation and confirm it actually reacts (deny/warn). ' +
|
|
148
|
+
'Catches a gate that is wired but silently allows. Exits non-zero on any no-reaction.',
|
|
149
|
+
)
|
|
150
|
+
.action(() => smokeGates());
|
|
151
|
+
|
|
152
|
+
registerTaskCommand(program);
|
|
153
|
+
|
|
154
|
+
program.parseAsync(process.argv).catch((error) => fail(error.message));
|