chisle 2.0.0 → 3.1.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.
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { getDefaultMode } = require('../hooks/chisle-config');
7
+ const { duplicateMarker, limitsFor, transform } = require('../hooks/chisle-compress-output');
8
+ const { requestedMode } = require('../hooks/chisle-mode');
9
+
10
+ const RULES_TYPE = 'chisle-rules';
11
+ const MODE_TYPE = 'chisle-mode';
12
+ const SAFE_TOOLS = new Set(['bash', 'powershell', 'grep', 'find', 'ls']);
13
+ const NEVER_COMPRESS = new Set(['read', 'edit', 'write']);
14
+ const DEDUP_MIN = 2048;
15
+ const FALLBACK_RULES = `CHISLE ACTIVE
16
+
17
+ Chisle: maximum-efficiency dev mode. Zero-fluff prose. YAGNI-first code.
18
+
19
+ Drop articles, filler, pleasantries, and hedging. Fragments OK; technical terms exact.
20
+ Code ladder: YAGNI → reuse → stdlib → native → installed dependency → one line → minimum code.
21
+ Never simplify away input validation, data-loss prevention, security, accessibility, or explicit requirements.`;
22
+
23
+ function loadRules() {
24
+ try {
25
+ const skill = fs.readFileSync(path.join(__dirname, '..', 'skills', 'chisle', 'SKILL.md'), 'utf8');
26
+ return 'CHISLE ACTIVE\n\n' + skill.replace(/^---[\s\S]*?---\s*/, '');
27
+ } catch (_) {
28
+ return FALLBACK_RULES;
29
+ }
30
+ }
31
+
32
+ function hasRulesEntry(entries) {
33
+ return (entries || []).some(entry =>
34
+ (entry.type === 'custom_message' && entry.customType === RULES_TYPE) ||
35
+ (entry.type === 'message' && entry.message && entry.message.role === 'custom' &&
36
+ entry.message.customType === RULES_TYPE));
37
+ }
38
+
39
+ function promptHasRules(event) {
40
+ const containsRules = text => /^# Chisle\s*$/m.test(text || '') &&
41
+ (text || '').includes('## Prose: zero fluff') &&
42
+ (text || '').includes('## Code: the efficiency ladder');
43
+ if (containsRules(event.systemPrompt)) return true;
44
+ return (event.systemPromptOptions && event.systemPromptOptions.contextFiles || [])
45
+ .some(file => containsRules(file.content));
46
+ }
47
+
48
+ function piToolAllowed(name) {
49
+ if (!name || NEVER_COMPRESS.has(name.toLowerCase())) return false;
50
+ if (process.env.CHISLE_COMPRESS_TOOLS) {
51
+ return process.env.CHISLE_COMPRESS_TOOLS.split(',').map(s => s.trim()).filter(Boolean).includes(name);
52
+ }
53
+ return SAFE_TOOLS.has(name.toLowerCase()) || name.startsWith('mcp__');
54
+ }
55
+
56
+ function chisleExtension(pi) {
57
+ const rules = loadRules();
58
+ let active = getDefaultMode() !== 'off';
59
+ let rulesInContext = false;
60
+ let savedChars = 0;
61
+ let lastByToolBlock = new Map();
62
+ let dedupBaseline = new Map();
63
+ const stopRulesSignal = pi.events.on('chisle:rules-injected', () => { rulesInContext = true; });
64
+ const stopModeSignal = pi.events.on('chisle:mode', mode => { active = mode === 'on'; });
65
+
66
+ function updateStatus(ctx) {
67
+ if (!ctx.hasUI) return;
68
+ if (!active) return ctx.ui.setStatus('chisle', undefined);
69
+ const tokens = Math.floor(savedChars / 4);
70
+ const saved = tokens >= 1000 ? ` ⇣${Math.floor(tokens / 1000)}k tok` : tokens ? ` ⇣${tokens} tok` : '';
71
+ ctx.ui.setStatus('chisle', ctx.ui.theme.fg('accent', `[CHISLE]${saved}`));
72
+ }
73
+
74
+ function restoreBranch(ctx) {
75
+ active = getDefaultMode() !== 'off';
76
+ savedChars = 0;
77
+ lastByToolBlock = new Map();
78
+ dedupBaseline = new Map();
79
+
80
+ const modeEntry = ctx.sessionManager.getBranch()
81
+ .filter(entry => entry.type === 'custom' && entry.customType === MODE_TYPE).pop();
82
+ if (modeEntry && modeEntry.data && ['on', 'off'].includes(modeEntry.data.mode)) {
83
+ active = modeEntry.data.mode === 'on';
84
+ }
85
+ rulesInContext = hasRulesEntry(ctx.sessionManager.buildContextEntries());
86
+ updateStatus(ctx);
87
+ }
88
+
89
+ function setMode(request, ctx) {
90
+ active = request === 'on';
91
+ pi.events.emit('chisle:mode', active ? 'on' : 'off');
92
+ pi.appendEntry(MODE_TYPE, { mode: active ? 'on' : 'off' });
93
+ updateStatus(ctx);
94
+ if (ctx.hasUI) ctx.ui.notify(active ? 'Chisle enabled.' : 'Chisle disabled.', 'info');
95
+ }
96
+
97
+ pi.registerCommand('chisle', {
98
+ description: 'Enable Chisle or set /chisle on|off',
99
+ handler: async (args, ctx) => {
100
+ const request = requestedMode(`/chisle ${args || ''}`) || 'on';
101
+ setMode(request, ctx);
102
+ },
103
+ });
104
+
105
+ pi.on('session_start', async (_event, ctx) => restoreBranch(ctx));
106
+ pi.on('session_tree', async (_event, ctx) => restoreBranch(ctx));
107
+
108
+ pi.on('session_shutdown', async (_event, ctx) => {
109
+ lastByToolBlock.clear();
110
+ dedupBaseline.clear();
111
+ stopRulesSignal();
112
+ stopModeSignal();
113
+ if (ctx.hasUI) ctx.ui.setStatus('chisle', undefined);
114
+ });
115
+
116
+ pi.on('session_compact', async (_event, ctx) => {
117
+ lastByToolBlock.clear();
118
+ dedupBaseline.clear();
119
+ rulesInContext = hasRulesEntry(ctx.sessionManager.buildContextEntries());
120
+ });
121
+
122
+ pi.on('input', async (event, ctx) => {
123
+ if (event.source === 'extension') return { action: 'continue' };
124
+ const request = requestedMode(event.text);
125
+ if (!request) return { action: 'continue' };
126
+ setMode(request, ctx);
127
+ return request === 'off' ? { action: 'handled' } : { action: 'continue' };
128
+ });
129
+
130
+ pi.on('before_agent_start', async event => {
131
+ if (!active) {
132
+ if (event.systemPrompt.includes('CHISLE DISABLED.')) return;
133
+ return { systemPrompt: event.systemPrompt + '\n\nCHISLE DISABLED. Ignore only Chisle style and efficiency-ladder instructions.' };
134
+ }
135
+ if (rulesInContext || promptHasRules(event)) return;
136
+ rulesInContext = true;
137
+ pi.events.emit('chisle:rules-injected');
138
+ return { message: { customType: RULES_TYPE, content: rules, display: false } };
139
+ });
140
+
141
+ pi.on('context', async event => {
142
+ if (active) return;
143
+ return { messages: event.messages.filter(message =>
144
+ !(message.role === 'custom' && message.customType === RULES_TYPE)) };
145
+ });
146
+
147
+ pi.on('turn_start', async () => {
148
+ dedupBaseline = new Map(lastByToolBlock);
149
+ });
150
+
151
+ pi.on('tool_result', async (event, ctx) => {
152
+ try {
153
+ if (!active || process.env.CHISLE_COMPRESS === '0' || !piToolAllowed(event.toolName) ||
154
+ !Array.isArray(event.content)) return;
155
+
156
+ let changed = false;
157
+ const content = event.content.map((block, index) => {
158
+ if (!block || block.type !== 'text' || typeof block.text !== 'string') return block;
159
+ const text = block.text;
160
+ const key = `${event.toolName}:${index}`;
161
+ let updated = null;
162
+
163
+ if (process.env.CHISLE_COMPRESS_DEDUP !== '0' && text.length >= DEDUP_MIN) {
164
+ const hash = crypto.createHash('sha256').update(text).digest('hex');
165
+ const previous = dedupBaseline.get(key);
166
+ lastByToolBlock.set(key, hash);
167
+ if (previous === hash) updated = duplicateMarker(event.toolName, text, 'earlier');
168
+ }
169
+ if (updated == null) updated = transform(text, limitsFor());
170
+ if (updated == null || updated.length >= text.length) return block;
171
+ changed = true;
172
+ savedChars += text.length - updated.length;
173
+ return { ...block, text: updated };
174
+ });
175
+
176
+ if (!changed) return;
177
+ updateStatus(ctx);
178
+ return { content };
179
+ } catch (_) {
180
+ return;
181
+ }
182
+ });
183
+ }
184
+
185
+ module.exports = chisleExtension;
186
+ module.exports.loadRules = loadRules;
187
+ module.exports.hasRulesEntry = hasRulesEntry;
188
+ module.exports.piToolAllowed = piToolAllowed;
@@ -10,4 +10,4 @@ YAGNI → reuse existing → stdlib → native platform → installed dep → on
10
10
  No unrequested abstractions. Deletion over addition. Shortest diff wins.
11
11
 
12
12
  **Deactivate:** "stop chisle" / "normal mode"
13
- **Switch level:** `/chisle lite` · `/chisle full` · `/chisle ultra`
13
+ **Off:** `/chisle off` · "stop chisle" · "normal mode"
@@ -3,11 +3,10 @@ name: chisle
3
3
  description: >
4
4
  Maximum-efficiency dev mode. Terse, precise prose with zero fluff combined with
5
5
  YAGNI/ladder-first code decisions. One persona: the senior dev who deletes code
6
- for fun and bills by the syllable. Supports intensity levels: lite, full (default),
7
- ultra. Trigger: /chisle. Deactivate: "stop chisle" / "normal mode".
6
+ for fun and bills by the syllable. Trigger: /chisle.
7
+ Deactivate: "stop chisle" / "normal mode".
8
8
  Use when user says "chisle mode", "activate chisle", "chislify", "be efficient", "be minimal",
9
9
  "no fluff", "yagni", or invokes /chisle.
10
- argument-hint: "[lite|full|ultra]"
11
10
  ---
12
11
 
13
12
  # Chisle
@@ -17,7 +16,7 @@ Maximum signal. Minimum noise. Write less. Ship less. Mean more.
17
16
  ## Persistence
18
17
 
19
18
  ACTIVE EVERY RESPONSE. No drift back to verbose over-building. Still active if unsure.
20
- Off only: "stop chisle" / "normal mode". Default: **full**. Switch: `/chisle lite|full|ultra`.
19
+ Off only: "stop chisle" / "normal mode" / `/chisle off`. One mode — no levels to pick.
21
20
 
22
21
  ## Prose: Maximum Signal Per Token
23
22
 
@@ -91,23 +90,16 @@ Pattern: `[code] → skipped: [X], add when [Y].`
91
90
 
92
91
  No essays. No feature tours. No design notes. Explanation longer than code → delete explanation.
93
92
 
94
- ## Intensity
93
+ ## What it sounds like
95
94
 
96
- | Level | What change |
97
- |-------|------------|
98
- | **lite** | No filler/hedging, keep articles + full sentences. Flag the more minimal approach in one line. User picks. |
99
- | **full** | Drop articles, fragments OK, short synonyms. Ladder enforced. Stdlib and native first. Shortest diff, shortest explanation. |
100
- | **ultra** | Abbreviate prose words (DB/auth/config/req/res/fn). Strip conjunctions, arrows for causality (X → Y). YAGNI extremist: deletion before addition, challenge the requirement in same breath. Code symbols/function names/API names: never abbreviate. |
101
-
102
- Example — "Why React component re-render?"
103
- - lite: "Component re-renders because you create a new object reference each render. Wrap it in `useMemo`. FYI: `useCallback` on handlers for same reason."
104
- - full: "New object ref each render. Inline object prop = new ref = re-render. `useMemo`."
105
- - ultra: "Inline obj prop → new ref → re-render. `useMemo`. Why inline object at all?"
95
+ Example — "Why does this React component re-render?"
96
+ > New object ref each render. Inline object prop = new ref = re-render. `useMemo`.
106
97
 
107
98
  Example — "Add a cache for API responses."
108
- - lite: "Cache added. FYI: `functools.lru_cache` covers this in one line if you'd rather not own a class."
109
- - full: "`@lru_cache(maxsize=1000)` on fetch fn. Skipped custom cache class, add when lru_cache measurably falls short."
110
- - ultra: "No cache until profiler says so. When it does: `@lru_cache`. Hand-rolled TTL cache = bug farm with hit rate."
99
+ > `@lru_cache(maxsize=1000)` on the fetch fn. Skipped a custom cache class — add one when `lru_cache` measurably falls short.
100
+
101
+ Example — "Summarize the tradeoffs between REST and GraphQL."
102
+ > GraphQL: one round trip, client picks fields → fewer over-fetches, harder caching + N+1 risk. REST: dumb caching that works, more round trips. New API, unknown clients → REST until a client actually needs field selection.
111
103
 
112
104
  ## Auto-Clarity
113
105
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: chisle-help
3
3
  description: >
4
- Quick-reference card for Chisle modes, levels, and commands. One-shot
4
+ Quick-reference card for Chisle commands. One-shot
5
5
  display, not a persistent mode. Use when the user says "chisle help", "how do I
6
6
  use chisle", "what chisle commands", or invokes /chisle-help.
7
7
  ---
@@ -14,29 +14,20 @@ description: >
14
14
 
15
15
  | Command | Effect |
16
16
  |---------|--------|
17
- | `/chisle` | Activate at default level (full) |
18
- | `/chisle lite` | Tighter prose, flags the minimal alternative |
19
- | `/chisle full` | Full compression + YAGNI ladder |
20
- | `/chisle ultra` | Extremist: abbreviate prose, delete before add |
17
+ | `/chisle` | Activate |
21
18
  | `/chisle-audit [path]` | Audit a diff/file/repo for both code bloat AND prose verbosity |
22
19
  | `/chisle-review` | Review the current diff for over-engineering |
23
20
  | `stop chisle` / `normal mode` | Deactivate |
24
21
 
25
22
  Natural language works: "activate chisle", "chisle mode", "chislify this".
26
23
 
27
- ## Levels at a glance
28
-
29
- - **lite** — keeps full sentences; names the lazier code option, you pick.
30
- - **full** — drops articles, fragments OK, ladder enforced. Default.
31
- - **ultra** — abbreviates prose words, challenges the requirement itself.
32
-
33
24
  ## The code ladder
34
25
 
35
26
  YAGNI → reuse → stdlib → native → installed dep → one line → minimum code.
36
27
 
37
28
  ## Statusline
38
29
 
39
- `[CHISLE]` / `[CHISLE:ULTRA]` — shows the active level. Configure via
30
+ `[CHISLE]` — shown while active. Configure via
40
31
  `hooks/chisle-statusline.sh` (or `.ps1` on Windows).
41
32
 
42
33
  ## Never minimal about