@devrik-tools/claude-gates 0.1.2 → 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 (104) 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 -45
  25. package/plugins/gates/hooks/gates/bash-commands/bash-commands.edge.test.mjs +165 -0
  26. package/plugins/gates/hooks/gates/bash-commands/index.mjs +285 -268
  27. package/plugins/gates/hooks/gates/brief-before-delegate/brief-before-delegate.edge.test.mjs +151 -0
  28. package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +265 -177
  29. package/plugins/gates/hooks/gates/circuit-breaker/circuit-breaker.edge.test.mjs +207 -0
  30. package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +501 -456
  31. package/plugins/gates/hooks/gates/circuit-breaker/test.mjs +161 -143
  32. package/plugins/gates/hooks/gates/dependency-skills/dependency-skills.edge.test.mjs +69 -0
  33. package/plugins/gates/hooks/gates/dependency-skills/index.mjs +126 -118
  34. package/plugins/gates/hooks/gates/diagnosis-before-patch/diagnosis-before-patch.edge.test.mjs +68 -0
  35. package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +43 -49
  36. package/plugins/gates/hooks/gates/feature-catalog/edge.edge.test.mjs +83 -0
  37. package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -100
  38. package/plugins/gates/hooks/gates/force-parallel/index.mjs +119 -0
  39. package/plugins/gates/hooks/gates/force-parallel/test.mjs +88 -0
  40. package/plugins/gates/hooks/gates/forge-flow/forge-flow.edge.test.mjs +155 -0
  41. package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -112
  42. package/plugins/gates/hooks/gates/implementation-pipeline/edge.edge.test.mjs +66 -0
  43. package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -186
  44. package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -238
  45. package/plugins/gates/hooks/gates/intent-flow/intent-flow.edge.test.mjs +96 -0
  46. package/plugins/gates/hooks/gates/intent-flow/test.mjs +165 -136
  47. package/plugins/gates/hooks/gates/lint-commit/index.mjs +149 -0
  48. package/plugins/gates/hooks/gates/lint-commit/test.mjs +85 -0
  49. package/plugins/gates/hooks/gates/mandatory-flow/edge.edge.test.mjs +100 -0
  50. package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -166
  51. package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +79 -85
  52. package/plugins/gates/hooks/gates/neutral-spanish/neutral-spanish.edge.test.mjs +80 -0
  53. package/plugins/gates/hooks/gates/never-assume/index.mjs +58 -55
  54. package/plugins/gates/hooks/gates/never-assume/never-assume.edge.test.mjs +71 -0
  55. package/plugins/gates/hooks/gates/never-assume/test.mjs +95 -78
  56. package/plugins/gates/hooks/gates/no-blocking/index.mjs +148 -142
  57. package/plugins/gates/hooks/gates/no-blocking/no-blocking.edge.test.mjs +109 -0
  58. package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +123 -120
  59. package/plugins/gates/hooks/gates/no-memory-dependency/no-memory-dependency.edge.test.mjs +83 -0
  60. package/plugins/gates/hooks/gates/no-reconfirm/no-reconfirm.edge.test.mjs +91 -0
  61. package/plugins/gates/hooks/gates/protected-paths/index.mjs +144 -147
  62. package/plugins/gates/hooks/gates/protected-paths/protected-paths.edge.test.mjs +126 -0
  63. package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +89 -64
  64. package/plugins/gates/hooks/gates/recurrence-lock/recurrence-lock.edge.test.mjs +102 -0
  65. package/plugins/gates/hooks/gates/reuse-before-build/edge.edge.test.mjs +94 -0
  66. package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +159 -150
  67. package/plugins/gates/hooks/gates/risk-level/index.mjs +263 -203
  68. package/plugins/gates/hooks/gates/risk-level/risk-level.edge.test.mjs +116 -0
  69. package/plugins/gates/hooks/gates/risk-level/test.mjs +153 -125
  70. package/plugins/gates/hooks/gates/root-cause-first/index.mjs +56 -50
  71. package/plugins/gates/hooks/gates/root-cause-first/root-cause-first.edge.test.mjs +58 -0
  72. package/plugins/gates/hooks/gates/root-whitelist/index.mjs +116 -111
  73. package/plugins/gates/hooks/gates/root-whitelist/root-whitelist.edge.test.mjs +97 -0
  74. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +184 -99
  75. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/rule-skill-autodiscovery.edge.test.mjs +126 -0
  76. package/plugins/gates/hooks/gates/sdd-specs/edge.edge.test.mjs +130 -0
  77. package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -251
  78. package/plugins/gates/hooks/gates/stop-pending/index.mjs +159 -0
  79. package/plugins/gates/hooks/gates/stop-pending/test.mjs +114 -0
  80. package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +101 -101
  81. package/plugins/gates/hooks/gates/test-after-implementation/test-after-implementation.edge.test.mjs +72 -0
  82. package/plugins/gates/hooks/gates/test-matrix/edge.edge.test.mjs +63 -0
  83. package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -181
  84. package/plugins/gates/hooks/gates/test-matrix/test.mjs +114 -87
  85. package/plugins/gates/hooks/gates/tool-map/edge.edge.test.mjs +62 -0
  86. package/plugins/gates/hooks/gates/tool-map/index.mjs +143 -140
  87. package/plugins/gates/hooks/hooks.json +346 -286
  88. package/plugins/gates/hooks/lib/__tests__/config.test.mjs +173 -154
  89. package/plugins/gates/hooks/lib/__tests__/hook-io.test.mjs +210 -154
  90. package/plugins/gates/hooks/lib/__tests__/signals.test.mjs +114 -0
  91. package/plugins/gates/hooks/lib/config.mjs +172 -165
  92. package/plugins/gates/hooks/lib/hook-io-config.edge.test.mjs +189 -0
  93. package/plugins/gates/hooks/lib/hook-io.mjs +318 -208
  94. package/plugins/gates/hooks/lib/signals.mjs +127 -0
  95. package/plugins/gates/hooks/wiring-check.mjs +227 -0
  96. package/plugins/tasks/.claude-plugin/plugin.json +9 -0
  97. package/plugins/tasks/hooks/__tests__/register-requests.test.mjs +100 -0
  98. package/plugins/tasks/hooks/__tests__/session-tasks.test.mjs +95 -0
  99. package/plugins/tasks/hooks/hooks.json +26 -0
  100. package/plugins/tasks/hooks/lib/__tests__/task-store.test.mjs +180 -132
  101. package/plugins/tasks/hooks/lib/task-store.mjs +197 -159
  102. package/plugins/tasks/hooks/register-requests.mjs +141 -108
  103. package/plugins/tasks/hooks/session-tasks.mjs +108 -0
  104. package/registry.json +793 -686
@@ -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
+ );