v3.17.0: hook modernization for CC 2.1.195 -- permission_mode awareness, 10K additionalContext bounding, post-task failure-detection hardening

This commit is contained in:
kochetkov-ma
2026-06-27 10:57:42 +01:00
parent 71ec857b4e
commit 18f0357ef6
19 changed files with 101 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "brewcode",
"version": "3.16.6",
"version": "3.17.0",
"description": "Brewcode - full-featured development platform for Claude Code: infinite focus tasks, prompt optimization, skill/agent creation, quorum reviews, rules management",
"author": {
"name": "Maksim Kochetkov",
+1 -1
View File
@@ -4,7 +4,7 @@
| Field | Value |
|-------|-------|
| Version | 3.16.6 |
| Version | 3.17.0 |
| Skills | 13 |
| Agents | 12 |
| Hooks | 9 |
+8 -1
View File
@@ -26,6 +26,12 @@
import { readStdin, output, getActiveMode } from './lib/utils.mjs';
// Cap text channels under the 2.1.174 10K disk-spill threshold (headroom 9000).
const TEXT_CHANNEL_CAP = 9000;
function capText(s, max = TEXT_CHANNEL_CAP) {
return (typeof s === 'string' && s.length > max) ? s.slice(0, max) + '\n...[truncated]' : s;
}
// --- Skill evaluation reminder ---
// SKILL_CHECK = always-on payload (every prompt). DEFAULT_MODE = light hint when no
@@ -81,6 +87,7 @@ async function main() {
}
// Effort-level prefix (CC 2.1.115+). Folded into injected context.
// NOTE: effort.level is NOT in HOOKS-REFERENCE.md (2.1.195). Presence-guarded existing read; do not expand to other hooks.
const effortLevel = input.effort?.level;
const effortPrefix = effortLevel === 'low' ? '[EFFORT: low | MODE: terse-light]\n' : '';
@@ -91,7 +98,7 @@ async function main() {
output({
hookSpecificOutput: {
hookEventName: 'UserPromptSubmit',
additionalContext: reminderText
additionalContext: capText(reminderText)
}
});
+1 -1
View File
@@ -26,7 +26,7 @@ async function main() {
session_id = input.session_id;
cwd = input.cwd || cwd;
const tool_input = input.tool_input;
const tool_result = input.tool_result;
const tool_result = input.tool_result ?? input.tool_response;
// Only process Task tool calls
if (!tool_input) {
+7 -1
View File
@@ -81,7 +81,11 @@ async function main() {
let updatedPrompt = tool_input.prompt || '';
let modified = false;
// permission_mode: DOC-VERIFIED common field (2.1.195). Presence-guarded; falsy when absent -> current behavior.
const planMode = input.permission_mode === 'plan';
// 0.0 Effort-level prefix (CC 2.1.115+). Idempotent: skip if already present.
// NOTE: effort.level is NOT in HOOKS-REFERENCE.md (2.1.195). Presence-guarded existing read; do not expand to other hooks.
const effortLevel = input.effort?.level;
if (effortLevel === 'low' && !updatedPrompt.includes('[EFFORT:')) {
updatedPrompt = `[EFFORT: low | MODE: terse-light]\n${updatedPrompt}`;
@@ -134,7 +138,9 @@ async function main() {
const config = loadConfig(cwd);
const lock = checkLock(cwd, session_id);
if (lock && lock.task_path) {
// In plan mode the parent is designing, not executing -> anti-pattern KNOWLEDGE is noise. Skip injection.
// Guard: planMode falsy when permission_mode absent or any non-'plan' value -> current injection behavior.
if (lock && lock.task_path && !planMode) {
const knowledgePath = getKnowledgePath(join(cwd, lock.task_path));
const entries = readKnowledge(knowledgePath);
+12 -3
View File
@@ -30,6 +30,12 @@ const VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const PLAN_FRESHNESS_MS = 60_000;
// Cap text channels under the 2.1.174 10K disk-spill threshold (headroom 9000).
const TEXT_CHANNEL_CAP = 9000;
function capText(s, max = TEXT_CHANNEL_CAP) {
return (typeof s === 'string' && s.length > max) ? s.slice(0, max) + '\n...[truncated]' : s;
}
/**
* Creates symlink .claude/plans/LATEST.md → ~/.claude/plans/<newest>.md
* Only if newest plan is < 60 seconds old (fresh from Plan Mode)
@@ -218,8 +224,10 @@ async function main() {
session_id = input.session_id;
cwd = input.cwd || cwd;
const source = input.source;
// permission_mode: DOC-VERIFIED common field (2.1.195). Presence-guarded use below.
const permMode = input.permission_mode;
log('info', '[session-start]', `Started: ${session_id?.slice(0, 8) || 'unknown'} (${source})`, cwd, session_id);
log('info', '[session-start]', `Started: ${session_id?.slice(0, 8) || 'unknown'} (${source}${permMode ? ', ' + permMode : ''})`, cwd, session_id);
if (source === 'clear' && cwd) {
try {
@@ -301,11 +309,12 @@ async function main() {
const modeTag = activeMode ? ` | mode: ${activeMode.name}` : '';
// reloadSkills not set: this hook toggles no skill files
const permTag = permMode ? ` | perm: ${permMode}` : '';
output({
systemMessage: `brewcode: ${pluginRoot} | session: ${sessionShort}${modeTag}${versionLines.length ? '\n' + versionLines.join('\n') : ''}`,
systemMessage: `brewcode: ${pluginRoot} | session: ${sessionShort}${modeTag}${permTag}${versionLines.length ? '\n' + versionLines.join('\n') : ''}`,
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: context,
additionalContext: capText(context),
...(sessionTitle ? { sessionTitle } : {})
}
});
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "claude-plugin-brewcode",
"version": "3.16.6",
"version": "3.17.0",
"description": "Infinite task execution with automatic handoff for Claude Code",
"keywords": [
"claude-code",
@@ -36,6 +36,6 @@
},
"claude-plugin": {
"name": "brewcode",
"version": "3.16.6"
"version": "3.17.0"
}
}