@devrik-tools/claude-gates 0.1.1 → 0.1.3

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 (105) hide show
  1. package/.claude-plugin/marketplace.json +23 -17
  2. package/cli/__tests__/config.test.mjs +113 -101
  3. package/cli/__tests__/install.test.mjs +102 -0
  4. package/cli/__tests__/materialize.test.mjs +95 -0
  5. package/cli/__tests__/registry-gates-consistency.test.mjs +52 -44
  6. package/cli/__tests__/task.test.mjs +124 -0
  7. package/cli/config.mjs +124 -113
  8. package/cli/index.mjs +100 -97
  9. package/cli/init.mjs +259 -256
  10. package/cli/install.mjs +150 -80
  11. package/cli/materialize.mjs +102 -51
  12. package/cli/registry.mjs +136 -127
  13. package/cli/task.mjs +140 -0
  14. package/package.json +74 -73
  15. package/plugins/gates/hooks/__tests__/ask-adoption.test.mjs +83 -0
  16. package/plugins/gates/hooks/__tests__/doctor.test.mjs +85 -0
  17. package/plugins/gates/hooks/__tests__/wiring-check.test.mjs +65 -0
  18. package/plugins/gates/hooks/ask-adoption.mjs +147 -0
  19. package/plugins/gates/hooks/doctor.mjs +207 -0
  20. package/plugins/gates/hooks/gates/audit-before-build/audit-before-build.edge.test.mjs +83 -0
  21. package/plugins/gates/hooks/gates/audit-before-build/index.mjs +88 -88
  22. package/plugins/gates/hooks/gates/audit-before-build/test.mjs +117 -92
  23. package/plugins/gates/hooks/gates/autonomous-mode/autonomous.edge.test.mjs +104 -0
  24. package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +48 -0
  25. package/plugins/gates/hooks/gates/autonomous-mode/test.mjs +58 -0
  26. package/plugins/gates/hooks/gates/bash-commands/bash-commands.edge.test.mjs +165 -0
  27. package/plugins/gates/hooks/gates/bash-commands/index.mjs +285 -268
  28. package/plugins/gates/hooks/gates/brief-before-delegate/brief-before-delegate.edge.test.mjs +151 -0
  29. package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +265 -177
  30. package/plugins/gates/hooks/gates/circuit-breaker/circuit-breaker.edge.test.mjs +207 -0
  31. package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +501 -456
  32. package/plugins/gates/hooks/gates/circuit-breaker/test.mjs +161 -143
  33. package/plugins/gates/hooks/gates/dependency-skills/dependency-skills.edge.test.mjs +69 -0
  34. package/plugins/gates/hooks/gates/dependency-skills/index.mjs +126 -118
  35. package/plugins/gates/hooks/gates/diagnosis-before-patch/diagnosis-before-patch.edge.test.mjs +68 -0
  36. package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +43 -49
  37. package/plugins/gates/hooks/gates/feature-catalog/edge.edge.test.mjs +83 -0
  38. package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -100
  39. package/plugins/gates/hooks/gates/force-parallel/index.mjs +119 -0
  40. package/plugins/gates/hooks/gates/force-parallel/test.mjs +88 -0
  41. package/plugins/gates/hooks/gates/forge-flow/forge-flow.edge.test.mjs +155 -0
  42. package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -112
  43. package/plugins/gates/hooks/gates/implementation-pipeline/edge.edge.test.mjs +66 -0
  44. package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -186
  45. package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -238
  46. package/plugins/gates/hooks/gates/intent-flow/intent-flow.edge.test.mjs +96 -0
  47. package/plugins/gates/hooks/gates/intent-flow/test.mjs +165 -136
  48. package/plugins/gates/hooks/gates/lint-commit/index.mjs +149 -0
  49. package/plugins/gates/hooks/gates/lint-commit/test.mjs +85 -0
  50. package/plugins/gates/hooks/gates/mandatory-flow/edge.edge.test.mjs +100 -0
  51. package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -166
  52. package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +79 -85
  53. package/plugins/gates/hooks/gates/neutral-spanish/neutral-spanish.edge.test.mjs +80 -0
  54. package/plugins/gates/hooks/gates/never-assume/index.mjs +58 -55
  55. package/plugins/gates/hooks/gates/never-assume/never-assume.edge.test.mjs +71 -0
  56. package/plugins/gates/hooks/gates/never-assume/test.mjs +95 -78
  57. package/plugins/gates/hooks/gates/no-blocking/index.mjs +148 -142
  58. package/plugins/gates/hooks/gates/no-blocking/no-blocking.edge.test.mjs +109 -0
  59. package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +123 -120
  60. package/plugins/gates/hooks/gates/no-memory-dependency/no-memory-dependency.edge.test.mjs +83 -0
  61. package/plugins/gates/hooks/gates/no-reconfirm/no-reconfirm.edge.test.mjs +91 -0
  62. package/plugins/gates/hooks/gates/protected-paths/index.mjs +144 -147
  63. package/plugins/gates/hooks/gates/protected-paths/protected-paths.edge.test.mjs +126 -0
  64. package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +89 -64
  65. package/plugins/gates/hooks/gates/recurrence-lock/recurrence-lock.edge.test.mjs +102 -0
  66. package/plugins/gates/hooks/gates/reuse-before-build/edge.edge.test.mjs +94 -0
  67. package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +159 -150
  68. package/plugins/gates/hooks/gates/risk-level/index.mjs +263 -203
  69. package/plugins/gates/hooks/gates/risk-level/risk-level.edge.test.mjs +116 -0
  70. package/plugins/gates/hooks/gates/risk-level/test.mjs +153 -125
  71. package/plugins/gates/hooks/gates/root-cause-first/index.mjs +56 -50
  72. package/plugins/gates/hooks/gates/root-cause-first/root-cause-first.edge.test.mjs +58 -0
  73. package/plugins/gates/hooks/gates/root-whitelist/index.mjs +116 -111
  74. package/plugins/gates/hooks/gates/root-whitelist/root-whitelist.edge.test.mjs +97 -0
  75. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +184 -99
  76. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/rule-skill-autodiscovery.edge.test.mjs +126 -0
  77. package/plugins/gates/hooks/gates/sdd-specs/edge.edge.test.mjs +130 -0
  78. package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -251
  79. package/plugins/gates/hooks/gates/stop-pending/index.mjs +159 -0
  80. package/plugins/gates/hooks/gates/stop-pending/test.mjs +114 -0
  81. package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +101 -101
  82. package/plugins/gates/hooks/gates/test-after-implementation/test-after-implementation.edge.test.mjs +72 -0
  83. package/plugins/gates/hooks/gates/test-matrix/edge.edge.test.mjs +63 -0
  84. package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -181
  85. package/plugins/gates/hooks/gates/test-matrix/test.mjs +114 -87
  86. package/plugins/gates/hooks/gates/tool-map/edge.edge.test.mjs +62 -0
  87. package/plugins/gates/hooks/gates/tool-map/index.mjs +143 -140
  88. package/plugins/gates/hooks/hooks.json +346 -266
  89. package/plugins/gates/hooks/lib/__tests__/config.test.mjs +173 -154
  90. package/plugins/gates/hooks/lib/__tests__/hook-io.test.mjs +210 -154
  91. package/plugins/gates/hooks/lib/__tests__/signals.test.mjs +114 -0
  92. package/plugins/gates/hooks/lib/config.mjs +172 -165
  93. package/plugins/gates/hooks/lib/hook-io-config.edge.test.mjs +189 -0
  94. package/plugins/gates/hooks/lib/hook-io.mjs +318 -208
  95. package/plugins/gates/hooks/lib/signals.mjs +127 -0
  96. package/plugins/gates/hooks/wiring-check.mjs +227 -0
  97. package/plugins/tasks/.claude-plugin/plugin.json +9 -0
  98. package/plugins/tasks/hooks/__tests__/register-requests.test.mjs +100 -0
  99. package/plugins/tasks/hooks/__tests__/session-tasks.test.mjs +95 -0
  100. package/plugins/tasks/hooks/hooks.json +26 -0
  101. package/plugins/tasks/hooks/lib/__tests__/task-store.test.mjs +180 -132
  102. package/plugins/tasks/hooks/lib/task-store.mjs +197 -159
  103. package/plugins/tasks/hooks/register-requests.mjs +141 -108
  104. package/plugins/tasks/hooks/session-tasks.mjs +108 -0
  105. package/registry.json +793 -668
@@ -1,186 +1,187 @@
1
- // implementation-pipeline — denies a delegation that sends a builder subagent straight
2
- // to writing code without the prompt declaring the pipeline stages around it:
3
- // definition (where the context came from), writing's own verification plan, and a
4
- // separate reviewer for validation/QA/closure. Migrated from
5
- // ~/.claude/hooks/guard-pipeline-de-implementacion.mjs.
6
- //
7
- // ── What a project can configure (params) ───────────────────────────────────────────
8
- // builderSubagents subagent types that count as builders — the ones this gate
9
- // holds to the pipeline. Replaces the built-in list wholesale.
10
- // exemptSubagents subagent types exempt outright: read-only pipeline stages that
11
- // cannot be required to declare a pipeline around themselves.
12
- // Replaces the built-in list wholesale.
13
- // The defaults live here, in the source, so a project reads them and knows exactly what
14
- // its override replaces.
15
- //
16
- // ── Off by default, and quiet outside its narrow trigger ───────────────────────────
17
- // This gate only evaluates a delegation whose prompt declares a STANDARD/HIGH-RISK
18
- // level, uses an implementation verb, targets a builder subagent, and is not about the
19
- // harness itself. A prompt that only describes work, or that is read-only/exploratory,
20
- // never reaches the check.
21
-
22
- import { runGate, deny, TOOL_GROUPS } from '../../lib/hook-io.mjs';
23
-
24
- const GATE_ID = 'implementation-pipeline';
25
- const CONFIG_KEY = 'requireImplementationPipeline';
26
-
27
- const DELEGATION_TOOLS = new Set(TOOL_GROUPS.delegation);
28
-
29
- const DEFAULT_BUILDER_SUBAGENTS = [
30
- 'frontend',
31
- 'backend',
32
- 'worker',
33
- 'worker-senior',
34
- 'general-purpose',
35
- ];
36
-
37
- const DEFAULT_EXEMPT_SUBAGENTS = [
38
- 'scout',
39
- 'explore',
40
- 'plan',
41
- 'revision',
42
- 'contraste',
43
- 'test-planner',
44
- 'qa',
45
- 'ui',
46
- 'ux',
47
- ];
48
-
49
- function withWordBoundary(alternation) {
50
- return new RegExp(
51
- `(?:^|[^\\p{L}\\p{N}_])(?:${alternation})(?:[^\\p{L}\\p{N}_]|$)`,
52
- 'iu',
53
- );
54
- }
55
-
56
- const IMPLEMENTATION_VERBS = withWordBoundary(
57
- 'implementa|implementar|implement(á|é)|agreg(a|á)|agregar|añad(e|í)|añadir|cre(a|á)|crear|' +
58
- 'arregl(a|á)|arreglar|cambi(a|á)|cambiar|migr(a|á)|migrar|' +
59
- 'corrige|corregir|correg(í|ir)|constru(ye|í)|construir|modific(a|á)|modificar|' +
60
- 'refactoriz(a|á)|refactorizar|elimin(a|á)|eliminar|reescrib(e|í)|reescribir|desplieg(a|á)|desplegar|' +
61
- 'escrib(í|e)|escribir|implement\\w*|writ(?:e|ing)|creat\\w*|fix\\w*|build\\w*|refactor\\w*|migrat\\w*|' +
62
- 'add\\w*|remov\\w*|delet\\w*|modify|modifies|modifying|rewrit\\w*',
63
- );
64
-
65
- /** Declared LEVEL near the word "level"/"classification" in Spanish or English, matching
66
- * the plugin-wide convention (see risk-level.mjs). */
67
- const DEMANDING_LEVEL_PATTERN =
68
- /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(STANDARD|HIGH-RISK)\b/iu;
69
- const EXEMPT_LEVEL_PATTERN =
70
- /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(QUESTION|MICRO)\b/iu;
71
- const HIGH_RISK_PATTERN =
72
- /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\bHIGH-RISK\b/iu;
73
-
74
- const HARNESS_PATTERN =
75
- /(\.claude[\\/]|hooks[\\/]|plugins[\\/]gates|settings\.json|[\\/]agents[\\/]\w|[\\/]rules[\\/]\w|[\\/]skills[\\/]\w)/i;
76
-
77
- // Several small patterns instead of one long alternation: a single big regex here
78
- // tripped the linter's regex-complexity limit. Any one matching counts as declared.
79
- const REVIEWER_NAME_PATTERN =
80
- /\b(reviewer|revisor|separate review|revisi[oó]n separada|contraste|auditor)\b/i;
81
- const REVIEWER_LABEL_PATTERN = /(review|revisi[oó]n)\s*:/i;
82
- const REVIEWER_ACTION_EN_PATTERN =
83
- /\breview\s+(?:the\s+)?(?:diff|result|work)\b/i;
84
- const REVIEWER_ACTION_ES_PATTERN =
85
- /\brevisa\s+(?:el\s+)?(?:diff|resultado|trabajo)\b/i;
86
- const REVIEWER_CODE_REVIEW_PATTERN = /code.review/i;
87
-
88
- function reviewerStageDeclared(prompt) {
89
- return (
90
- REVIEWER_NAME_PATTERN.test(prompt) ||
91
- REVIEWER_LABEL_PATTERN.test(prompt) ||
92
- REVIEWER_ACTION_EN_PATTERN.test(prompt) ||
93
- REVIEWER_ACTION_ES_PATTERN.test(prompt) ||
94
- REVIEWER_CODE_REVIEW_PATTERN.test(prompt)
95
- );
96
- }
97
-
98
- const DEFINITION_PATTERN =
99
- /\b(scout|reconnaissance|reconocimiento|prior exploration|explora(?:ci[oó]n)? previa|verified context|contexto verificado|asserts\.md|brief\.md)\b/i;
100
- const WRITING_PATTERN =
101
- /\b(test|tests|prueba|pruebas|vitest|jest|playwright|typecheck|lint|verification|verificaci[oó]n|verification criteria)\b/i;
102
-
103
- /** The three pipeline stages this gate requires be declared, in this order. */
104
- const REQUIRED_STAGES = [
105
- {
106
- id: 'definition',
107
- isDeclared: (prompt) => DEFINITION_PATTERN.test(prompt),
108
- missing:
109
- 'DEFINITION: does not declare where the context came from (a prior scout, read-only exploration, or an already-written asserts/brief).',
110
- line: 'Verified context: <path to the cited asserts/brief> — or: a prior scout ran, findings at <path>.',
111
- },
112
- {
113
- id: 'writing',
114
- isDeclared: (prompt) => WRITING_PATTERN.test(prompt),
115
- missing:
116
- 'WRITING: does not declare what is verified nor with what command. A builder does not validate its own work without a criterion written beforehand.',
117
- line: 'Verification: <exact command> and what is expected. Anything that cannot run is reported OMITTED, never PASS.',
118
- },
119
- {
120
- id: 'reviewer',
121
- isDeclared: reviewerStageDeclared,
122
- missing:
123
- 'REVIEWER: does not declare who reviews the result. Whoever implements does not validate their own work.',
124
- line: 'Review: the coordinator reviews the diff and the evidence before closing — or: a read-only `revision`/`contraste` agent reviews it.',
125
- },
126
- ];
127
-
128
- function isExempt(toolInput, prompt, builderSubagents, exemptSubagents) {
129
- const subagentType = String(
130
- toolInput.subagent_type ?? toolInput.subagentType ?? '',
131
- ).toLowerCase();
132
- if (exemptSubagents.includes(subagentType)) return true;
133
- // An unknown type is not assumed to be a builder: the safe side here is not to
134
- // invent requirements for agents this gate cannot classify.
135
- if (subagentType && !builderSubagents.includes(subagentType)) return true;
136
- if (EXEMPT_LEVEL_PATTERN.test(prompt)) return true;
137
- if (!DEMANDING_LEVEL_PATTERN.test(prompt)) return true;
138
- if (!IMPLEMENTATION_VERBS.test(prompt)) return true;
139
- if (HARNESS_PATTERN.test(prompt)) return true;
140
- return false;
141
- }
142
-
143
- runGate(
144
- {
145
- id: GATE_ID,
146
- configKey: CONFIG_KEY,
147
- enabledByDefault: false,
148
- defaultParams: {
149
- builderSubagents: DEFAULT_BUILDER_SUBAGENTS,
150
- exemptSubagents: DEFAULT_EXEMPT_SUBAGENTS,
151
- },
152
- },
153
- ({ toolName, toolInput, parameters }) => {
154
- if (!DELEGATION_TOOLS.has(toolName)) return;
155
-
156
- const prompt = String(
157
- toolInput.prompt ?? toolInput.Prompt ?? toolInput.task ?? '',
158
- );
159
- if (!prompt.trim()) return;
160
-
161
- const builderSubagents =
162
- parameters.builderSubagents ?? DEFAULT_BUILDER_SUBAGENTS;
163
- const exemptSubagents =
164
- parameters.exemptSubagents ?? DEFAULT_EXEMPT_SUBAGENTS;
165
- if (isExempt(toolInput, prompt, builderSubagents, exemptSubagents)) return;
166
-
167
- const missingStages = REQUIRED_STAGES.filter(
168
- (stage) => !stage.isDeclared(prompt),
169
- );
170
- if (missingStages.length === 0) return;
171
-
172
- const isHighRisk = HIGH_RISK_PATTERN.test(prompt);
173
- const details = missingStages.map((stage) => stage.missing).join(' ');
174
- const lines = missingStages.map((stage) => ` ${stage.line}`).join('\n');
175
-
176
- deny(
177
- GATE_ID,
178
- `This delegation is going to build code but skips ${missingStages.length} pipeline ` +
179
- `stage(s). ${details} ${
180
- isHighRisk
181
- ? 'In HIGH-RISK a separate reviewer is not negotiable. '
182
- : ''
183
- }PASTE THESE LINES INTO THE PROMPT AND RELAUNCH:\n${lines}`,
184
- );
185
- },
186
- );
1
+ // implementation-pipeline — denies a delegation that sends a builder subagent straight
2
+ // to writing code without the prompt declaring the pipeline stages around it:
3
+ // definition (where the context came from), writing's own verification plan, and a
4
+ // separate reviewer for validation/QA/closure. Migrated from
5
+ // ~/.claude/hooks/guard-pipeline-de-implementacion.mjs.
6
+ //
7
+ // ── What a project can configure (params) ───────────────────────────────────────────
8
+ // builderSubagents subagent types that count as builders — the ones this gate
9
+ // holds to the pipeline. Replaces the built-in list wholesale.
10
+ // exemptSubagents subagent types exempt outright: read-only pipeline stages that
11
+ // cannot be required to declare a pipeline around themselves.
12
+ // Replaces the built-in list wholesale.
13
+ // The defaults live here, in the source, so a project reads them and knows exactly what
14
+ // its override replaces.
15
+ //
16
+ // ── Off by default, and quiet outside its narrow trigger ───────────────────────────
17
+ // This gate only evaluates a delegation whose prompt declares a STANDARD/HIGH-RISK
18
+ // level, uses an implementation verb, targets a builder subagent, and is not about the
19
+ // harness itself. A prompt that only describes work, or that is read-only/exploratory,
20
+ // never reaches the check.
21
+
22
+ import {
23
+ runGate,
24
+ deny,
25
+ toolInGroups,
26
+ delegationPromptOf,
27
+ } from '../../lib/hook-io.mjs';
28
+
29
+ const GATE_ID = 'implementation-pipeline';
30
+ const CONFIG_KEY = 'requireImplementationPipeline';
31
+
32
+ const DEFAULT_BUILDER_SUBAGENTS = [
33
+ 'frontend',
34
+ 'backend',
35
+ 'worker',
36
+ 'worker-senior',
37
+ 'general-purpose',
38
+ ];
39
+
40
+ const DEFAULT_EXEMPT_SUBAGENTS = [
41
+ 'scout',
42
+ 'explore',
43
+ 'plan',
44
+ 'revision',
45
+ 'contraste',
46
+ 'test-planner',
47
+ 'qa',
48
+ 'ui',
49
+ 'ux',
50
+ ];
51
+
52
+ function withWordBoundary(alternation) {
53
+ return new RegExp(
54
+ `(?:^|[^\\p{L}\\p{N}_])(?:${alternation})(?:[^\\p{L}\\p{N}_]|$)`,
55
+ 'iu',
56
+ );
57
+ }
58
+
59
+ const IMPLEMENTATION_VERBS = withWordBoundary(
60
+ 'implementa|implementar|implement(á|é)|agreg(a|á)|agregar|añad(e|í)|añadir|cre(a|á)|crear|' +
61
+ 'arregl(a|á)|arreglar|cambi(a|á)|cambiar|migr(a|á)|migrar|' +
62
+ 'corrige|corregir|correg(í|ir)|constru(ye|í)|construir|modific(a|á)|modificar|' +
63
+ 'refactoriz(a|á)|refactorizar|elimin(a|á)|eliminar|reescrib(e|í)|reescribir|desplieg(a|á)|desplegar|' +
64
+ 'escrib(í|e)|escribir|implement\\w*|writ(?:e|ing)|creat\\w*|fix\\w*|build\\w*|refactor\\w*|migrat\\w*|' +
65
+ 'add\\w*|remov\\w*|delet\\w*|modify|modifies|modifying|rewrit\\w*',
66
+ );
67
+
68
+ /** Declared LEVEL near the word "level"/"classification" in Spanish or English, matching
69
+ * the plugin-wide convention (see risk-level.mjs). */
70
+ const DEMANDING_LEVEL_PATTERN =
71
+ /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(STANDARD|HIGH-RISK)\b/iu;
72
+ const EXEMPT_LEVEL_PATTERN =
73
+ /(nivel|level|clasificaci[oó]n|classification)[^\n]{0,25}?\b(QUESTION|MICRO)\b/iu;
74
+ const HIGH_RISK_PATTERN =
75
+ /(nivel|level|clasificaci[]n|classification)[^\n]{0,25}?\bHIGH-RISK\b/iu;
76
+
77
+ const HARNESS_PATTERN =
78
+ /(\.claude[\\/]|hooks[\\/]|plugins[\\/]gates|settings\.json|[\\/]agents[\\/]\w|[\\/]rules[\\/]\w|[\\/]skills[\\/]\w)/i;
79
+
80
+ // Several small patterns instead of one long alternation: a single big regex here
81
+ // tripped the linter's regex-complexity limit. Any one matching counts as declared.
82
+ const REVIEWER_NAME_PATTERN =
83
+ /\b(reviewer|revisor|separate review|revisi[oó]n separada|contraste|auditor)\b/i;
84
+ const REVIEWER_LABEL_PATTERN = /(review|revisi[oó]n)\s*:/i;
85
+ const REVIEWER_ACTION_EN_PATTERN =
86
+ /\breview\s+(?:the\s+)?(?:diff|result|work)\b/i;
87
+ const REVIEWER_ACTION_ES_PATTERN =
88
+ /\brevisa\s+(?:el\s+)?(?:diff|resultado|trabajo)\b/i;
89
+ const REVIEWER_CODE_REVIEW_PATTERN = /code.review/i;
90
+
91
+ function reviewerStageDeclared(prompt) {
92
+ return (
93
+ REVIEWER_NAME_PATTERN.test(prompt) ||
94
+ REVIEWER_LABEL_PATTERN.test(prompt) ||
95
+ REVIEWER_ACTION_EN_PATTERN.test(prompt) ||
96
+ REVIEWER_ACTION_ES_PATTERN.test(prompt) ||
97
+ REVIEWER_CODE_REVIEW_PATTERN.test(prompt)
98
+ );
99
+ }
100
+
101
+ const DEFINITION_PATTERN =
102
+ /\b(scout|reconnaissance|reconocimiento|prior exploration|explora(?:ci[oó]n)? previa|verified context|contexto verificado|asserts\.md|brief\.md)\b/i;
103
+ const WRITING_PATTERN =
104
+ /\b(test|tests|prueba|pruebas|vitest|jest|playwright|typecheck|lint|verification|verificaci[oó]n|verification criteria)\b/i;
105
+
106
+ /** The three pipeline stages this gate requires be declared, in this order. */
107
+ const REQUIRED_STAGES = [
108
+ {
109
+ id: 'definition',
110
+ isDeclared: (prompt) => DEFINITION_PATTERN.test(prompt),
111
+ missing:
112
+ 'DEFINITION: does not declare where the context came from (a prior scout, read-only exploration, or an already-written asserts/brief).',
113
+ line: 'Verified context: <path to the cited asserts/brief> — or: a prior scout ran, findings at <path>.',
114
+ },
115
+ {
116
+ id: 'writing',
117
+ isDeclared: (prompt) => WRITING_PATTERN.test(prompt),
118
+ missing:
119
+ 'WRITING: does not declare what is verified nor with what command. A builder does not validate its own work without a criterion written beforehand.',
120
+ line: 'Verification: <exact command> and what is expected. Anything that cannot run is reported OMITTED, never PASS.',
121
+ },
122
+ {
123
+ id: 'reviewer',
124
+ isDeclared: reviewerStageDeclared,
125
+ missing:
126
+ 'REVIEWER: does not declare who reviews the result. Whoever implements does not validate their own work.',
127
+ line: 'Review: the coordinator reviews the diff and the evidence before closing — or: a read-only `revision`/`contraste` agent reviews it.',
128
+ },
129
+ ];
130
+
131
+ function isExempt(toolInput, prompt, builderSubagents, exemptSubagents) {
132
+ const subagentType = String(
133
+ toolInput.subagent_type ?? toolInput.subagentType ?? '',
134
+ ).toLowerCase();
135
+ if (exemptSubagents.includes(subagentType)) return true;
136
+ // An unknown type is not assumed to be a builder: the safe side here is not to
137
+ // invent requirements for agents this gate cannot classify.
138
+ if (subagentType && !builderSubagents.includes(subagentType)) return true;
139
+ if (EXEMPT_LEVEL_PATTERN.test(prompt)) return true;
140
+ if (!DEMANDING_LEVEL_PATTERN.test(prompt)) return true;
141
+ if (!IMPLEMENTATION_VERBS.test(prompt)) return true;
142
+ if (HARNESS_PATTERN.test(prompt)) return true;
143
+ return false;
144
+ }
145
+
146
+ runGate(
147
+ {
148
+ id: GATE_ID,
149
+ configKey: CONFIG_KEY,
150
+ enabledByDefault: false,
151
+ defaultParams: {
152
+ builderSubagents: DEFAULT_BUILDER_SUBAGENTS,
153
+ exemptSubagents: DEFAULT_EXEMPT_SUBAGENTS,
154
+ },
155
+ },
156
+ ({ toolName, toolInput, parameters }) => {
157
+ if (!toolInGroups(toolName, ['delegation'])) return;
158
+
159
+ const prompt = delegationPromptOf(toolInput);
160
+ if (!prompt.trim()) return;
161
+
162
+ const builderSubagents =
163
+ parameters.builderSubagents ?? DEFAULT_BUILDER_SUBAGENTS;
164
+ const exemptSubagents =
165
+ parameters.exemptSubagents ?? DEFAULT_EXEMPT_SUBAGENTS;
166
+ if (isExempt(toolInput, prompt, builderSubagents, exemptSubagents)) return;
167
+
168
+ const missingStages = REQUIRED_STAGES.filter(
169
+ (stage) => !stage.isDeclared(prompt),
170
+ );
171
+ if (missingStages.length === 0) return;
172
+
173
+ const isHighRisk = HIGH_RISK_PATTERN.test(prompt);
174
+ const details = missingStages.map((stage) => stage.missing).join(' ');
175
+ const lines = missingStages.map((stage) => ` ${stage.line}`).join('\n');
176
+
177
+ deny(
178
+ GATE_ID,
179
+ `This delegation is going to build code but skips ${missingStages.length} pipeline ` +
180
+ `stage(s). ${details} ${
181
+ isHighRisk
182
+ ? 'In HIGH-RISK a separate reviewer is not negotiable. '
183
+ : ''
184
+ }PASTE THESE LINES INTO THE PROMPT AND RELAUNCH:\n${lines}`,
185
+ );
186
+ },
187
+ );