Code Generation
Hook
Hooks
What chisle runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
> /plugin marketplace add JayPokale/Chisle > /plugin install chisle@chisle
Ships with chisle. Installing the plugin gets these hooks.
Where it lives
- hooks/chisle-activate.jsGitHub
Read the script
#!/usr/bin/env node // chisle — SessionStart hook // // 1. Writes flag at $CLAUDE_CONFIG_DIR/.chisle-active // 2. Resets session turn counter // 3. Emits the chisle ruleset as system context // 4. Nudges user to configure statusline if missing // 5. Major-version update notice (majors only, cached, fail-silent) const fs = require('fs'); const path = require('path'); const { getDefaultMode, getClaudeDir, safeWriteFlag, legacySetting, resolveSections, filterSections } = require('./chisle-config'); // ── Update-notice helpers (pure — tested directly) ───────────────────────── // Minor/patch releases stay quiet: a nudge per major is signal, more is spam. const UPDATE_CACHE_MS = 3 * 24 * 60 * 60 * 1000; function majorOf(v) { const m = /^(\d+)\./.exec(String(v || '')); return m ? parseInt(m[1], 10) : null; } // Notice line for a major jump, else null. function majorUpdateNotice(installed, latest) { const i = majorOf(installed), l = majorOf(latest); if (i == null || l == null || l <= i) return null; return '\n\nCHISLE UPDATE AVAILABLE: v' + latest + ' (major; you run v' + installed + '). ' + 'Mention this to the user once: update with `claude plugin update chisle@chisle` or `npx chisle`.'; } function installedVersion() { try { return JSON.parse(fs.readFileSync( path.join(__dirname, '..', '.claude-plugin', 'plugin.json'), 'utf8')).version; } catch (e) { return null; } // standalone-hooks install → no manifest → skip } // Registry lookup, cached on disk. Network capped at 1.5s inside the 5s hook budget. async function latestVersion(cachePath) { try { const c = JSON.parse(fs.readFileSync(cachePath, 'utf8')); if (c && Date.now() - c.checkedAt < UPDATE_CACHE_MS) return c.latest; } catch (e) {} try { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 1500); const res = await fetch('https://registry.npmjs.org/chisle/latest', { signal: ctrl.signal }); clearTimeout(t); if (!res.ok) return null; const latest = (await res.json()).version; try { fs.writeFileSync(cachePath, JSON.stringify({ checkedAt: Date.now(), latest }), { mode: 0o600 }); } catch (e) {} return latest; } catch (e) { return null; } } // SessionStart fires on startup, resume, clear and compact. Only a genuinely // new session needs the whole ruleset: on the other three the conversation // already carries it, and the UserPromptSubmit reminder re-states the active // behaviour every turn anyway. Re-sending the full ruleset each time was the bulk // of the plugin's own overhead and piled standing instructions on top of what // the user actually asked for. Measured and reported by @enc0ded (#2). const FULL_INJECT_SOURCES = new Set(['startup']); // ── Hook body ─────────────────────────────────────────────────────────────── function run(source) { const claudeDir = getClaudeDir(); const flagPath = path.join(claudeDir, '.chisle-active'); const settingsPath = path.join(claudeDir, 'settings.json'); const mode = getDefaultMode(); if (mode === 'off') { try { fs.unlinkSync(flagPath); } catch (e) {} process.stdout.write('OK'); process.exit(0); } // 1. Write flag safeWriteFlag(flagPath, mode); // 2. Resumed/cleared/compacted session: reactivate, don't re-teach. if (!FULL_INJECT_SOURCES.has(source)) { process.stdout.write( 'CHISLE ACTIVE (resumed). ' + 'Ruleset already in context; see the chisle skill if it is not.' ); return; } // 3. Read SKILL.md — single source of truth for behaviour. No level // filtering any more: there is one mode, so the whole body ships, minus // whatever `sections` in config.json opts out of (prose and/or code rules; // config-file only, defaults to both enabled — see chisle-config.js). let skillContent = ''; try { skillContent = fs.readFileSync( path.join(__dirname, '..', 'skills', 'chisle', 'SKILL.md'), 'utf8' ); } catch (e) {} // Yields the prose section to an active Claude Code output style, which // governs the same thing and would otherwise contradict it (#15/#17). // An explicit sections.prose in config.json still wins. const sections = resolveSections({ cwd: process.cwd(), claudeDir }); let output; if (skillContent) { output = 'CHISLE ACTIVE\n\n' + filterSections(skillContent.replace(/^---[\s\S]*?---\s*/, ''), sections); } else { // Fallback ruleset when SKILL.md not found const fallbackBody = 'Chisle: maximum-efficiency dev mode. Zero-fluff prose. YAGNI-first code.\n\n' + '## Persistence\n\n' + 'ACTIVE EVERY RESPONSE. Off only: "stop chisle" / "normal mode".\n\n' + '## Prose\n\n' + 'Drop articles/filler/pleasantries/hedging. Fragments OK. Technical terms exact.\n\n' + '## Code\n\n' + 'Ladder: YAGNI → reuse → stdlib → native → installed dep → one line → min code.\n' + 'No unrequested abstractions. Deletion over addition. Shortest diff wins.'; output = 'CHISLE ACTIVE\n\n' + filterSections(fallbackBody, sections, ['Prose'], ['Code']); } // 3. Detect missing statusline config try { let hasStatusline = false; if (fs.existsSync(settingsPath)) { const raw = fs.readFileSync(settingsPath, 'utf8').replace(/^/, ''); const settings = JSON.parse(raw); if (settings.statusLine) hasStatusline = true; } if (!hasStatusline) { const scriptPath = path.join(__dirname, 'chisle-statusline.sh'); const command = `bash "${scriptPath}"`; const statusLineSnippet = '"statusLine": { "type": "command", "command": ' + JSON.stringify(command) + ' }'; output += '\n\n' + 'STATUSLINE SETUP NEEDED: The chisle plugin includes a statusline badge ' + '([CHISLE]) with token savings. It is not configured yet. ' + 'To enable, add this to ' + settingsPath + ': ' + statusLineSnippet + ' ' + 'Proactively offer to set this up for the user on first interaction.'; - hooks/chisle-compress-output.jsGitHub
Read the script
#!/usr/bin/env node // chisle — PostToolUse hook: tool-output compression (the input axis). // // Prompt-side rules shrink what the agent WRITES; this shrinks what it READS. // Oversized tool results (Bash dumps, subagent reports, web fetches) get their // repetitive middle elided — head kept (command context), tail kept (results/ // errors), error-looking lines salvaged from the cut — and the trimmed version // replaces what the model sees. Deterministic, zero LLM, zero network. Small // outputs pass through untouched. // // Two harnesses call into this same core: // - Claude Code / Pi: `updatedToolOutput`, snake_case payload // (tool_name, tool_response, session_id, tool_use_id), gated behind the // /chisle on/off flag file (see chisle-config.js: getDefaultMode/readFlag). // - GitHub Copilot CLI: `modifiedResult.textResultForLlm`, camelCase flat // payload (toolName, toolResult.textResultForLlm, sessionId). Copilot has // no /chisle-equivalent mode toggle (no UserPromptSubmit-driven flag), so // compression is always-on for Copilot once the hook is installed — // exactly like the static ruleset Copilot already gets unconditionally // (see README: "the always-on ruleset still ships to every other agent"). // CHISLE_COMPRESS=0 is still honored as the one kill switch for both. // // Correctness guardrails (why this never touches Read/Edit/Write): // - Read output feeds later Edit old_string matching — eliding it makes the // model edit against text it never saw. Allowlist below, never a blocklist. // - Error lines in the elided middle are salvaged, capped, and kept verbatim. // - If compression doesn't shrink the output, the original is kept. // - Never throws: a broken hook must not break the tool pipeline. // // Two tiers, applied in order: // scrub (output > 1k) — lossless: strip ANSI escapes, collapse blank-line // runs, collapse ≥4 identical consecutive lines to // one + "[repeated N×]". No information lost. // elide (output > threshold) — head + tail + error salvage, as above. // Plus dedup: a tool output byte-identical to that tool's immediately previous // output is replaced by a short marker — the content is already in context. // // Compression follows the /chisle flag for Claude/Pi (off → untouched); it is // unconditional for Copilot (see above). Tunables via env, shared across all // three harnesses: // CHISLE_COMPRESS=0 — kill switch // CHISLE_COMPRESS_SCRUB=0 — disable the lossless scrub tier // CHISLE_COMPRESS_DEDUP=0 — disable duplicate-output markers // CHISLE_COMPRESS_MAX_CHARS — outputs at/under this size are not elided // CHISLE_COMPRESS_HEAD_LINES — lines kept from the top // CHISLE_COMPRESS_TAIL_LINES — lines kept from the bottom // CHISLE_COMPRESS_TOOLS=Bash,Grep — override the tool allowlist (both harnesses) // CHISLE_COMPRESS_SPILL=0 — elide without writing the recovery copy // // Savings accrue in <stateDir>/.chisle-compress-stats.json for the statusline // (Claude/Pi: <claudeDir>; Copilot: <copilotDir>, see chisle-config.js). // Elided originals spill to <stateDir>/chisle-spill/ so the dropped middle can // be grepped back instead of re-running the command; the newest 40 are kept. const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const { getClaudeDir, getCopilotDir, getOpencodeStateDir, readFlag, getDefaultMode } = require('./chisle-config'); // One threshold. Env overrides all. // These are the values the old 'full' level used — the default, // and the only one the published benchmarks ever ran at. const THRESHOLDS = { maxChars: 8000, headLines: 60, tailLines: 40 }; // Tools whose output is safe to elide. Read/Edit/Write are absent on purpose: // their output feeds exact-match edits. mcp__* are read-only info tools. const SAFE_TOOLS = ['Bash', 'Agent', 'WebFetch', 'WebSearch', 'Grep', 'Glob']; // The correctness invariant, enforced rather than merely defaulted: these tools' // output feeds later exact-match edits, so eliding it makes the model edit text // it never saw. CHISLE_COMPRESS_TOOLS is an override for the *allowlist*, not a // way to switch this off — the Pi extension (NEVER_COMPRESS) and the OpenCode // path (UNSAFE_TOOLS_OPENCODE) have always checked it first, and this is the // same guard for the Claude/Pi hook payloads. const NEVER_COMPRESS = ['read', 'edit', 'write', 'multiedit', 'notebookedit', 'notebookread']; // Copilot CLI's own runtime tool names (lowercase, no mcp__ prefix scheme). // Same allowlist rule as SAFE_TOOLS: Copilot's Read/Edit/Write equivalents // are `view`/`create`/`edit` and are deliberately absent here too. Verified // against Copilot CLI's real tool set, not guessed — see the accompanying PR // description for the live-verification evidence (Beyin chisle-port project). const SAFE_TOOLS_COPILOT = ['bash', 'powershell', 'grep', 'glob', 'web_fetch', 'web_search', 'task']; // Copilot's Read/Edit/Write family, hard-blocked for the same reason as // NEVER_COMPRESS above — not merely absent from the allowlist. const NEVER_COMPRESS_COPILOT = ['view', 'create', 'edit', 'str_replace', 'insert']; // Letter/digit boundaries, not \b: `_` is a word char, so \b missed severities // inside codes like FATAL_MEMORY_PROBE / APP_ERROR_CODE (#38), and `err!` then // a space has no trailing \b at all. const SALVAGE_RE = /(?<![A-Za-z0-9])(?:err!|(?:error|fail(ed|ure|ing)?|exception|traceback|panic|fatal|denied|refused|timed?[ _-]?out|assert(ion)?|segfault|npe|undefined reference|cannot find|not found|warning)(?![A-Za-z0-9]))/i; const MAX_SALVAGED = 12; // error lines rescued from the elided middle const MAX_SALVAGE_LINE = 300; // per-line char cap on salvaged lines function splitsSurrogatePair(text, index) { if (index <= 0 || index >= text.length) return false; const before = text.charCodeAt(index - 1) - hooks/chisle-config.jsGitHub
Read the script
#!/usr/bin/env node // chisle — shared configuration resolver // // Resolution order for default mode: // 1. CHISLE_DEFAULT_MODE environment variable // 2. User config: $XDG_CONFIG_HOME/chisle/config.json, ~/.config/chisle/config.json // 3. 'on' const fs = require('fs'); const path = require('path'); const os = require('os'); // One mode. The intensity levels are gone: three dials on a tool whose whole // argument is "fewer knobs" was the joke writing itself, and the middle setting // was the only one ever benchmarked. 'on' or 'off'. const VALID_MODES = ['off', 'on']; // Values that meant something before 3.0.0. Kept only so the upgrade can say // "your setting no longer does anything" instead of ignoring it in silence. const LEGACY_MODES = ['lite', 'full', 'ultra']; // Returns the stale value and where it came from, or null. Never throws. function legacySetting() { const env = process.env.CHISLE_DEFAULT_MODE; if (env && LEGACY_MODES.includes(String(env).toLowerCase())) { return { value: String(env).toLowerCase(), source: 'CHISLE_DEFAULT_MODE' }; } try { const p = path.join(getConfigDir(), 'config.json'); const c = JSON.parse(fs.readFileSync(p, 'utf8')); const v = c && c.defaultMode ? String(c.defaultMode).toLowerCase() : null; if (v && LEGACY_MODES.includes(v)) return { value: v, source: p }; } catch (e) {} return null; } function getClaudeDir() { return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'); } // OpenCode's global config dir. The compressor plugin keeps its spill/dedup/ // stats here (as Copilot uses getCopilotDir), so recovery files sit beside // OpenCode's own config instead of leaking into ~/.claude. function getOpencodeDir(opts) { const options = opts || {}; const env = options.env || process.env; const home = options.home || os.homedir(); if (env.OPENCODE_CONFIG_DIR) return env.OPENCODE_CONFIG_DIR; if (env.XDG_CONFIG_HOME) return path.join(env.XDG_CONFIG_HOME, 'opencode'); return path.join(home, '.config', 'opencode'); } function getOpencodeStateDir(opts) { const env = (opts && opts.env) || process.env; return env.CHISLE_STATE_DIR || getOpencodeDir(opts); } // GitHub Copilot CLI's own user-level state directory (mirrors the // CLAUDE_CONFIG_DIR precedent above). Copilot CLI already reads/writes hook // config under this same directory (~/.copilot/hooks/), so chisle's // dedup/spill/stats state lives alongside it rather than under ~/.claude, // which would be misleading for a machine that never runs Claude Code. function getCopilotDir() { return process.env.COPILOT_HOME || path.join(os.homedir(), '.copilot'); } function getConfigDir() { if (process.env.XDG_CONFIG_HOME) { return path.join(process.env.XDG_CONFIG_HOME, 'chisle'); } if (process.platform === 'win32') { return path.join( process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'chisle' ); } return path.join(os.homedir(), '.config', 'chisle'); } function readModeFromConfigFile(configPath) { try { const raw = fs.readFileSync(configPath, 'utf8'); const config = JSON.parse(raw); if (config && config.defaultMode && VALID_MODES.includes(String(config.defaultMode).toLowerCase())) { return String(config.defaultMode).toLowerCase(); } } catch (e) {} return null; } function getDefaultMode() { const envMode = process.env.CHISLE_DEFAULT_MODE; if (envMode && VALID_MODES.includes(envMode.toLowerCase())) { return envMode.toLowerCase(); } const userMode = readModeFromConfigFile(path.join(getConfigDir(), 'config.json')); if (userMode) return userMode; return 'on'; } // ── Section suppression ───────────────────────────────────────────────────── // Optional config key letting a user drop Chisle's prose rules or code rules // independently, while keeping the other. Config file only — no env override, // unlike defaultMode. Absent, malformed, or non-boolean always falls back to // enabled (true); never throws. // { "sections": { "prose": false } } // suppress prose rules, keep code // Headings from skills/chisle/SKILL.md, grouped by section. const PROSE_HEADINGS = [ 'Prose: Maximum Signal Per Token', 'Output Format', ]; const CODE_HEADINGS = [ 'Code: The Efficiency Ladder', 'Code Rules', 'Context Diet: Read Less Into the Window', ]; function readSectionsFromConfigFile(configPath) { try { const raw = fs.readFileSync(configPath, 'utf8'); const config = JSON.parse(raw); const s = config && typeof config === 'object' ? config.sections : null; return { prose: !!(s && typeof s === 'object' && typeof s.prose === 'boolean' ? s.prose : true), code: !!(s && typeof s === 'object' && typeof s.code === 'boolean' ? s.code : true), }; } catch (e) { return { prose: true, code: true }; } } function getSections() { return readSectionsFromConfigFile(path.join(getConfigDir(), 'config.json')); } // Drops whole `## Heading` sections (heading line through the line before the // next `## ` heading) whose heading is in an excluded group. Byte-identical // to `content` when nothing is excluded. Never throws — falls back to the // unfiltered content. Assumes no `## ` line appears inside a fenced code // block (true for SKILL.md and the fallback ruleset at 07c603b). function filterSections(content, sections, proseHeadings, codeHeadings) { try { if (typeof content !== 'string' || !content) return content; const prose = sections ? sections.prose !== false : true; const code = sections ? sections.code !== false : true; if (prose && code) return content; const dropProse = proseHeadings || PROSE_HEADINGS; const dropCode = codeHeadings || CODE_HEADINGS; const chunks = content.split(/(?=^## .+$)/m); const kept = chunks.filter((chunk) => { const m = /^## (.+)$/m.exec(chunk); if (!m) return true; // preamble chunk (no heading) const heading = m[1].trim(); if (!pr - hooks/chisle-mcp-proxy.jsGitHub
Read the script
#!/usr/bin/env node // chisle — stdio MCP proxy: tool-output compression for any MCP host. // // Most agents cannot rewrite a tool result from a hook (Windsurf, Cline, // Kiro, Gemini, Qwen; Cursor only for MCP), but every one of them launches // stdio MCP servers from a command line it does not inspect. Wrapping that // command puts Chisle between host and server: // // node chisle-mcp-proxy.js -- <original command> [args...] // // Bytes pass through untouched in both directions, except the JSON-RPC // response to a `tools/call` request: its `content[]` text blocks go through // the same scrub + elide core as every other harness (head/tail kept, error // lines salvaged, full original spilled to disk). Everything else — init, // tools/list, notifications, resources, malformed lines — is forwarded // byte-identical. `npx chisle mcp wrap` rewrites host configs to use it. // // Deliberately absent: // - dedup markers: one server process can outlive a chat and serve the // next one, whose context never saw the earlier copy. // - read/edit-shaped tools (read_file, get_file_contents, edit_file, ...): // their output feeds exact-match edits. Skipped by name. // - structuredContent: left as-is; hosts that forward it get it unchanged. // // Never breaks the server: any compressor error forwards the original line. // Tunables: CHISLE_COMPRESS=0, CHISLE_DEFAULT_MODE=off, CHISLE_COMPRESS_MAX_CHARS // (+ HEAD/TAIL_LINES), CHISLE_COMPRESS_SPILL=0, CHISLE_STATE_DIR (spill + stats, // default ~/.config/chisle). 'use strict'; const cp = require('child_process'); const { StringDecoder } = require('string_decoder'); const core = require('./chisle-compress-output'); const { getDefaultMode, getConfigDir } = require('./chisle-config'); const IS_WIN = process.platform === 'win32'; // Tool names whose output may feed a later exact-match edit, or is file // content the model quotes back. Matched on the raw MCP tool name. const UNSAFE_MCP_RE = /read|view|file|edit|write|patch|replace|insert/i; const MAX_PENDING_LINE = 16 * 1024 * 1024; // drop observation of absurd lines function mcpToolAllowed(name) { return typeof name === 'string' && !!name && !UNSAFE_MCP_RE.test(name); } function stateDir() { return process.env.CHISLE_STATE_DIR || getConfigDir(); } // Compress one tools/call result in place. Returns chars saved (0 = untouched). function compressResult(result, toolName) { if (!result || !Array.isArray(result.content)) return 0; const limits = core.limitsFor(); let saved = 0; for (const block of result.content) { if (!block || block.type !== 'text' || typeof block.text !== 'string') continue; if (block.text.includes('[chisle:')) continue; // already compressed upstream const updated = core.transform(block.text, limits, 'mcp-' + toolName, stateDir()); if (updated != null && updated.length < block.text.length) { saved += block.text.length - updated.length; block.text = updated; } } return saved; } // One server→host line. Returns the line to forward (original when untouched). function rewriteLine(line, pending) { if (!pending.size || line.indexOf('"result"') === -1) return line; let msg; try { msg = JSON.parse(line); } catch (e) { return line; } const msgs = Array.isArray(msg) ? msg : [msg]; let saved = 0; for (const m of msgs) { if (!m || m.id == null || !('result' in m)) continue; const key = JSON.stringify(m.id); const tool = pending.get(key); if (tool === undefined) continue; pending.delete(key); if (!mcpToolAllowed(tool)) continue; saved += compressResult(m.result, tool); } if (!saved) return line; core.recordSavings(saved, stateDir()); return JSON.stringify(msg); } // One host→server line: remember which request ids are tool calls. function observeRequest(line, pending) { if (line.indexOf('tools/call') === -1 && line.indexOf('notifications/cancelled') === -1) return; let msg; try { msg = JSON.parse(line); } catch (e) { return; } for (const m of Array.isArray(msg) ? msg : [msg]) { if (m && m.method === 'tools/call' && m.id != null) { pending.set(JSON.stringify(m.id), m.params && typeof m.params.name === 'string' ? m.params.name : ''); } // A cancelled call never gets a result; forget it. if (m && m.method === 'notifications/cancelled' && m.params && m.params.requestId != null) { pending.delete(JSON.stringify(m.params.requestId)); } } } // Splits a byte stream into lines, calling onLine(lineWithoutNewline, eol). function lineSplitter(onLine) { let buf = ''; return { push(chunk) { buf += chunk; let i; while ((i = buf.indexOf('\n')) !== -1) { const raw = buf.slice(0, i); buf = buf.slice(i + 1); const cr = raw.endsWith('\r'); onLine(cr ? raw.slice(0, -1) : raw, cr ? '\r\n' : '\n'); } }, rest() { const r = buf; buf = ''; return r; }, get size() { return buf.length; }, }; } function quoteWin(a) { if (a === '' || /[\s"&|<>^()%!]/.test(a)) return '"' + String(a).replace(/\\(?=\\*"|$)/g, '\\\\').replace(/"/g, '\\"') + '"'; return a; } function main(argv) { const sep = argv.indexOf('--'); const cmd = sep === -1 ? argv : argv.slice(sep + 1); if (!cmd.length) { process.stderr.write('usage: chisle-mcp-proxy -- <command> [args...]\n'); process.exit(2); } // Windows MCP commands are usually npx/uvx shims (.cmd), which Node refuses // to spawn without a shell; elsewhere spawn directly, no shell parsing. const child = IS_WIN ? cp.spawn(cmd.map(quoteWin).join(' '), [], { stdio: ['pipe', 'pipe', 'inherit'], shell: true, windowsHide: true }) : cp.spawn(cmd[0], cmd.slice(1), { stdio: ['pipe', 'pipe', 'inherit'] }); const enabled = process.env.CHISLE_COMPRESS !== '0' && getDefaultMode() !== 'off'; const pending = new Map(); child.on('error', (e) => { process.stderr.write('chisle-mcp-proxy: cannot start ' + cmd[0] + ': ' + e.message + '\n'); - hooks/chisle-mode-tracker.jsGitHub
Read the script
#!/usr/bin/env node // chisle — UserPromptSubmit hook // Handles /chisle commands, natural language activation/deactivation, and // per-turn reinforcement. const fs = require('fs'); const path = require('path'); const { getClaudeDir, safeWriteFlag, readFlag, resolveSections } = require('./chisle-config'); const { requestedMode } = require('./chisle-mode'); // Per-turn reinforcement clauses. `Code/commits/security: write normal.` is // the Boundaries carve-out (always-on in SKILL.md) and ships regardless of // `sections`; the prose/code clauses honor it the same way the SessionStart // ruleset does. const PROSE_CLAUSE = 'Prose: drop articles/filler/pleasantries/hedging. Fragments OK.'; const CODE_CLAUSE = 'Code: YAGNI ladder first (reuse → stdlib → native → dep → one line → min code).'; const BOUNDARIES_CLAUSE = 'Code/commits/security: write normal.'; function reinforcementLine(sections) { const prose = sections ? sections.prose !== false : true; const code = sections ? sections.code !== false : true; const parts = ['CHISLE ACTIVE.']; if (prose) parts.push(PROSE_CLAUSE); if (code) parts.push(CODE_CLAUSE); parts.push(BOUNDARIES_CLAUSE); return parts.join(' '); } const claudeDir = getClaudeDir(); const flagPath = path.join(claudeDir, '.chisle-active'); let input = ''; process.stdin.on('data', chunk => { input += chunk; }); process.stdin.on('end', () => { try { const data = JSON.parse(input.replace(/^/, '')); const prompt = (data.prompt || '').trim(); const request = requestedMode(prompt); if (request === 'off') { try { fs.unlinkSync(flagPath); } catch (e) {} } else if (request === 'on') { safeWriteFlag(flagPath, 'on'); } // Per-turn reinforcement const activeMode = readFlag(flagPath); if (activeMode && activeMode !== 'off') { // Inject compact reminder — keeps chisle visible across context compression process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: reinforcementLine(resolveSections({ cwd: process.cwd(), claudeDir })) } })); } } catch (e) { // Silent fail } }); - hooks/chisle-mode.jsGitHub
Read the script
'use strict'; // Shared mode-directive parser. Harness adapters decide how to persist mode. function requestedMode(prompt) { const text = String(prompt || '').trim().toLowerCase(); if (/\b(turn off|disable|deactivate|stop|kill|exit)\s+chisle\b/i.test(text) || /\bchisle\s+(mode\s+)?(off|stop|disable|deactivate)\b/i.test(text) || /\bnormal mode\b/i.test(text)) return 'off'; if (/^\/chisle(\b|:chisle\b)/.test(text) || /\b(activate|enable|turn on|start|use)\b.*\bchisle\b/i.test(text) || /\bchisle\b.*\b(mode|activate|enable|on)\b/i.test(text) || /\bchislif(y|ier)\b/i.test(text)) return 'on'; return null; } module.exports = { requestedMode }; - hooks/chisle-statusline.ps1GitHub
- hooks/chisle-statusline.shGitHub
All 8 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withchisle
Cut your AI coding agent's token bill on three axes: terse prose, YAGNI-first code, and tool-output compression. Claude Code, Pi, Cursor, Codex, Gemini + 4 more. Zero deps, published benchmarks including the runs it loses.
Get the whole plugin
Stats
674
Stars
49
Forks
Active
Maintenance
JavaScript
Language
MIT
License
11h ago
Last commit
3mo ago
Created
2h ago
Added

