@devrik-tools/claude-gates 0.6.0 → 0.7.1

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 (48) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.es.md +25 -5
  3. package/README.md +23 -5
  4. package/cli/smoke-fixtures.json +14 -0
  5. package/package.json +1 -1
  6. package/plugins/gates/.claude-plugin/plugin.json +1 -1
  7. package/plugins/gates/hooks/gates/atomic-commit/index.mjs +2 -2
  8. package/plugins/gates/hooks/gates/audit-before-build/index.mjs +2 -2
  9. package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +1 -1
  10. package/plugins/gates/hooks/gates/bash-commands/index.mjs +3 -3
  11. package/plugins/gates/hooks/gates/block-remote-publish/index.mjs +2 -2
  12. package/plugins/gates/hooks/gates/brief-approved/index.mjs +216 -0
  13. package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +3 -3
  14. package/plugins/gates/hooks/gates/capability-map/index.mjs +366 -42
  15. package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +1 -1
  16. package/plugins/gates/hooks/gates/dependency-skills/index.mjs +1 -1
  17. package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +1 -1
  18. package/plugins/gates/hooks/gates/feature-catalog/index.mjs +2 -2
  19. package/plugins/gates/hooks/gates/force-parallel/index.mjs +1 -1
  20. package/plugins/gates/hooks/gates/forge-flow/index.mjs +2 -2
  21. package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +1 -1
  22. package/plugins/gates/hooks/gates/intent-flow/index.mjs +3 -3
  23. package/plugins/gates/hooks/gates/lint-commit/index.mjs +2 -2
  24. package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +3 -3
  25. package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +1 -1
  26. package/plugins/gates/hooks/gates/never-assume/index.mjs +1 -1
  27. package/plugins/gates/hooks/gates/no-blocking/index.mjs +1 -1
  28. package/plugins/gates/hooks/gates/no-coauthor/index.mjs +1 -1
  29. package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +1 -1
  30. package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +1 -1
  31. package/plugins/gates/hooks/gates/no-reconfirm/index.mjs +1 -1
  32. package/plugins/gates/hooks/gates/protected-paths/index.mjs +2 -2
  33. package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +1 -1
  34. package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +2 -2
  35. package/plugins/gates/hooks/gates/risk-level/index.mjs +3 -3
  36. package/plugins/gates/hooks/gates/root-cause-first/index.mjs +1 -1
  37. package/plugins/gates/hooks/gates/root-whitelist/index.mjs +1 -1
  38. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +2 -2
  39. package/plugins/gates/hooks/gates/sdd-specs/index.mjs +3 -3
  40. package/plugins/gates/hooks/gates/staged-lint/index.mjs +2 -2
  41. package/plugins/gates/hooks/gates/stop-pending/index.mjs +1 -2
  42. package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +1 -1
  43. package/plugins/gates/hooks/gates/test-matrix/index.mjs +1 -1
  44. package/plugins/gates/hooks/gates/tool-map/index.mjs +1 -1
  45. package/plugins/gates/hooks/hooks.json +10 -0
  46. package/plugins/gates/hooks/lib/hook-io.mjs +1 -1
  47. package/plugins/tasks/.claude-plugin/plugin.json +1 -1
  48. package/registry.json +22 -1
@@ -175,7 +175,7 @@ runGate(
175
175
  const lines = missingStages.map((stage) => ` ${stage.line}`).join('\n');
176
176
 
177
177
  deny(
178
- GATE_ID,
178
+ CONFIG_KEY,
179
179
  `This delegation is going to build code but skips ${missingStages.length} pipeline ` +
180
180
  `stage(s). ${details} ${
181
181
  isHighRisk
@@ -181,7 +181,7 @@ function missingSignals(prompt) {
181
181
 
182
182
  function denyUnresolvedUnknown() {
183
183
  deny(
184
- GATE_ID,
184
+ CONFIG_KEY,
185
185
  'This request touches a high-impact signal (money/auth/persisted data/public contract/' +
186
186
  'irreversible/production) AND declares an unresolved UNKNOWN. Do not relaunch assuming an ' +
187
187
  'answer — ask the user what to decide about that UNKNOWN before delegating again.',
@@ -192,7 +192,7 @@ function denyTooShortForScopeList(prompt) {
192
192
  const excerpt = prompt.slice(0, PROMPT_EXCERPT_LENGTH);
193
193
  const ellipsis = prompt.length > PROMPT_EXCERPT_LENGTH ? '…' : '';
194
194
  deny(
195
- GATE_ID,
195
+ CONFIG_KEY,
196
196
  `This implementation request ("${excerpt}${ellipsis}") does not carry a scope list: missing IN ` +
197
197
  'SCOPE, OUT OF SCOPE and EDGE CASES. Add it to the prompt before relaunching.',
198
198
  );
@@ -200,7 +200,7 @@ function denyTooShortForScopeList(prompt) {
200
200
 
201
201
  function denyMissingSignals(missing) {
202
202
  deny(
203
- GATE_ID,
203
+ CONFIG_KEY,
204
204
  `This implementation request does not declare, in recognizable form: ${missing.join('; ')}. ` +
205
205
  'Add what is missing to the prompt before relaunching this delegation.',
206
206
  );
@@ -133,7 +133,7 @@ runGate(
133
133
  // timeout kill). A commit gate that cannot evaluate must not silently permit — deny
134
134
  // with the spawn error so the user can fix the environment, not guess.
135
135
  deny(
136
- GATE_ID,
136
+ CONFIG_KEY,
137
137
  `Could not run the lint command ("${lintCommand}"): ${result.error.message}. Fix ` +
138
138
  'the lint setup or set lintCommand/blockCommitWithFailingLint in .ai/config.json.',
139
139
  );
@@ -143,7 +143,7 @@ runGate(
143
143
  const combinedOutput =
144
144
  `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
145
145
  deny(
146
- GATE_ID,
146
+ CONFIG_KEY,
147
147
  `Lint failed (exit ${result.status}) — commit blocked until it passes:\n` +
148
148
  `${tailLines(combinedOutput, OUTPUT_TAIL_LINES)}`,
149
149
  );
@@ -125,7 +125,7 @@ function readSlug(pointerPath) {
125
125
  function checkLiveTask(pointerPath) {
126
126
  if (!existsSync(pointerPath)) {
127
127
  deny(
128
- GATE_ID,
128
+ CONFIG_KEY,
129
129
  `This delegation is going to implement, but there is no live task: ${pointerPath} ` +
130
130
  'does not exist. Start a task pointing to it before delegating, or declare ' +
131
131
  'LEVEL: QUESTION/MICRO if this is not implementation.',
@@ -135,7 +135,7 @@ function checkLiveTask(pointerPath) {
135
135
  const slug = readSlug(pointerPath);
136
136
  if (!slug) {
137
137
  deny(
138
- GATE_ID,
138
+ CONFIG_KEY,
139
139
  `${pointerPath} exists but is empty. An active-task pointer with no slug is not ` +
140
140
  'a live task. Write the task slug into it before delegating.',
141
141
  );
@@ -144,7 +144,7 @@ function checkLiveTask(pointerPath) {
144
144
  const taskDirectory = join(process.cwd(), '.ai', 'pipeline', slug);
145
145
  if (!hasTaskContract(taskDirectory)) {
146
146
  deny(
147
- GATE_ID,
147
+ CONFIG_KEY,
148
148
  `The active task '${slug}' has no contract on disk: none of ` +
149
149
  `${TASK_CONTRACT_FILES.join(', ')} exists under ${taskDirectory}. ` +
150
150
  'Write the contract before implementing.',
@@ -94,7 +94,7 @@ runGate(
94
94
 
95
95
  const unique = [...new Set(hits)].slice(0, MAX_REPORTED_MARKERS);
96
96
  deny(
97
- GATE_ID,
97
+ CONFIG_KEY,
98
98
  `Text being written contains regional Spanish markers: ${unique.join(', ')}. ` +
99
99
  'Rewrite in neutral Spanish before writing. If the regional wording is intentional ' +
100
100
  `(a literal quote, a test fixture, a captured log, a data sample), add the marker ` +
@@ -49,7 +49,7 @@ runGate(
49
49
  if (hits.length === 0) return;
50
50
 
51
51
  warn(
52
- GATE_ID,
52
+ CONFIG_KEY,
53
53
  `Content contains conjecture phrasing without stated verification: ${hits.join(', ')}. Verify before asserting instead of assuming.`,
54
54
  );
55
55
  },
@@ -153,7 +153,7 @@ runGate(
153
153
  for (const [source, reason] of blockingPairs) {
154
154
  if (compile(source).test(command)) {
155
155
  deny(
156
- GATE_ID,
156
+ CONFIG_KEY,
157
157
  `${reason} If this wait is genuinely justified, add "${marker} <concrete reason>" ` +
158
158
  'to the command and try again. A declared wait is a decision; a silent one is an oversight.',
159
159
  );
@@ -116,7 +116,7 @@ runGate(
116
116
  if (!match) return;
117
117
 
118
118
  deny(
119
- GATE_ID,
119
+ CONFIG_KEY,
120
120
  `This commit message carries an AI/agent attribution trailer (matched "${match}"). ` +
121
121
  'Remove it — no Co-Authored-By, no "Generated with", no session trailer. If this ' +
122
122
  `commit legitimately must keep a co-author (e.g. importing external history), add ` +
@@ -173,7 +173,7 @@ runGate(
173
173
  if (!offender) return;
174
174
 
175
175
  deny(
176
- GATE_ID,
176
+ CONFIG_KEY,
177
177
  `This write silences the linter/type-checker instead of fixing the code: "${offender}". ` +
178
178
  'Fix the underlying issue rather than turning the check off. If this is a genuine, ' +
179
179
  `documented false positive, put "${escapeHatch} <reason>" on the same line so the ` +
@@ -128,7 +128,7 @@ runGate(
128
128
 
129
129
  const quotedPhrases = phrases.map((phrase) => `"${phrase}"`).join(', ');
130
130
  deny(
131
- GATE_ID,
131
+ CONFIG_KEY,
132
132
  `This delegation depends on the subagent remembering something (${quotedPhrases}), but a ` +
133
133
  'fresh subagent has none of this conversation. Put the data IN the prompt, in a file it ' +
134
134
  'reads, a flag, or an already-persisted decision. If the phrase directs the subagent’s ' +
@@ -197,6 +197,6 @@ runGate(
197
197
  parameters.overlapThreshold,
198
198
  );
199
199
  if (notices.length === 0) return;
200
- warn(GATE_ID, notices.join('\n\n'));
200
+ warn(CONFIG_KEY, notices.join('\n\n'));
201
201
  },
202
202
  );
@@ -87,7 +87,7 @@ function checkWrite(toolInput, protectedPaths) {
87
87
  const target = writtenPathOf(toolInput);
88
88
  if (target && isProtectedPath(target, protectedPaths)) {
89
89
  deny(
90
- GATE_ID,
90
+ CONFIG_KEY,
91
91
  `Writing to '${target}' is not allowed: it matches a protected path (${protectedPaths.join(', ')}).`,
92
92
  );
93
93
  }
@@ -110,7 +110,7 @@ function checkShellCommand(toolInput, protectedPaths, mutatingCommands) {
110
110
 
111
111
  if (targetsProtected) {
112
112
  deny(
113
- GATE_ID,
113
+ CONFIG_KEY,
114
114
  `This command targets a protected path (${protectedPaths.join(', ')}) with a mutating operation. ` +
115
115
  'Reading (echo/cat/grep) is fine; modifying it is not.',
116
116
  );
@@ -84,7 +84,7 @@ runGate(
84
84
 
85
85
  const names = openRecurrences.map((entry) => entry.class).join(', ');
86
86
  deny(
87
- GATE_ID,
87
+ CONFIG_KEY,
88
88
  `Registered recurring issue classes are still open and at/above threshold: ${names}. Resolve or close them before proceeding.`,
89
89
  );
90
90
  },
@@ -224,7 +224,7 @@ function checkDelegation(toolInput, cwd, parameters) {
224
224
  const prompt = delegationPromptOf(toolInput);
225
225
  if (!isBuildIntent(prompt)) return;
226
226
  if (buildIsCleared(prompt, cwd, parameters, null)) return;
227
- deny(GATE_ID, DENY_MESSAGE);
227
+ deny(CONFIG_KEY, DENY_MESSAGE);
228
228
  }
229
229
 
230
230
  /** Write: block a new tool-like file that is not cleared by content, map, or an installed dep. */
@@ -237,7 +237,7 @@ function checkWrite(toolInput, cwd, parameters) {
237
237
  const content = writtenContentOf(toolInput);
238
238
  const toolBaseName = basename(filePath).replace(/\.[^.]+$/, '');
239
239
  if (buildIsCleared(content, cwd, parameters, toolBaseName)) return;
240
- deny(GATE_ID, DENY_MESSAGE);
240
+ deny(CONFIG_KEY, DENY_MESSAGE);
241
241
  }
242
242
 
243
243
  runGate(
@@ -191,7 +191,7 @@ function denyNoLevelDeclared(prompt) {
191
191
  const excerpt = prompt.slice(0, PROMPT_EXCERPT_LENGTH);
192
192
  const ellipsis = prompt.length > PROMPT_EXCERPT_LENGTH ? '…' : '';
193
193
  deny(
194
- GATE_ID,
194
+ CONFIG_KEY,
195
195
  `This implementation delegation ("${excerpt}${ellipsis}") does not declare its risk LEVEL. Add a ` +
196
196
  'line such as "LEVEL: STANDARD" (or QUESTION/MICRO/HIGH-RISK, whichever fits) before relaunching ' +
197
197
  'this delegation.',
@@ -200,7 +200,7 @@ function denyNoLevelDeclared(prompt) {
200
200
 
201
201
  function denyAmbiguousLevel(levels) {
202
202
  deny(
203
- GATE_ID,
203
+ CONFIG_KEY,
204
204
  `This delegation declares multiple different risk levels (${[...new Set(levels)].join(', ')}) — ` +
205
205
  'it is not clear which one governs this task. Declare a single, unambiguous LEVEL for this ' +
206
206
  'delegation (remove any decoy/reference mention of a different level) before relaunching.',
@@ -209,7 +209,7 @@ function denyAmbiguousLevel(levels) {
209
209
 
210
210
  function denyLevelContradictsSignal(level, signal) {
211
211
  deny(
212
- GATE_ID,
212
+ CONFIG_KEY,
213
213
  `This delegation declares LEVEL: ${level}, but the request touches the risk signal "${signal}" near ` +
214
214
  'an implementation verb (not quoted, not the topic of a documentary deliverable) — that requires ' +
215
215
  `LEVEL: HIGH-RISK, not ${level}. Raise the declaration to HIGH-RISK before relaunching.`,
@@ -43,7 +43,7 @@ runGate(
43
43
  if (!matched) return;
44
44
 
45
45
  deny(
46
- GATE_ID,
46
+ CONFIG_KEY,
47
47
  `Content matches a patch-without-diagnosis marker (${matched.source}). Identify and fix the root cause before patching; do not defer with a "fix later" marker.`,
48
48
  );
49
49
  },
@@ -205,7 +205,7 @@ runGate(
205
205
  filesWhitelist,
206
206
  foldersWhitelist,
207
207
  );
208
- if (reason) deny(GATE_ID, reason);
208
+ if (reason) deny(CONFIG_KEY, reason);
209
209
  }
210
210
  },
211
211
  );
@@ -163,7 +163,7 @@ runGate(
163
163
  if (configsTampered(before)) {
164
164
  revertConfigs(before);
165
165
  deny(
166
- GATE_ID,
166
+ CONFIG_KEY,
167
167
  `Discovered rule/skill script ${relative(projectRoot, script)} attempted to modify gate security configuration (.ai/config.json or the global config). The change was reverted and the action is blocked.`,
168
168
  );
169
169
  return;
@@ -171,7 +171,7 @@ runGate(
171
171
 
172
172
  if (failed) {
173
173
  deny(
174
- GATE_ID,
174
+ CONFIG_KEY,
175
175
  `Sub-gate failed: ${relative(projectRoot, script)}. Fix it before continuing.`,
176
176
  );
177
177
  return;
@@ -153,7 +153,7 @@ function denyIfAdvancedFeatureLacksContract(features, treeRoot) {
153
153
  if (!feature?.name || !ADVANCED_STATUSES.has(feature.status)) continue;
154
154
  if (contractExistsFor(treeRoot, feature.name)) continue;
155
155
  deny(
156
- GATE_ID,
156
+ CONFIG_KEY,
157
157
  `Feature '${feature.name}' is set to '${feature.status}' but has no non-empty ` +
158
158
  `contract (${CONTRACT_FILES.join(', ')}) under the discovered contract tree.`,
159
159
  );
@@ -171,7 +171,7 @@ function checkCatalogWrite(toolInput, catalogPath, treeRoot) {
171
171
  // still targets the catalog is suspicious on its own -- a spec_ready/done transition
172
172
  // hidden behind a malformed payload must not be silently allowed through.
173
173
  deny(
174
- GATE_ID,
174
+ CONFIG_KEY,
175
175
  `The write to ${CATALOG_FILE_NAME} does not parse as JSON. A catalog write that ` +
176
176
  'cannot be verified for the spec-contract invariant is not allowed; fix the JSON ' +
177
177
  'or write valid content.',
@@ -213,7 +213,7 @@ function checkDelegation(toolInput, treeRoot, exemptSubagents) {
213
213
  );
214
214
  if (missing.length === citedFeatures.length) {
215
215
  deny(
216
- GATE_ID,
216
+ CONFIG_KEY,
217
217
  `This implementation delegation cites feature(s) [${missing.join(', ')}] with no ` +
218
218
  `non-empty contract (${CONTRACT_FILES.join(', ')}) on disk. Write the contract ` +
219
219
  'before implementing.',
@@ -133,7 +133,7 @@ function runLintOverStaged(lintCommand, files, cwd, timeoutMs) {
133
133
  function denyIfLintFailed(result, lintCommand, escapeHatch) {
134
134
  if (result.error) {
135
135
  deny(
136
- GATE_ID,
136
+ CONFIG_KEY,
137
137
  `Could not run the lint command ("${lintCommand}") over the staged files: ` +
138
138
  `${result.error.message}. Fix the lint setup or set lintCommand/` +
139
139
  `${CONFIG_KEY} in .ai/config.json.`,
@@ -144,7 +144,7 @@ function denyIfLintFailed(result, lintCommand, escapeHatch) {
144
144
  `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
145
145
  const outputTail = tailLines(combinedOutput, OUTPUT_TAIL_LINES);
146
146
  deny(
147
- GATE_ID,
147
+ CONFIG_KEY,
148
148
  `The files you staged fail lint — commit blocked until your own changes are clean ` +
149
149
  `(the rest of the repo is not checked). Fix them, or add "${escapeHatch}" to the ` +
150
150
  `commit command for a deliberate exception:\n${outputTail}`,
@@ -50,7 +50,6 @@ import { dirname, join } from 'node:path';
50
50
  import { isGateEnabled, gateParameters } from '../../lib/config.mjs';
51
51
  import { readHookPayload } from '../../lib/hook-io.mjs';
52
52
 
53
- const GATE_ID = 'stop-pending';
54
53
  const CONFIG_KEY = 'blockStopWithPendingTasks';
55
54
 
56
55
  const PROJECT_ROOT_MARKERS = ['.git', '.ai'];
@@ -153,7 +152,7 @@ function main() {
153
152
  if (blockingTasks.length === 0) return allow();
154
153
 
155
154
  return block(
156
- `[${GATE_ID}] There are ${blockingTasks.length} pending task(s) still active for this ` +
155
+ `[${CONFIG_KEY}] There are ${blockingTasks.length} pending task(s) still active for this ` +
157
156
  `project:\n${describeTasks(blockingTasks)}\n` +
158
157
  'Close each before ending the turn: `task close <id> --evidence "..."` when done, ' +
159
158
  'or `task abandon <id> --reason "..."` when it will not be finished. A task left ' +
@@ -107,7 +107,7 @@ runGate(
107
107
  if (!pairedImplementationChanged) return;
108
108
 
109
109
  deny(
110
- GATE_ID,
110
+ CONFIG_KEY,
111
111
  `Creating test ${testFileName} after its paired implementation was already modified ` +
112
112
  '(uncommitted). Write the test before or alongside the implementation, not after — a ' +
113
113
  'test that passes the first time it runs proves nothing about the change. If this is a ' +
@@ -175,7 +175,7 @@ runGate(
175
175
 
176
176
  const lines = missing.map((entry) => ` ${entry.line}`).join('\n');
177
177
  deny(
178
- GATE_ID,
178
+ CONFIG_KEY,
179
179
  `Missing ${missing.length} test type(s) the requirement makes mandatory. Test types ` +
180
180
  'are chosen ACCORDING TO THE REQUIREMENT: E2E if it touches money/auth/persistence, ' +
181
181
  'visual/QA if it touches UI, and always unit or mutation. ' +
@@ -129,7 +129,7 @@ function recordTool(mapPath, normalizedPath, auditLine, toolMapFile) {
129
129
  );
130
130
  } catch (error) {
131
131
  warn(
132
- GATE_ID,
132
+ CONFIG_KEY,
133
133
  `Could not record this tool in ${toolMapFile}: ${error?.message ?? error}. ` +
134
134
  'The build proceeds, but the discovery was not remembered.',
135
135
  );
@@ -121,6 +121,16 @@
121
121
  }
122
122
  ]
123
123
  },
124
+ {
125
+ "matcher": "Agent|Task|invoke_subagent|mcp__.*",
126
+ "hooks": [
127
+ {
128
+ "type": "command",
129
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gates/brief-approved/index.mjs\"",
130
+ "timeout": 30
131
+ }
132
+ ]
133
+ },
124
134
  {
125
135
  "matcher": "Agent|Task|invoke_subagent|mcp__.*",
126
136
  "hooks": [
@@ -359,7 +359,7 @@ export async function runGate(gate, check) {
359
359
  });
360
360
  } catch (error) {
361
361
  deny(
362
- gate.id,
362
+ gate.configKey,
363
363
  `The gate failed to evaluate and blocks the action for safety: ${error?.message ?? error}.`,
364
364
  );
365
365
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tasks",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Deterministic task tracking for Claude Code: persists tasks the model registers via the CLI, reminds of open tasks on a message counter, and lists active tasks on session start.",
5
5
  "author": {
6
6
  "name": "Devrik"
package/registry.json CHANGED
@@ -293,6 +293,27 @@
293
293
  }
294
294
  ]
295
295
  },
296
+ {
297
+ "id": "brief-approved",
298
+ "configKey": "requireApprovedBriefBeforeImplementing",
299
+ "default": true,
300
+ "event": "PreToolUse",
301
+ "tools": ["delegation"],
302
+ "description": "Denies implementing a cited feature whose brief has no recorded user approval (status: approved + approval_quote frontmatter).",
303
+ "script": "gates/brief-approved/index.mjs",
304
+ "params": [
305
+ {
306
+ "name": "catalogLocations",
307
+ "type": "string[]",
308
+ "description": "Paths searched for the feature catalog."
309
+ },
310
+ {
311
+ "name": "exemptSubagents",
312
+ "type": "string[]",
313
+ "description": "Subagent types exempt from the approval requirement."
314
+ }
315
+ ]
316
+ },
296
317
  {
297
318
  "id": "implementation-pipeline",
298
319
  "configKey": "requireImplementationPipeline",
@@ -826,7 +847,7 @@
826
847
  {
827
848
  "id": "capability-map",
828
849
  "configKey": "injectCapabilityMap",
829
- "default": false,
850
+ "default": true,
830
851
  "event": "UserPromptSubmit",
831
852
  "tools": [],
832
853
  "description": "Injects the current catalog of the project's AI capabilities (skills, agents/subagents, commands) as compact context on every message, grouped by kind, read fresh from disk. Also persists it to .ai/capability-map.json like the tool map. Autosynced: it lists the directories, never a hardcoded copy. Never blocks.",