@devrik-tools/claude-gates 0.1.2 → 0.2.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 +7 -1
- package/cli/__tests__/config.test.mjs +113 -101
- package/cli/__tests__/install.test.mjs +102 -0
- package/cli/__tests__/materialize.test.mjs +95 -0
- package/cli/__tests__/registry-gates-consistency.test.mjs +52 -44
- package/cli/__tests__/task.test.mjs +124 -0
- package/cli/__tests__/version-consistency.test.mjs +44 -0
- package/cli/config.mjs +124 -113
- package/cli/index.mjs +100 -97
- package/cli/init.mjs +259 -256
- package/cli/install.mjs +150 -80
- package/cli/materialize.mjs +102 -51
- package/cli/registry.mjs +136 -127
- package/cli/task.mjs +140 -0
- package/package.json +2 -1
- package/plugins/gates/.claude-plugin/plugin.json +8 -2
- package/plugins/gates/hooks/__tests__/ask-adoption.test.mjs +83 -0
- package/plugins/gates/hooks/__tests__/doctor.test.mjs +85 -0
- package/plugins/gates/hooks/__tests__/wiring-check.test.mjs +65 -0
- package/plugins/gates/hooks/ask-adoption.mjs +147 -0
- package/plugins/gates/hooks/doctor.mjs +207 -0
- package/plugins/gates/hooks/gates/audit-before-build/audit-before-build.edge.test.mjs +83 -0
- package/plugins/gates/hooks/gates/audit-before-build/index.mjs +88 -88
- package/plugins/gates/hooks/gates/audit-before-build/test.mjs +117 -92
- package/plugins/gates/hooks/gates/autonomous-mode/autonomous.edge.test.mjs +104 -0
- package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +48 -45
- package/plugins/gates/hooks/gates/bash-commands/bash-commands.edge.test.mjs +165 -0
- package/plugins/gates/hooks/gates/bash-commands/index.mjs +285 -268
- package/plugins/gates/hooks/gates/brief-before-delegate/brief-before-delegate.edge.test.mjs +151 -0
- package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +265 -177
- package/plugins/gates/hooks/gates/circuit-breaker/circuit-breaker.edge.test.mjs +207 -0
- package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +501 -456
- package/plugins/gates/hooks/gates/circuit-breaker/test.mjs +161 -143
- package/plugins/gates/hooks/gates/dependency-skills/dependency-skills.edge.test.mjs +69 -0
- package/plugins/gates/hooks/gates/dependency-skills/index.mjs +126 -118
- package/plugins/gates/hooks/gates/diagnosis-before-patch/diagnosis-before-patch.edge.test.mjs +68 -0
- package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +43 -49
- package/plugins/gates/hooks/gates/feature-catalog/edge.edge.test.mjs +83 -0
- package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -100
- package/plugins/gates/hooks/gates/force-parallel/index.mjs +119 -0
- package/plugins/gates/hooks/gates/force-parallel/test.mjs +88 -0
- package/plugins/gates/hooks/gates/forge-flow/forge-flow.edge.test.mjs +155 -0
- package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -112
- package/plugins/gates/hooks/gates/implementation-pipeline/edge.edge.test.mjs +66 -0
- package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -186
- package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -238
- package/plugins/gates/hooks/gates/intent-flow/intent-flow.edge.test.mjs +96 -0
- package/plugins/gates/hooks/gates/intent-flow/test.mjs +165 -136
- package/plugins/gates/hooks/gates/lint-commit/index.mjs +149 -0
- package/plugins/gates/hooks/gates/lint-commit/test.mjs +85 -0
- package/plugins/gates/hooks/gates/mandatory-flow/edge.edge.test.mjs +100 -0
- package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -166
- package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +79 -85
- package/plugins/gates/hooks/gates/neutral-spanish/neutral-spanish.edge.test.mjs +80 -0
- package/plugins/gates/hooks/gates/never-assume/index.mjs +58 -55
- package/plugins/gates/hooks/gates/never-assume/never-assume.edge.test.mjs +71 -0
- package/plugins/gates/hooks/gates/never-assume/test.mjs +95 -78
- package/plugins/gates/hooks/gates/no-blocking/index.mjs +148 -142
- package/plugins/gates/hooks/gates/no-blocking/no-blocking.edge.test.mjs +109 -0
- package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +123 -120
- package/plugins/gates/hooks/gates/no-memory-dependency/no-memory-dependency.edge.test.mjs +83 -0
- package/plugins/gates/hooks/gates/no-reconfirm/no-reconfirm.edge.test.mjs +91 -0
- package/plugins/gates/hooks/gates/protected-paths/index.mjs +144 -147
- package/plugins/gates/hooks/gates/protected-paths/protected-paths.edge.test.mjs +126 -0
- package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +89 -64
- package/plugins/gates/hooks/gates/recurrence-lock/recurrence-lock.edge.test.mjs +102 -0
- package/plugins/gates/hooks/gates/reuse-before-build/edge.edge.test.mjs +94 -0
- package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +159 -150
- package/plugins/gates/hooks/gates/risk-level/index.mjs +263 -203
- package/plugins/gates/hooks/gates/risk-level/risk-level.edge.test.mjs +116 -0
- package/plugins/gates/hooks/gates/risk-level/test.mjs +153 -125
- package/plugins/gates/hooks/gates/root-cause-first/index.mjs +56 -50
- package/plugins/gates/hooks/gates/root-cause-first/root-cause-first.edge.test.mjs +58 -0
- package/plugins/gates/hooks/gates/root-whitelist/index.mjs +116 -111
- package/plugins/gates/hooks/gates/root-whitelist/root-whitelist.edge.test.mjs +97 -0
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +184 -99
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/rule-skill-autodiscovery.edge.test.mjs +126 -0
- package/plugins/gates/hooks/gates/sdd-specs/edge.edge.test.mjs +130 -0
- package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -251
- package/plugins/gates/hooks/gates/stop-pending/index.mjs +159 -0
- package/plugins/gates/hooks/gates/stop-pending/test.mjs +114 -0
- package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +101 -101
- package/plugins/gates/hooks/gates/test-after-implementation/test-after-implementation.edge.test.mjs +72 -0
- package/plugins/gates/hooks/gates/test-matrix/edge.edge.test.mjs +63 -0
- package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -181
- package/plugins/gates/hooks/gates/test-matrix/test.mjs +114 -87
- package/plugins/gates/hooks/gates/tool-map/edge.edge.test.mjs +62 -0
- package/plugins/gates/hooks/gates/tool-map/index.mjs +143 -140
- package/plugins/gates/hooks/hooks.json +346 -286
- package/plugins/gates/hooks/lib/__tests__/config.test.mjs +173 -154
- package/plugins/gates/hooks/lib/__tests__/hook-io.test.mjs +210 -154
- package/plugins/gates/hooks/lib/__tests__/signals.test.mjs +114 -0
- package/plugins/gates/hooks/lib/config.mjs +172 -165
- package/plugins/gates/hooks/lib/hook-io-config.edge.test.mjs +189 -0
- package/plugins/gates/hooks/lib/hook-io.mjs +318 -208
- package/plugins/gates/hooks/lib/signals.mjs +127 -0
- package/plugins/gates/hooks/wiring-check.mjs +227 -0
- package/plugins/tasks/.claude-plugin/plugin.json +14 -0
- package/plugins/tasks/hooks/__tests__/register-requests.test.mjs +100 -0
- package/plugins/tasks/hooks/__tests__/session-tasks.test.mjs +95 -0
- package/plugins/tasks/hooks/hooks.json +26 -0
- package/plugins/tasks/hooks/lib/__tests__/task-store.test.mjs +180 -132
- package/plugins/tasks/hooks/lib/task-store.mjs +197 -159
- package/plugins/tasks/hooks/register-requests.mjs +141 -108
- package/plugins/tasks/hooks/session-tasks.mjs +108 -0
- package/registry.json +793 -686
|
@@ -1,154 +1,173 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { test } from 'node:test';
|
|
6
|
-
import { isGateEnabled, gateParameters } from '../config.mjs';
|
|
7
|
-
|
|
8
|
-
const GATE_KEY = 'blockDestructiveShellCommands';
|
|
9
|
-
|
|
10
|
-
function temporaryDirectory() {
|
|
11
|
-
return mkdtempSync(join(tmpdir(), 'claude-gates-config-'));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** A project directory with a .git marker so config.mjs finds a project root. */
|
|
15
|
-
function projectDirectory() {
|
|
16
|
-
const root = temporaryDirectory();
|
|
17
|
-
mkdirSync(join(root, '.git'));
|
|
18
|
-
return root;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function writeProjectConfig(root, config) {
|
|
22
|
-
mkdirSync(join(root, '.ai'), { recursive: true });
|
|
23
|
-
writeFileSync(join(root, '.ai', 'config.json'), JSON.stringify(config));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function writeGlobalConfig(home, config) {
|
|
27
|
-
const directory = join(home, '.claude', 'claude-gates');
|
|
28
|
-
mkdirSync(directory, { recursive: true });
|
|
29
|
-
writeFileSync(join(directory, 'config.json'), JSON.stringify(config));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
test('isGateEnabled falls back to the registry default when no config exists anywhere', () => {
|
|
33
|
-
const project = projectDirectory();
|
|
34
|
-
const home = temporaryDirectory();
|
|
35
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
36
|
-
assert.equal(isGateEnabled(GATE_KEY, false, project, { home }), false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('isGateEnabled reads a bool shorthand entry from the project config', () => {
|
|
40
|
-
const project = projectDirectory();
|
|
41
|
-
const home = temporaryDirectory();
|
|
42
|
-
writeProjectConfig(project, { gates: { [GATE_KEY]: false } });
|
|
43
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
44
|
-
|
|
45
|
-
const projectTrue = projectDirectory();
|
|
46
|
-
writeProjectConfig(projectTrue, { gates: { [GATE_KEY]: true } });
|
|
47
|
-
assert.equal(isGateEnabled(GATE_KEY, false, projectTrue, { home }), true);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('isGateEnabled reads an object entry with an explicit enabled flag', () => {
|
|
51
|
-
const project = projectDirectory();
|
|
52
|
-
const home = temporaryDirectory();
|
|
53
|
-
writeProjectConfig(project, {
|
|
54
|
-
gates: { [GATE_KEY]: { enabled: false, someParam: 'x' } },
|
|
55
|
-
});
|
|
56
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('isGateEnabled treats an object entry with no enabled field as silent (uses registry default)', () => {
|
|
60
|
-
const project = projectDirectory();
|
|
61
|
-
const home = temporaryDirectory();
|
|
62
|
-
writeProjectConfig(project, {
|
|
63
|
-
gates: { [GATE_KEY]: { someParam: 'x' } },
|
|
64
|
-
});
|
|
65
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
66
|
-
assert.equal(isGateEnabled(GATE_KEY, false, project, { home }), false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('isGateEnabled falls back to the global config when the project declares nothing', () => {
|
|
70
|
-
const project = projectDirectory();
|
|
71
|
-
const home = temporaryDirectory();
|
|
72
|
-
writeGlobalConfig(home, { gates: { [GATE_KEY]: false } });
|
|
73
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('isGateEnabled: an existing project config wins over the global config even if silent about this gate', () => {
|
|
77
|
-
const project = projectDirectory();
|
|
78
|
-
const home = temporaryDirectory();
|
|
79
|
-
writeGlobalConfig(home, { gates: { [GATE_KEY]: false } });
|
|
80
|
-
writeProjectConfig(project, { gates: {} });
|
|
81
|
-
// Project config exists (even without this gate declared): registry default wins,
|
|
82
|
-
// global's false must NOT leak through.
|
|
83
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('isGateEnabled: corrupt project config is treated as absent and falls through to global', () => {
|
|
87
|
-
const project = projectDirectory();
|
|
88
|
-
const home = temporaryDirectory();
|
|
89
|
-
mkdirSync(join(project, '.ai'), { recursive: true });
|
|
90
|
-
writeFileSync(join(project, '.ai', 'config.json'), '{not valid json');
|
|
91
|
-
writeGlobalConfig(home, { gates: { [GATE_KEY]: false } });
|
|
92
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('isGateEnabled: a malformed entry (string/number) is ignored, falls back to registry default', () => {
|
|
96
|
-
const project = projectDirectory();
|
|
97
|
-
const home = temporaryDirectory();
|
|
98
|
-
writeProjectConfig(project, { gates: { [GATE_KEY]: 'yes' } });
|
|
99
|
-
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
100
|
-
assert.equal(isGateEnabled(GATE_KEY, false, project, { home }), false);
|
|
101
|
-
|
|
102
|
-
const projectNumber = projectDirectory();
|
|
103
|
-
writeProjectConfig(projectNumber, { gates: { [GATE_KEY]: 1 } });
|
|
104
|
-
assert.equal(isGateEnabled(GATE_KEY, false, projectNumber, { home }), false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('gateParameters returns {} when nothing is declared for the gate', () => {
|
|
108
|
-
const project = projectDirectory();
|
|
109
|
-
const home = temporaryDirectory();
|
|
110
|
-
writeProjectConfig(project, { gates: {} });
|
|
111
|
-
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('gateParameters returns {} for a bool shorthand entry (no params, just enabled)', () => {
|
|
115
|
-
const project = projectDirectory();
|
|
116
|
-
const home = temporaryDirectory();
|
|
117
|
-
writeProjectConfig(project, { gates: { [GATE_KEY]: true } });
|
|
118
|
-
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('gateParameters strips enabled and returns the remaining params from the project config', () => {
|
|
122
|
-
const project = projectDirectory();
|
|
123
|
-
const home = temporaryDirectory();
|
|
124
|
-
writeProjectConfig(project, {
|
|
125
|
-
gates: {
|
|
126
|
-
[GATE_KEY]: { enabled: true, protectedPaths: ['a', 'b'] },
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {
|
|
130
|
-
protectedPaths: ['a', 'b'],
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('gateParameters: project params replace (do not merge with) global params', () => {
|
|
135
|
-
const project = projectDirectory();
|
|
136
|
-
const home = temporaryDirectory();
|
|
137
|
-
writeGlobalConfig(home, {
|
|
138
|
-
gates: {
|
|
139
|
-
[GATE_KEY]: { protectedPaths: ['global-a'], extra: 'g' },
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
writeProjectConfig(project, {
|
|
143
|
-
gates: { [GATE_KEY]: { protectedPaths: ['project-a'] } },
|
|
144
|
-
});
|
|
145
|
-
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {
|
|
146
|
-
protectedPaths: ['project-a'],
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('gateParameters returns {} when no config exists anywhere', () => {
|
|
151
|
-
const project = projectDirectory();
|
|
152
|
-
const home = temporaryDirectory();
|
|
153
|
-
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {});
|
|
154
|
-
});
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import { isGateEnabled, gateParameters } from '../config.mjs';
|
|
7
|
+
|
|
8
|
+
const GATE_KEY = 'blockDestructiveShellCommands';
|
|
9
|
+
|
|
10
|
+
function temporaryDirectory() {
|
|
11
|
+
return mkdtempSync(join(tmpdir(), 'claude-gates-config-'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A project directory with a .git marker so config.mjs finds a project root. */
|
|
15
|
+
function projectDirectory() {
|
|
16
|
+
const root = temporaryDirectory();
|
|
17
|
+
mkdirSync(join(root, '.git'));
|
|
18
|
+
return root;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeProjectConfig(root, config) {
|
|
22
|
+
mkdirSync(join(root, '.ai'), { recursive: true });
|
|
23
|
+
writeFileSync(join(root, '.ai', 'config.json'), JSON.stringify(config));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeGlobalConfig(home, config) {
|
|
27
|
+
const directory = join(home, '.claude', 'claude-gates');
|
|
28
|
+
mkdirSync(directory, { recursive: true });
|
|
29
|
+
writeFileSync(join(directory, 'config.json'), JSON.stringify(config));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('isGateEnabled falls back to the registry default when no config exists anywhere', () => {
|
|
33
|
+
const project = projectDirectory();
|
|
34
|
+
const home = temporaryDirectory();
|
|
35
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
36
|
+
assert.equal(isGateEnabled(GATE_KEY, false, project, { home }), false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('isGateEnabled reads a bool shorthand entry from the project config', () => {
|
|
40
|
+
const project = projectDirectory();
|
|
41
|
+
const home = temporaryDirectory();
|
|
42
|
+
writeProjectConfig(project, { gates: { [GATE_KEY]: false } });
|
|
43
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
44
|
+
|
|
45
|
+
const projectTrue = projectDirectory();
|
|
46
|
+
writeProjectConfig(projectTrue, { gates: { [GATE_KEY]: true } });
|
|
47
|
+
assert.equal(isGateEnabled(GATE_KEY, false, projectTrue, { home }), true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('isGateEnabled reads an object entry with an explicit enabled flag', () => {
|
|
51
|
+
const project = projectDirectory();
|
|
52
|
+
const home = temporaryDirectory();
|
|
53
|
+
writeProjectConfig(project, {
|
|
54
|
+
gates: { [GATE_KEY]: { enabled: false, someParam: 'x' } },
|
|
55
|
+
});
|
|
56
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('isGateEnabled treats an object entry with no enabled field as silent (uses registry default)', () => {
|
|
60
|
+
const project = projectDirectory();
|
|
61
|
+
const home = temporaryDirectory();
|
|
62
|
+
writeProjectConfig(project, {
|
|
63
|
+
gates: { [GATE_KEY]: { someParam: 'x' } },
|
|
64
|
+
});
|
|
65
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
66
|
+
assert.equal(isGateEnabled(GATE_KEY, false, project, { home }), false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('isGateEnabled falls back to the global config when the project declares nothing', () => {
|
|
70
|
+
const project = projectDirectory();
|
|
71
|
+
const home = temporaryDirectory();
|
|
72
|
+
writeGlobalConfig(home, { gates: { [GATE_KEY]: false } });
|
|
73
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('isGateEnabled: an existing project config wins over the global config even if silent about this gate', () => {
|
|
77
|
+
const project = projectDirectory();
|
|
78
|
+
const home = temporaryDirectory();
|
|
79
|
+
writeGlobalConfig(home, { gates: { [GATE_KEY]: false } });
|
|
80
|
+
writeProjectConfig(project, { gates: {} });
|
|
81
|
+
// Project config exists (even without this gate declared): registry default wins,
|
|
82
|
+
// global's false must NOT leak through.
|
|
83
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('isGateEnabled: corrupt project config is treated as absent and falls through to global', () => {
|
|
87
|
+
const project = projectDirectory();
|
|
88
|
+
const home = temporaryDirectory();
|
|
89
|
+
mkdirSync(join(project, '.ai'), { recursive: true });
|
|
90
|
+
writeFileSync(join(project, '.ai', 'config.json'), '{not valid json');
|
|
91
|
+
writeGlobalConfig(home, { gates: { [GATE_KEY]: false } });
|
|
92
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('isGateEnabled: a malformed entry (string/number) is ignored, falls back to registry default', () => {
|
|
96
|
+
const project = projectDirectory();
|
|
97
|
+
const home = temporaryDirectory();
|
|
98
|
+
writeProjectConfig(project, { gates: { [GATE_KEY]: 'yes' } });
|
|
99
|
+
assert.equal(isGateEnabled(GATE_KEY, true, project, { home }), true);
|
|
100
|
+
assert.equal(isGateEnabled(GATE_KEY, false, project, { home }), false);
|
|
101
|
+
|
|
102
|
+
const projectNumber = projectDirectory();
|
|
103
|
+
writeProjectConfig(projectNumber, { gates: { [GATE_KEY]: 1 } });
|
|
104
|
+
assert.equal(isGateEnabled(GATE_KEY, false, projectNumber, { home }), false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('gateParameters returns {} when nothing is declared for the gate', () => {
|
|
108
|
+
const project = projectDirectory();
|
|
109
|
+
const home = temporaryDirectory();
|
|
110
|
+
writeProjectConfig(project, { gates: {} });
|
|
111
|
+
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('gateParameters returns {} for a bool shorthand entry (no params, just enabled)', () => {
|
|
115
|
+
const project = projectDirectory();
|
|
116
|
+
const home = temporaryDirectory();
|
|
117
|
+
writeProjectConfig(project, { gates: { [GATE_KEY]: true } });
|
|
118
|
+
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('gateParameters strips enabled and returns the remaining params from the project config', () => {
|
|
122
|
+
const project = projectDirectory();
|
|
123
|
+
const home = temporaryDirectory();
|
|
124
|
+
writeProjectConfig(project, {
|
|
125
|
+
gates: {
|
|
126
|
+
[GATE_KEY]: { enabled: true, protectedPaths: ['a', 'b'] },
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {
|
|
130
|
+
protectedPaths: ['a', 'b'],
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('gateParameters: project params replace (do not merge with) global params', () => {
|
|
135
|
+
const project = projectDirectory();
|
|
136
|
+
const home = temporaryDirectory();
|
|
137
|
+
writeGlobalConfig(home, {
|
|
138
|
+
gates: {
|
|
139
|
+
[GATE_KEY]: { protectedPaths: ['global-a'], extra: 'g' },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
writeProjectConfig(project, {
|
|
143
|
+
gates: { [GATE_KEY]: { protectedPaths: ['project-a'] } },
|
|
144
|
+
});
|
|
145
|
+
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {
|
|
146
|
+
protectedPaths: ['project-a'],
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('gateParameters returns {} when no config exists anywhere', () => {
|
|
151
|
+
const project = projectDirectory();
|
|
152
|
+
const home = temporaryDirectory();
|
|
153
|
+
assert.deepEqual(gateParameters(GATE_KEY, project, { home }), {});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('a project config saved WITH a UTF-8 BOM is still read (not silently dropped)', () => {
|
|
157
|
+
// A BOM is what a Windows editor or PowerShell 5.1 `Set-Content -Encoding utf8` prepends.
|
|
158
|
+
// Before the fix, JSON.parse threw on the leading BOM, the project config was treated as
|
|
159
|
+
// absent, and its gate disables were silently ignored (falling through to global/registry).
|
|
160
|
+
// Here the project disables the gate; the BOM must not resurrect it.
|
|
161
|
+
const project = projectDirectory();
|
|
162
|
+
const home = temporaryDirectory();
|
|
163
|
+
mkdirSync(join(project, '.ai'), { recursive: true });
|
|
164
|
+
writeFileSync(
|
|
165
|
+
join(project, '.ai', 'config.json'),
|
|
166
|
+
'' + JSON.stringify({ gates: { [GATE_KEY]: { enabled: false } } }),
|
|
167
|
+
);
|
|
168
|
+
assert.equal(
|
|
169
|
+
isGateEnabled(GATE_KEY, true, project, { home }),
|
|
170
|
+
false,
|
|
171
|
+
'the BOM-prefixed project disable must take effect',
|
|
172
|
+
);
|
|
173
|
+
});
|