@devrik-tools/claude-gates 0.4.0 → 0.7.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.
Files changed (57) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.es.md +39 -4
  3. package/README.md +34 -5
  4. package/cli/config.mjs +126 -124
  5. package/cli/init.mjs +303 -276
  6. package/cli/install.mjs +281 -175
  7. package/cli/materialize.mjs +103 -102
  8. package/cli/registry.mjs +139 -136
  9. package/cli/smoke-fixtures.json +65 -0
  10. package/cli/task.mjs +140 -140
  11. package/package.json +1 -1
  12. package/plugins/gates/.claude-plugin/plugin.json +1 -1
  13. package/plugins/gates/hooks/ask-adoption.mjs +147 -147
  14. package/plugins/gates/hooks/doctor.mjs +207 -207
  15. package/plugins/gates/hooks/gates/atomic-commit/index.mjs +229 -0
  16. package/plugins/gates/hooks/gates/audit-before-build/index.mjs +110 -88
  17. package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +50 -50
  18. package/plugins/gates/hooks/gates/bash-commands/index.mjs +215 -215
  19. package/plugins/gates/hooks/gates/brief-approved/index.mjs +216 -0
  20. package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +269 -265
  21. package/plugins/gates/hooks/gates/capability-map/index.mjs +701 -0
  22. package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +527 -501
  23. package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +48 -43
  24. package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -83
  25. package/plugins/gates/hooks/gates/force-parallel/index.mjs +134 -119
  26. package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -134
  27. package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -187
  28. package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -260
  29. package/plugins/gates/hooks/gates/lint-commit/index.mjs +152 -149
  30. package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -180
  31. package/plugins/gates/hooks/gates/never-assume/index.mjs +59 -58
  32. package/plugins/gates/hooks/gates/no-blocking/index.mjs +163 -148
  33. package/plugins/gates/hooks/gates/no-coauthor/index.mjs +127 -0
  34. package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +183 -0
  35. package/plugins/gates/hooks/gates/protected-paths/index.mjs +149 -144
  36. package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +91 -89
  37. package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +263 -159
  38. package/plugins/gates/hooks/gates/risk-level/index.mjs +265 -263
  39. package/plugins/gates/hooks/gates/root-cause-first/index.mjs +57 -56
  40. package/plugins/gates/hooks/gates/root-whitelist/index.mjs +211 -131
  41. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +181 -184
  42. package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -256
  43. package/plugins/gates/hooks/gates/staged-lint/index.mjs +187 -0
  44. package/plugins/gates/hooks/gates/stop-pending/index.mjs +169 -164
  45. package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -187
  46. package/plugins/gates/hooks/gates/tool-map/index.mjs +168 -143
  47. package/plugins/gates/hooks/hooks.json +61 -0
  48. package/plugins/gates/hooks/lib/config.mjs +179 -172
  49. package/plugins/gates/hooks/lib/hook-io.mjs +367 -357
  50. package/plugins/gates/hooks/lib/signals.mjs +172 -127
  51. package/plugins/gates/hooks/wiring-check.mjs +227 -227
  52. package/plugins/tasks/.claude-plugin/plugin.json +1 -1
  53. package/plugins/tasks/hooks/hooks.json +26 -26
  54. package/plugins/tasks/hooks/lib/task-store.mjs +217 -197
  55. package/plugins/tasks/hooks/register-requests.mjs +145 -145
  56. package/plugins/tasks/hooks/session-tasks.mjs +108 -108
  57. package/registry.json +192 -1
@@ -1,180 +1,180 @@
1
- // mandatory-flow — denies an implementation delegation when there is no live task
2
- // pointer on disk backed by a contract file. Declaring pipeline stages in prose (what
3
- // implementation-pipeline.mjs checks) is not the same as a live task actually existing
4
- // on disk; this gate checks the disk fact, not the prompt's wording. Migrated from
5
- // ~/.claude/hooks/guard-flujo-obligatorio.mjs.
6
- //
7
- // ── What a project can configure (params) ───────────────────────────────────────────
8
- // activePointerPath path (relative to the project root) to the file naming the
9
- // active task/pipeline slug.
10
- // exemptSubagents subagent types exempt outright (read-only pipeline stages).
11
- // Replaces the built-in list wholesale.
12
- // The defaults live here, in the source, so a project reads them and knows exactly what
13
- // its override replaces.
14
- //
15
- // ── Off by default, and quiet outside its narrow trigger ───────────────────────────
16
- // Only STANDARD/HIGH-RISK implementation delegations, on non-exempt subagents, not
17
- // about the harness itself, reach the disk check.
18
-
19
- import { existsSync, readFileSync } from 'node:fs';
20
- import { join, normalize, sep } from 'node:path';
21
- import {
22
- runGate,
23
- deny,
24
- toolInGroups,
25
- delegationPromptOf,
26
- } from '../../lib/hook-io.mjs';
27
-
28
- const GATE_ID = 'mandatory-flow';
29
- const CONFIG_KEY = 'requireLiveTaskWhenImplementing';
30
-
31
- const DEFAULT_ACTIVE_POINTER_PATH = join('.ai', 'pipeline', 'ACTIVA');
32
-
33
- const DEFAULT_EXEMPT_SUBAGENTS = [
34
- 'explore',
35
- 'plan',
36
- 'scout',
37
- 'revision',
38
- 'contraste',
39
- 'test-planner',
40
- 'qa',
41
- 'ui',
42
- 'ux',
43
- ];
44
-
45
- /** Contract files that count as evidence a task's contract exists on disk. */
46
- const TASK_CONTRACT_FILES = [
47
- 'asserts.md',
48
- 'task.json',
49
- 'spec.md',
50
- 'requirements.md',
51
- 'acceptance.md',
52
- ];
53
-
54
- function withWordBoundary(alternation) {
55
- return new RegExp(
56
- `(?:^|[^\\p{L}\\p{N}_])(?:${alternation})(?:[^\\p{L}\\p{N}_]|$)`,
57
- 'iu',
58
- );
59
- }
60
-
61
- const IMPLEMENTATION_VERBS = withWordBoundary(
62
- 'implementa|implementar|implement(á|é)|agreg(a|á)|agregar|añad(e|í)|añadir|cre(a|á)|crear|' +
63
- 'arregl(a|á)|arreglar|cambi(a|á)|cambiar|migr(a|á)|migrar|' +
64
- 'corrige|corregir|correg(í|ir)|constru(ye|í)|construir|modific(a|á)|modificar|' +
65
- 'refactoriz(a|á)|refactorizar|elimin(a|á)|eliminar|reescrib(e|í)|reescribir|desplieg(a|á)|desplegar|' +
66
- 'escrib(í|e)|escribir|implement\\w*|writ(?:e|ing)|creat\\w*|fix\\w*|build\\w*|refactor\\w*|migrat\\w*|' +
67
- 'add\\w*|remov\\w*|delet\\w*|modify|modifies|modifying|rewrit\\w*',
68
- );
69
-
70
- /** Declared LEVEL near the word "level"/"classification" in Spanish or English, matching
71
- * the plugin-wide convention (see risk-level.mjs). */
72
- const DEMANDING_LEVEL_PATTERN =
73
- /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(STANDARD|HIGH-RISK)\b/iu;
74
- const EXEMPT_LEVEL_PATTERN =
75
- /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(QUESTION|MICRO)\b/iu;
76
-
77
- const HARNESS_PATTERN =
78
- /(\.claude[\\/]|hooks[\\/]|plugins[\\/]gates|settings\.json|[\\/]agents[\\/]\w|[\\/]rules[\\/]\w|[\\/]skills[\\/]\w)/i;
79
-
80
- function isExempt(toolInput, prompt, exemptSubagents) {
81
- const subagentType = String(
82
- toolInput.subagent_type ?? toolInput.subagentType ?? '',
83
- ).toLowerCase();
84
- if (exemptSubagents.includes(subagentType)) return true;
85
- if (EXEMPT_LEVEL_PATTERN.test(prompt)) return true;
86
- if (!DEMANDING_LEVEL_PATTERN.test(prompt)) return true;
87
- if (!IMPLEMENTATION_VERBS.test(prompt)) return true;
88
- if (HARNESS_PATTERN.test(prompt)) return true;
89
- return false;
90
- }
91
-
92
- function hasTaskContract(taskDirectory) {
93
- return TASK_CONTRACT_FILES.some((file) => {
94
- try {
95
- return existsSync(join(taskDirectory, file));
96
- } catch {
97
- return false;
98
- }
99
- });
100
- }
101
-
102
- /** The pointer file's trimmed content, or '' when it cannot be read or the slug
103
- * attempts path traversal. A slug is a directory name, not a path: rejecting any
104
- * segment separator or '..' closes off `join(cwd, '.ai', 'pipeline', slug)` escaping
105
- * that directory to accept an unrelated file elsewhere on disk as the task contract. */
106
- function readSlug(pointerPath) {
107
- let raw;
108
- try {
109
- raw = readFileSync(pointerPath, 'utf8').trim();
110
- } catch {
111
- return '';
112
- }
113
- if (!raw) return '';
114
- const normalized = normalize(raw);
115
- const hasTraversal = normalized
116
- .split(/[\\/]/)
117
- .some((segment) => segment === '..' || segment === '.');
118
- if (hasTraversal || normalized.includes(sep) || normalized.includes('/')) {
119
- return '';
120
- }
121
- return normalized;
122
- }
123
-
124
- /** Denies for whichever of the three live-task facts is missing, or does nothing. */
125
- function checkLiveTask(pointerPath) {
126
- if (!existsSync(pointerPath)) {
127
- deny(
128
- GATE_ID,
129
- `This delegation is going to implement, but there is no live task: ${pointerPath} ` +
130
- 'does not exist. Start a task pointing to it before delegating, or declare ' +
131
- 'LEVEL: QUESTION/MICRO if this is not implementation.',
132
- );
133
- }
134
-
135
- const slug = readSlug(pointerPath);
136
- if (!slug) {
137
- deny(
138
- GATE_ID,
139
- `${pointerPath} exists but is empty. An active-task pointer with no slug is not ` +
140
- 'a live task. Write the task slug into it before delegating.',
141
- );
142
- }
143
-
144
- const taskDirectory = join(process.cwd(), '.ai', 'pipeline', slug);
145
- if (!hasTaskContract(taskDirectory)) {
146
- deny(
147
- GATE_ID,
148
- `The active task '${slug}' has no contract on disk: none of ` +
149
- `${TASK_CONTRACT_FILES.join(', ')} exists under ${taskDirectory}. ` +
150
- 'Write the contract before implementing.',
151
- );
152
- }
153
- }
154
-
155
- runGate(
156
- {
157
- id: GATE_ID,
158
- configKey: CONFIG_KEY,
159
- enabledByDefault: false,
160
- defaultParams: {
161
- activePointerPath: DEFAULT_ACTIVE_POINTER_PATH,
162
- exemptSubagents: DEFAULT_EXEMPT_SUBAGENTS,
163
- },
164
- },
165
- ({ toolName, toolInput, parameters }) => {
166
- if (!toolInGroups(toolName, ['delegation'])) return;
167
-
168
- const prompt = delegationPromptOf(toolInput);
169
- if (!prompt.trim()) return;
170
-
171
- const exemptSubagents =
172
- parameters.exemptSubagents ?? DEFAULT_EXEMPT_SUBAGENTS;
173
- if (isExempt(toolInput, prompt, exemptSubagents)) return;
174
-
175
- const activePointerPath = String(
176
- parameters.activePointerPath ?? DEFAULT_ACTIVE_POINTER_PATH,
177
- );
178
- checkLiveTask(join(process.cwd(), activePointerPath));
179
- },
180
- );
1
+ // mandatory-flow — denies an implementation delegation when there is no live task
2
+ // pointer on disk backed by a contract file. Declaring pipeline stages in prose (what
3
+ // implementation-pipeline.mjs checks) is not the same as a live task actually existing
4
+ // on disk; this gate checks the disk fact, not the prompt's wording. Migrated from
5
+ // ~/.claude/hooks/guard-flujo-obligatorio.mjs.
6
+ //
7
+ // ── What a project can configure (params) ───────────────────────────────────────────
8
+ // activePointerPath path (relative to the project root) to the file naming the
9
+ // active task/pipeline slug.
10
+ // exemptSubagents subagent types exempt outright (read-only pipeline stages).
11
+ // Replaces the built-in list wholesale.
12
+ // The defaults live here, in the source, so a project reads them and knows exactly what
13
+ // its override replaces.
14
+ //
15
+ // ── Off by default, and quiet outside its narrow trigger ───────────────────────────
16
+ // Only STANDARD/HIGH-RISK implementation delegations, on non-exempt subagents, not
17
+ // about the harness itself, reach the disk check.
18
+
19
+ import { existsSync, readFileSync } from 'node:fs';
20
+ import { join, normalize, sep } from 'node:path';
21
+ import {
22
+ runGate,
23
+ deny,
24
+ toolInGroups,
25
+ delegationPromptOf,
26
+ } from '../../lib/hook-io.mjs';
27
+
28
+ const GATE_ID = 'mandatory-flow';
29
+ const CONFIG_KEY = 'requireLiveTaskWhenImplementing';
30
+
31
+ const DEFAULT_ACTIVE_POINTER_PATH = join('.ai', 'pipeline', 'ACTIVA');
32
+
33
+ const DEFAULT_EXEMPT_SUBAGENTS = [
34
+ 'explore',
35
+ 'plan',
36
+ 'scout',
37
+ 'revision',
38
+ 'contraste',
39
+ 'test-planner',
40
+ 'qa',
41
+ 'ui',
42
+ 'ux',
43
+ ];
44
+
45
+ /** Contract files that count as evidence a task's contract exists on disk. */
46
+ const TASK_CONTRACT_FILES = [
47
+ 'asserts.md',
48
+ 'task.json',
49
+ 'spec.md',
50
+ 'requirements.md',
51
+ 'acceptance.md',
52
+ ];
53
+
54
+ function withWordBoundary(alternation) {
55
+ return new RegExp(
56
+ `(?:^|[^\\p{L}\\p{N}_])(?:${alternation})(?:[^\\p{L}\\p{N}_]|$)`,
57
+ 'iu',
58
+ );
59
+ }
60
+
61
+ const IMPLEMENTATION_VERBS = withWordBoundary(
62
+ 'implementa|implementar|implement(á|é)|agreg(a|á)|agregar|añad(e|í)|añadir|cre(a|á)|crear|' +
63
+ 'arregl(a|á)|arreglar|cambi(a|á)|cambiar|migr(a|á)|migrar|' +
64
+ 'corrige|corregir|correg(í|ir)|constru(ye|í)|construir|modific(a|á)|modificar|' +
65
+ 'refactoriz(a|á)|refactorizar|elimin(a|á)|eliminar|reescrib(e|í)|reescribir|desplieg(a|á)|desplegar|' +
66
+ 'escrib(í|e)|escribir|implement\\w*|writ(?:e|ing)|creat\\w*|fix\\w*|build\\w*|refactor\\w*|migrat\\w*|' +
67
+ 'add\\w*|remov\\w*|delet\\w*|modify|modifies|modifying|rewrit\\w*',
68
+ );
69
+
70
+ /** Declared LEVEL near the word "level"/"classification" in Spanish or English, matching
71
+ * the plugin-wide convention (see risk-level.mjs). */
72
+ const DEMANDING_LEVEL_PATTERN =
73
+ /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(STANDARD|HIGH-RISK)\b/iu;
74
+ const EXEMPT_LEVEL_PATTERN =
75
+ /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(QUESTION|MICRO)\b/iu;
76
+
77
+ const HARNESS_PATTERN =
78
+ /(\.claude[\\/]|hooks[\\/]|plugins[\\/]gates|settings\.json|[\\/]agents[\\/]\w|[\\/]rules[\\/]\w|[\\/]skills[\\/]\w)/i;
79
+
80
+ function isExempt(toolInput, prompt, exemptSubagents) {
81
+ const subagentType = String(
82
+ toolInput.subagent_type ?? toolInput.subagentType ?? '',
83
+ ).toLowerCase();
84
+ if (exemptSubagents.includes(subagentType)) return true;
85
+ if (EXEMPT_LEVEL_PATTERN.test(prompt)) return true;
86
+ if (!DEMANDING_LEVEL_PATTERN.test(prompt)) return true;
87
+ if (!IMPLEMENTATION_VERBS.test(prompt)) return true;
88
+ if (HARNESS_PATTERN.test(prompt)) return true;
89
+ return false;
90
+ }
91
+
92
+ function hasTaskContract(taskDirectory) {
93
+ return TASK_CONTRACT_FILES.some((file) => {
94
+ try {
95
+ return existsSync(join(taskDirectory, file));
96
+ } catch {
97
+ return false;
98
+ }
99
+ });
100
+ }
101
+
102
+ /** The pointer file's trimmed content, or '' when it cannot be read or the slug
103
+ * attempts path traversal. A slug is a directory name, not a path: rejecting any
104
+ * segment separator or '..' closes off `join(cwd, '.ai', 'pipeline', slug)` escaping
105
+ * that directory to accept an unrelated file elsewhere on disk as the task contract. */
106
+ function readSlug(pointerPath) {
107
+ let raw;
108
+ try {
109
+ raw = readFileSync(pointerPath, 'utf8').trim();
110
+ } catch {
111
+ return '';
112
+ }
113
+ if (!raw) return '';
114
+ const normalized = normalize(raw);
115
+ const hasTraversal = normalized
116
+ .split(/[\\/]/)
117
+ .some((segment) => segment === '..' || segment === '.');
118
+ if (hasTraversal || normalized.includes(sep) || normalized.includes('/')) {
119
+ return '';
120
+ }
121
+ return normalized;
122
+ }
123
+
124
+ /** Denies for whichever of the three live-task facts is missing, or does nothing. */
125
+ function checkLiveTask(pointerPath) {
126
+ if (!existsSync(pointerPath)) {
127
+ deny(
128
+ GATE_ID,
129
+ `This delegation is going to implement, but there is no live task: ${pointerPath} ` +
130
+ 'does not exist. Start a task pointing to it before delegating, or declare ' +
131
+ 'LEVEL: QUESTION/MICRO if this is not implementation.',
132
+ );
133
+ }
134
+
135
+ const slug = readSlug(pointerPath);
136
+ if (!slug) {
137
+ deny(
138
+ GATE_ID,
139
+ `${pointerPath} exists but is empty. An active-task pointer with no slug is not ` +
140
+ 'a live task. Write the task slug into it before delegating.',
141
+ );
142
+ }
143
+
144
+ const taskDirectory = join(process.cwd(), '.ai', 'pipeline', slug);
145
+ if (!hasTaskContract(taskDirectory)) {
146
+ deny(
147
+ GATE_ID,
148
+ `The active task '${slug}' has no contract on disk: none of ` +
149
+ `${TASK_CONTRACT_FILES.join(', ')} exists under ${taskDirectory}. ` +
150
+ 'Write the contract before implementing.',
151
+ );
152
+ }
153
+ }
154
+
155
+ runGate(
156
+ {
157
+ id: GATE_ID,
158
+ configKey: CONFIG_KEY,
159
+ enabledByDefault: false,
160
+ defaultParams: {
161
+ activePointerPath: DEFAULT_ACTIVE_POINTER_PATH,
162
+ exemptSubagents: DEFAULT_EXEMPT_SUBAGENTS,
163
+ },
164
+ },
165
+ ({ toolName, toolInput, parameters }) => {
166
+ if (!toolInGroups(toolName, ['delegation'])) return;
167
+
168
+ const prompt = delegationPromptOf(toolInput);
169
+ if (!prompt.trim()) return;
170
+
171
+ const exemptSubagents =
172
+ parameters.exemptSubagents ?? DEFAULT_EXEMPT_SUBAGENTS;
173
+ if (isExempt(toolInput, prompt, exemptSubagents)) return;
174
+
175
+ const activePointerPath = String(
176
+ parameters.activePointerPath ?? DEFAULT_ACTIVE_POINTER_PATH,
177
+ );
178
+ checkLiveTask(join(process.cwd(), activePointerPath));
179
+ },
180
+ );
@@ -1,58 +1,59 @@
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.
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']))
20
+ return delegationPromptOf(toolInput);
21
+ return writtenContentOf(toolInput);
22
+ }
23
+
24
+ runGate(
25
+ {
26
+ id: GATE_ID,
27
+ configKey: CONFIG_KEY,
28
+ enabledByDefault: false,
29
+ defaultParams: {
30
+ conjecturePatterns: DEFAULT_CONJECTURE_PATTERNS,
31
+ },
32
+ },
33
+ ({ toolName, toolInput, parameters }) => {
34
+ const isWrite = toolInGroups(toolName, ['write']);
35
+ const isDelegation = toolInGroups(toolName, ['delegation']);
36
+ if (!isWrite && !isDelegation) return;
37
+
38
+ const content = extractContent(toolName, toolInput);
39
+ if (!content) return;
40
+
41
+ const patterns = parameters.conjecturePatterns.map(
42
+ (source) => new RegExp(source, 'i'),
43
+ );
44
+ const hits = [];
45
+ for (const pattern of patterns) {
46
+ const match = content.match(pattern);
47
+ if (match) hits.push(match[0]);
48
+ }
49
+ if (hits.length === 0) return;
50
+
51
+ warn(
52
+ GATE_ID,
53
+ `Content contains conjecture phrasing without stated verification: ${hits.join(', ')}. Verify before asserting instead of assuming.`,
54
+ );
55
+ },
56
+ );
57
+
58
+ // The source guard (guard-never-assume.mjs) is explicit: this never denies,
59
+ // only warns — conjecture language is a prompt to verify, not a blocker.