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