@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.es.md +39 -4
- package/README.md +34 -5
- package/cli/config.mjs +126 -124
- package/cli/init.mjs +303 -276
- package/cli/install.mjs +281 -175
- package/cli/materialize.mjs +103 -102
- package/cli/registry.mjs +139 -136
- package/cli/smoke-fixtures.json +65 -0
- package/cli/task.mjs +140 -140
- package/package.json +1 -1
- package/plugins/gates/.claude-plugin/plugin.json +1 -1
- package/plugins/gates/hooks/ask-adoption.mjs +147 -147
- package/plugins/gates/hooks/doctor.mjs +207 -207
- package/plugins/gates/hooks/gates/atomic-commit/index.mjs +229 -0
- package/plugins/gates/hooks/gates/audit-before-build/index.mjs +110 -88
- package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +50 -50
- package/plugins/gates/hooks/gates/bash-commands/index.mjs +215 -215
- package/plugins/gates/hooks/gates/brief-approved/index.mjs +216 -0
- package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +269 -265
- package/plugins/gates/hooks/gates/capability-map/index.mjs +701 -0
- package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +527 -501
- package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +48 -43
- package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -83
- package/plugins/gates/hooks/gates/force-parallel/index.mjs +134 -119
- package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -134
- package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -187
- package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -260
- package/plugins/gates/hooks/gates/lint-commit/index.mjs +152 -149
- package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -180
- package/plugins/gates/hooks/gates/never-assume/index.mjs +59 -58
- package/plugins/gates/hooks/gates/no-blocking/index.mjs +163 -148
- package/plugins/gates/hooks/gates/no-coauthor/index.mjs +127 -0
- package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +183 -0
- package/plugins/gates/hooks/gates/protected-paths/index.mjs +149 -144
- package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +91 -89
- package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +263 -159
- package/plugins/gates/hooks/gates/risk-level/index.mjs +265 -263
- package/plugins/gates/hooks/gates/root-cause-first/index.mjs +57 -56
- package/plugins/gates/hooks/gates/root-whitelist/index.mjs +211 -131
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +181 -184
- package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -256
- package/plugins/gates/hooks/gates/staged-lint/index.mjs +187 -0
- package/plugins/gates/hooks/gates/stop-pending/index.mjs +169 -164
- package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -187
- package/plugins/gates/hooks/gates/tool-map/index.mjs +168 -143
- package/plugins/gates/hooks/hooks.json +61 -0
- package/plugins/gates/hooks/lib/config.mjs +179 -172
- package/plugins/gates/hooks/lib/hook-io.mjs +367 -357
- package/plugins/gates/hooks/lib/signals.mjs +172 -127
- package/plugins/gates/hooks/wiring-check.mjs +227 -227
- package/plugins/tasks/.claude-plugin/plugin.json +1 -1
- package/plugins/tasks/hooks/hooks.json +26 -26
- package/plugins/tasks/hooks/lib/task-store.mjs +217 -197
- package/plugins/tasks/hooks/register-requests.mjs +145 -145
- package/plugins/tasks/hooks/session-tasks.mjs +108 -108
- 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']))
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
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.
|