@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,55 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
import {
|
|
2
|
+
runGate,
|
|
3
|
+
warn,
|
|
4
|
+
toolInGroups,
|
|
5
|
+
writtenContentOf,
|
|
6
|
+
delegationPromptOf,
|
|
7
|
+
} from '../../lib/hook-io.mjs';
|
|
8
|
+
import { CONJECTURE_SOURCES } from '../../lib/signals.mjs';
|
|
9
|
+
|
|
10
|
+
const GATE_ID = 'never-assume';
|
|
11
|
+
const CONFIG_KEY = 'requireVerificationBeforeAssuming';
|
|
12
|
+
|
|
13
|
+
// Bilingual (ES+EN) conjecture phrasing, centralized in lib/signals.mjs so every gate
|
|
14
|
+
// that needs to recognize unverified-assumption prose shares the same coverage. See
|
|
15
|
+
// signals.mjs header for the ES/EN-only limitation.
|
|
16
|
+
const DEFAULT_CONJECTURE_PATTERNS = CONJECTURE_SOURCES;
|
|
17
|
+
|
|
18
|
+
function extractContent(toolName, toolInput) {
|
|
19
|
+
if (toolInGroups(toolName, ['delegation'])) return delegationPromptOf(toolInput);
|
|
20
|
+
return writtenContentOf(toolInput);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
runGate(
|
|
24
|
+
{
|
|
25
|
+
id: GATE_ID,
|
|
26
|
+
configKey: CONFIG_KEY,
|
|
27
|
+
enabledByDefault: false,
|
|
28
|
+
defaultParams: {
|
|
29
|
+
conjecturePatterns: DEFAULT_CONJECTURE_PATTERNS,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
({ toolName, toolInput, parameters }) => {
|
|
33
|
+
const isWrite = toolInGroups(toolName, ['write']);
|
|
34
|
+
const isDelegation = toolInGroups(toolName, ['delegation']);
|
|
35
|
+
if (!isWrite && !isDelegation) return;
|
|
36
|
+
|
|
37
|
+
const content = extractContent(toolName, toolInput);
|
|
38
|
+
if (!content) return;
|
|
39
|
+
|
|
40
|
+
const patterns = parameters.conjecturePatterns.map(
|
|
41
|
+
(source) => new RegExp(source, 'i'),
|
|
42
|
+
);
|
|
43
|
+
const hits = [];
|
|
44
|
+
for (const pattern of patterns) {
|
|
45
|
+
const match = content.match(pattern);
|
|
46
|
+
if (match) hits.push(match[0]);
|
|
47
|
+
}
|
|
48
|
+
if (hits.length === 0) return;
|
|
49
|
+
|
|
50
|
+
warn(
|
|
51
|
+
GATE_ID,
|
|
52
|
+
`Content contains conjecture phrasing without stated verification: ${hits.join(', ')}. Verify before asserting instead of assuming.`,
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// The source guard (guard-never-assume.mjs) is explicit: this never denies,
|
|
58
|
+
// only warns — conjecture language is a prompt to verify, not a blocker.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const GATE = join(dirname(fileURLToPath(import.meta.url)), 'index.mjs');
|
|
10
|
+
|
|
11
|
+
function runGate(payload, { config } = {}) {
|
|
12
|
+
const project = mkdtempSync(join(tmpdir(), 'never-assume-edge-'));
|
|
13
|
+
mkdirSync(join(project, '.git'));
|
|
14
|
+
if (config) {
|
|
15
|
+
mkdirSync(join(project, '.ai'));
|
|
16
|
+
writeFileSync(join(project, '.ai', 'config.json'), JSON.stringify(config));
|
|
17
|
+
}
|
|
18
|
+
const out = execFileSync(process.execPath, [GATE], {
|
|
19
|
+
input: JSON.stringify(payload),
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
cwd: project,
|
|
22
|
+
env: { ...process.env, HOME: project, USERPROFILE: project },
|
|
23
|
+
});
|
|
24
|
+
return out.trim() ? JSON.parse(out.trim()) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isWarn(result) {
|
|
28
|
+
return result?.hookSpecificOutput?.additionalContext !== undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ENABLE = { config: { gates: { requireVerificationBeforeAssuming: true } } };
|
|
32
|
+
|
|
33
|
+
// FIXED: extractContent() now reads through writtenContentOf() (lib/hook-io.mjs), which
|
|
34
|
+
// covers NotebookEdit's new_source field, closing the tool-coverage gap this test used to
|
|
35
|
+
// document as a bug.
|
|
36
|
+
test('FIXED: conjecture phrasing written via NotebookEdit is detected', () => {
|
|
37
|
+
const payload = {
|
|
38
|
+
tool_name: 'NotebookEdit',
|
|
39
|
+
tool_input: {
|
|
40
|
+
notebook_path: '/repo/nb.ipynb',
|
|
41
|
+
cell_id: 'x',
|
|
42
|
+
new_source: '# i assume the timezone is UTC',
|
|
43
|
+
cell_type: 'code',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
assert.ok(isWarn(runGate(payload, ENABLE)), 'gate now detects conjecture phrasing via NotebookEdit');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// EDGE CASE (BUG): 'invoke_subagent' is a declared delegation tool name (TOOL_GROUPS.delegation
|
|
50
|
+
// in lib/hook-io.mjs) and is matched in hooks.json, but extractContent() branches on
|
|
51
|
+
// TOOL_GROUPS.delegation.includes(toolName) which DOES include invoke_subagent — so this one
|
|
52
|
+
// should actually work. Verify it does (a real behavioral check, not a bug) to make sure the
|
|
53
|
+
// delegation branch itself is sound before concluding the write-side gap is the only issue.
|
|
54
|
+
test('OK: conjecture phrasing in an invoke_subagent prompt is detected (delegation branch is sound)', () => {
|
|
55
|
+
const payload = {
|
|
56
|
+
tool_name: 'invoke_subagent',
|
|
57
|
+
tool_input: { prompt: 'i assume the config is already loaded, proceed' },
|
|
58
|
+
};
|
|
59
|
+
assert.ok(isWarn(runGate(payload, ENABLE)));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// EDGE CASE: false-positive check — "should be" is a very broad conjecture pattern that can
|
|
63
|
+
// match ordinary correct engineering prose describing an invariant, not an unverified guess,
|
|
64
|
+
// e.g. "the response should be a 200 for valid input" (spec language, not conjecture).
|
|
65
|
+
test('OK/expected-noise: "should be" fires even on spec-style invariant language (broad pattern, not a bug but a known false-positive source)', () => {
|
|
66
|
+
const result = runGate(
|
|
67
|
+
{ tool_name: 'Write', tool_input: { file_path: '/repo/x.js', content: '// per the spec, the response should be a 200 for valid input' } },
|
|
68
|
+
ENABLE,
|
|
69
|
+
);
|
|
70
|
+
assert.ok(isWarn(result), 'documents that "should be" over-fires on legitimate spec prose, not just conjecture');
|
|
71
|
+
});
|
|
@@ -1,78 +1,95 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
6
|
-
import { test } from 'node:test';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
|
-
|
|
9
|
-
const GATE = join(dirname(fileURLToPath(import.meta.url)), 'index.mjs');
|
|
10
|
-
|
|
11
|
-
function runGate(payload, { config } = {}) {
|
|
12
|
-
const project = mkdtempSync(join(tmpdir(), 'never-assume-'));
|
|
13
|
-
mkdirSync(join(project, '.git'));
|
|
14
|
-
if (config) {
|
|
15
|
-
mkdirSync(join(project, '.ai'));
|
|
16
|
-
writeFileSync(join(project, '.ai', 'config.json'), JSON.stringify(config));
|
|
17
|
-
}
|
|
18
|
-
const out = execFileSync(process.execPath, [GATE], {
|
|
19
|
-
input: JSON.stringify(payload),
|
|
20
|
-
encoding: 'utf8',
|
|
21
|
-
cwd: project,
|
|
22
|
-
// Isolate from the user's real global config: point homedir() at the temp
|
|
23
|
-
// project so the global-config fallback finds nothing (registry default).
|
|
24
|
-
env: { ...process.env, HOME: project, USERPROFILE: project },
|
|
25
|
-
});
|
|
26
|
-
return out.trim() ? JSON.parse(out.trim()) : null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function write(content) {
|
|
30
|
-
return {
|
|
31
|
-
tool_name: 'Write',
|
|
32
|
-
tool_input: { file_path: '/repo/src/x.js', content },
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function isWarn(result) {
|
|
36
|
-
return result?.hookSpecificOutput?.additionalContext !== undefined;
|
|
37
|
-
}
|
|
38
|
-
function isDeny(result) {
|
|
39
|
-
return result?.hookSpecificOutput?.permissionDecision === 'deny';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const ENABLE = {
|
|
43
|
-
config: { gates: { requireVerificationBeforeAssuming: true } },
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
test('warns on conjecture phrasing', () => {
|
|
47
|
-
assert.ok(isWarn(runGate(write('// i assume the timezone is UTC'), ENABLE)));
|
|
48
|
-
assert.ok(isWarn(runGate(write('// probably fine to skip this'), ENABLE)));
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('never denies, only warns', () => {
|
|
52
|
-
const result = runGate(write('// i assume this works'), ENABLE);
|
|
53
|
-
assert.ok(isWarn(result));
|
|
54
|
-
assert.ok(!isDeny(result));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('allows content without conjecture phrasing', () => {
|
|
58
|
-
assert.equal(runGate(write('const x = 1;'), ENABLE), null);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('disabled by default (registry default is false)', () => {
|
|
62
|
-
assert.equal(runGate(write('// i assume this works')), null);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
assert.
|
|
75
|
-
assert.ok(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const GATE = join(dirname(fileURLToPath(import.meta.url)), 'index.mjs');
|
|
10
|
+
|
|
11
|
+
function runGate(payload, { config } = {}) {
|
|
12
|
+
const project = mkdtempSync(join(tmpdir(), 'never-assume-'));
|
|
13
|
+
mkdirSync(join(project, '.git'));
|
|
14
|
+
if (config) {
|
|
15
|
+
mkdirSync(join(project, '.ai'));
|
|
16
|
+
writeFileSync(join(project, '.ai', 'config.json'), JSON.stringify(config));
|
|
17
|
+
}
|
|
18
|
+
const out = execFileSync(process.execPath, [GATE], {
|
|
19
|
+
input: JSON.stringify(payload),
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
cwd: project,
|
|
22
|
+
// Isolate from the user's real global config: point homedir() at the temp
|
|
23
|
+
// project so the global-config fallback finds nothing (registry default).
|
|
24
|
+
env: { ...process.env, HOME: project, USERPROFILE: project },
|
|
25
|
+
});
|
|
26
|
+
return out.trim() ? JSON.parse(out.trim()) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function write(content) {
|
|
30
|
+
return {
|
|
31
|
+
tool_name: 'Write',
|
|
32
|
+
tool_input: { file_path: '/repo/src/x.js', content },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function isWarn(result) {
|
|
36
|
+
return result?.hookSpecificOutput?.additionalContext !== undefined;
|
|
37
|
+
}
|
|
38
|
+
function isDeny(result) {
|
|
39
|
+
return result?.hookSpecificOutput?.permissionDecision === 'deny';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ENABLE = {
|
|
43
|
+
config: { gates: { requireVerificationBeforeAssuming: true } },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
test('warns on conjecture phrasing', () => {
|
|
47
|
+
assert.ok(isWarn(runGate(write('// i assume the timezone is UTC'), ENABLE)));
|
|
48
|
+
assert.ok(isWarn(runGate(write('// probably fine to skip this'), ENABLE)));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('never denies, only warns', () => {
|
|
52
|
+
const result = runGate(write('// i assume this works'), ENABLE);
|
|
53
|
+
assert.ok(isWarn(result));
|
|
54
|
+
assert.ok(!isDeny(result));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('allows content without conjecture phrasing', () => {
|
|
58
|
+
assert.equal(runGate(write('const x = 1;'), ENABLE), null);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('disabled by default (registry default is false)', () => {
|
|
62
|
+
assert.equal(runGate(write('// i assume this works')), null);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('warns on conjecture phrasing in Spanish', () => {
|
|
66
|
+
assert.ok(isWarn(runGate(write('// supongo que la zona horaria es UTC'), ENABLE)));
|
|
67
|
+
assert.ok(isWarn(runGate(write('// probablemente sea correcto omitir esto'), ENABLE)));
|
|
68
|
+
assert.ok(isWarn(runGate(write('// deberia ser suficiente con este chequeo'), ENABLE)));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('bilingual control: ES and EN equivalent conjecture briefs both warn', () => {
|
|
72
|
+
const es = write('// creo que el usuario ya esta autenticado, no lo verifique');
|
|
73
|
+
const en = write('// i assume the user is already authenticated, did not verify it');
|
|
74
|
+
assert.ok(isWarn(runGate(es, ENABLE)));
|
|
75
|
+
assert.ok(isWarn(runGate(en, ENABLE)));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('allows neutral Spanish content without conjecture phrasing', () => {
|
|
79
|
+
assert.equal(runGate(write('const contrasena = "verificada";'), ENABLE), null);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('project conjecturePatterns override replaces the built-in list', () => {
|
|
83
|
+
const config = {
|
|
84
|
+
gates: {
|
|
85
|
+
requireVerificationBeforeAssuming: {
|
|
86
|
+
enabled: true,
|
|
87
|
+
conjecturePatterns: ['totally made up'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
assert.equal(runGate(write('// i assume this works'), { config }), null);
|
|
92
|
+
assert.ok(
|
|
93
|
+
isWarn(runGate(write('// totally made up value here'), { config })),
|
|
94
|
+
);
|
|
95
|
+
});
|