chisle 3.4.0 → 3.6.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/plugin.json +1 -1
- package/.clinerules/chisle.md +2 -1
- package/.cursor/rules/chisle.mdc +2 -1
- package/.github/copilot-instructions.md +2 -1
- package/.kiro/steering/chisle.md +2 -1
- package/.windsurf/rules/chisle.md +2 -1
- package/AGENTS.md +5 -0
- package/README.md +83 -14
- package/hooks/chisle-activate.js +13 -6
- package/hooks/chisle-config.js +161 -0
- package/hooks/chisle-mode-tracker.js +20 -6
- package/package.json +1 -1
- package/pi-extension/index.js +41 -8
- package/skills/chisle/SKILL.md +32 -99
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chisle",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Maximum-efficiency dev mode. Zero-fluff prose combined with YAGNI/ladder-first code decisions. One persona: the senior dev who deletes code for fun and bills by the syllable.",
|
|
5
5
|
"hooks": {
|
|
6
6
|
"SessionStart": [
|
package/.clinerules/chisle.md
CHANGED
|
@@ -8,7 +8,8 @@ meaning survives. Causality as arrows (X → Y). Technical terms, code, API name
|
|
|
8
8
|
errors: exact, verbatim. Terse ≠ incomplete: keep every decisive fact (the fix,
|
|
9
9
|
the gotcha, the why); cut the words around them, never the facts. Structure is
|
|
10
10
|
tokens, so answer at the question's altitude; no manufactured headings, bullet
|
|
11
|
-
lists, or sections the question didn't ask for.
|
|
11
|
+
lists, numbered steps, or sections the question didn't ask for. Two tight
|
|
12
|
+
paragraphs beat five headed sections. "Summarize/compare X vs Y" is
|
|
12
13
|
the trap: skip headed pro/con bullet walls, name the decisive tradeoffs in
|
|
13
14
|
prose, give the verdict, stop.
|
|
14
15
|
|
package/.cursor/rules/chisle.mdc
CHANGED
|
@@ -13,7 +13,8 @@ meaning survives. Causality as arrows (X → Y). Technical terms, code, API name
|
|
|
13
13
|
errors: exact, verbatim. Terse ≠ incomplete: keep every decisive fact (the fix,
|
|
14
14
|
the gotcha, the why); cut the words around them, never the facts. Structure is
|
|
15
15
|
tokens, so answer at the question's altitude; no manufactured headings, bullet
|
|
16
|
-
lists, or sections the question didn't ask for.
|
|
16
|
+
lists, numbered steps, or sections the question didn't ask for. Two tight
|
|
17
|
+
paragraphs beat five headed sections. "Summarize/compare X vs Y" is
|
|
17
18
|
the trap: skip headed pro/con bullet walls, name the decisive tradeoffs in
|
|
18
19
|
prose, give the verdict, stop.
|
|
19
20
|
|
|
@@ -8,7 +8,8 @@ meaning survives. Causality as arrows (X → Y). Technical terms, code, API name
|
|
|
8
8
|
errors: exact, verbatim. Terse ≠ incomplete: keep every decisive fact (the fix,
|
|
9
9
|
the gotcha, the why); cut the words around them, never the facts. Structure is
|
|
10
10
|
tokens, so answer at the question's altitude; no manufactured headings, bullet
|
|
11
|
-
lists, or sections the question didn't ask for.
|
|
11
|
+
lists, numbered steps, or sections the question didn't ask for. Two tight
|
|
12
|
+
paragraphs beat five headed sections. "Summarize/compare X vs Y" is
|
|
12
13
|
the trap: skip headed pro/con bullet walls, name the decisive tradeoffs in
|
|
13
14
|
prose, give the verdict, stop.
|
|
14
15
|
|
package/.kiro/steering/chisle.md
CHANGED
|
@@ -12,7 +12,8 @@ meaning survives. Causality as arrows (X → Y). Technical terms, code, API name
|
|
|
12
12
|
errors: exact, verbatim. Terse ≠ incomplete: keep every decisive fact (the fix,
|
|
13
13
|
the gotcha, the why); cut the words around them, never the facts. Structure is
|
|
14
14
|
tokens, so answer at the question's altitude; no manufactured headings, bullet
|
|
15
|
-
lists, or sections the question didn't ask for.
|
|
15
|
+
lists, numbered steps, or sections the question didn't ask for. Two tight
|
|
16
|
+
paragraphs beat five headed sections. "Summarize/compare X vs Y" is
|
|
16
17
|
the trap: skip headed pro/con bullet walls, name the decisive tradeoffs in
|
|
17
18
|
prose, give the verdict, stop.
|
|
18
19
|
|
|
@@ -12,7 +12,8 @@ meaning survives. Causality as arrows (X → Y). Technical terms, code, API name
|
|
|
12
12
|
errors: exact, verbatim. Terse ≠ incomplete: keep every decisive fact (the fix,
|
|
13
13
|
the gotcha, the why); cut the words around them, never the facts. Structure is
|
|
14
14
|
tokens, so answer at the question's altitude; no manufactured headings, bullet
|
|
15
|
-
lists, or sections the question didn't ask for.
|
|
15
|
+
lists, numbered steps, or sections the question didn't ask for. Two tight
|
|
16
|
+
paragraphs beat five headed sections. "Summarize/compare X vs Y" is
|
|
16
17
|
the trap: skip headed pro/con bullet walls, name the decisive tradeoffs in
|
|
17
18
|
prose, give the verdict, stop.
|
|
18
19
|
|
package/AGENTS.md
CHANGED
|
@@ -13,6 +13,11 @@ Drop articles, filler (just/really/basically/actually), pleasantries
|
|
|
13
13
|
(sure/certainly/happy to), hedging. Fragments OK. Technical terms exact.
|
|
14
14
|
Code blocks unchanged. Pattern: `[thing] [action] [reason].`
|
|
15
15
|
|
|
16
|
+
Terse ≠ incomplete: keep every decisive fact (the fix, the gotcha, the why);
|
|
17
|
+
cut the words around them. Structure is tokens: no headings, bullets,
|
|
18
|
+
numbered steps or tables the question didn't ask for. Two tight paragraphs
|
|
19
|
+
beat five headed sections.
|
|
20
|
+
|
|
16
21
|
## Code: the efficiency ladder
|
|
17
22
|
|
|
18
23
|
Before writing anything, stop at the first rung that holds:
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<em>The only tool in this class that publishes the runs where it lost.</em>
|
|
12
|
+
<em>The only tool in this class that publishes the runs where it lost. <a href="https://jaypokale.me/writing/chisle-benchmarks-it-loses">Here's why.</a></em>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
25
|
-
<strong>
|
|
25
|
+
<strong>83% of a bare model's output, where caveman and ponytail land above 100% · 11 agents · zero dependencies · one command</strong>
|
|
26
26
|
</p>
|
|
27
27
|
|
|
28
28
|
<p align="center">
|
|
@@ -153,15 +153,25 @@ That refreshes every agent that already has Chisle and installs it into none tha
|
|
|
153
153
|
- **Plain `npx chisle` does not upgrade.** Every install path skips what is already present, so an upgrade run reports success and changes nothing. `--update` pairs the refresh with that check.
|
|
154
154
|
- **`@latest` matters.** `npx chisle` can serve a cached copy of the package from a previous run, so the pin is what guarantees you get the new one.
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
Update one agent at a time with `--only`:
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
|
|
160
|
-
pi install npm:chisle
|
|
161
|
-
gemini extensions install https://github.com/JayPokale/Chisle
|
|
159
|
+
npx chisle@latest --update --only claude # or: claude plugin update chisle@chisle
|
|
160
|
+
npx chisle@latest --update --only pi # or: pi install npm:chisle
|
|
161
|
+
npx chisle@latest --update --only gemini # or: gemini extensions install https://github.com/JayPokale/Chisle
|
|
162
|
+
npx chisle@latest --update --only codex
|
|
163
|
+
npx chisle@latest --update --only opencode # ruleset + skills + the compression plugin
|
|
164
|
+
npx chisle@latest --update --only hermes
|
|
162
165
|
```
|
|
163
166
|
|
|
164
|
-
|
|
167
|
+
Cursor, Windsurf, Cline, Kiro and Copilot keep their rule file **inside the repo**, because that is how those agents load rules:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
cd ~/code/my-project
|
|
171
|
+
npx chisle@latest --update --only cursor
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`--update` looks for that file in the current directory, so running it from `~` reports "Nothing to update" even when the project is set up correctly. Run it once per repo that has one. Full table, including what each agent's update actually refreshes: [INSTALL.md](./INSTALL.md#per-agent).
|
|
165
175
|
|
|
166
176
|
Not sure what you are running? `npx chisle --list` prints the agents it detects, and `claude plugin list` shows the installed plugin and its version.
|
|
167
177
|
|
|
@@ -224,7 +234,21 @@ node benchmarks/replay-compress.js pi # Pi; marginal over Pi's native truncat
|
|
|
224
234
|
|
|
225
235
|
Nothing here is estimated. Every figure below is recomputed from committed raw data; the 2026-07-07 [verification writeup](benchmarks/results/2026-07-07-verify-rerun.md) re-derived the old claims from scratch, re-ran the whole suite against the competitors' **installed plugins**, and retired the one claim that didn't survive.
|
|
226
236
|
|
|
227
|
-
### Output axis
|
|
237
|
+
### Output axis, re-measured 2026-10-01 (current)
|
|
238
|
+
|
|
239
|
+
Re-run with the shipped ruleset, Claude Code 2.1.285 and the rivals' current plugins (caveman `ef6050c5e184`, ponytail 4.8.3): 13 live prompts × 2 seeds = 26 cells per arm on Haiku 4.5, billed output tokens vs the no-tool baseline ([writeup + raw cells](benchmarks/results/2026-10-01-live-rerun.md)):
|
|
240
|
+
|
|
241
|
+
| | total bill | 95% CI | visible answer | worst cell | backfires |
|
|
242
|
+
|---|--:|--:|--:|--:|--:|
|
|
243
|
+
| caveman | 102% | 79–128% | 105% | 305% | 15 / 26 |
|
|
244
|
+
| ponytail | 105% | 86–129% | 97% | 493% | 16 / 26 |
|
|
245
|
+
| **Chisle** | **83%** | **69–95%** | **76%** | **170%** | **11 / 26** |
|
|
246
|
+
|
|
247
|
+
Chisle is the only arm measurably below a bare model, by about a third of what the June table below claimed. It pays on long answers (77%) and coding prompts (76%); on short answers it breaks even (106%), on explanation prompts ponytail is leaner (86% vs 91%), and reasoning tokens are not cut (107%). Single cells swing hard between seeds (57% → 135% on one prompt), and the bare model "backfires" against itself on 13 of 26 cells, so read totals, not backfire counts ([control](benchmarks/results/2026-10-01-backfires.md)).
|
|
248
|
+
|
|
249
|
+
**Why the June headline was retired.** Its 52% leaned on one cell: the June bare `cache` answer billed 4,910 tokens, while every later run of the same prompt billed 375–813. The ruleset also carried examples matching `cache` (from June 18) and `auth-bug` (from June 27), so four cells may have been primed; without them the June total was 70%. The tables below stay as what they were.
|
|
250
|
+
|
|
251
|
+
### Output axis, June–July 2026 (superseded): vs caveman & ponytail, 20 live tasks
|
|
228
252
|
|
|
229
253
|
**What this suite measures, and what it does not.** Every figure below is *billed output tokens on single-turn prompts with no tools available*. That isolates the ruleset's effect on how the model writes, which is what it was built to measure. It is not whole-session cost: a real agentic session is dominated by tool output and cached input, so a tool can win here and still fail to pay for itself end to end. The input axis below is measured separately, and against its own baseline.
|
|
230
254
|
|
|
@@ -316,10 +340,10 @@ SUITE=large bash benchmarks/run-live.sh <model> benchmarks/results/raw-large
|
|
|
316
340
|
|
|
317
341
|
| | prose | code judgment | input/context | worst-case guard | publishes failures |
|
|
318
342
|
|---|:---:|:---:|:---:|:---:|:---:|
|
|
319
|
-
| caveman | ✅ | ❌ | ❌ | ❌
|
|
320
|
-
| ponytail | ❌ | ✅ | ❌ | ❌
|
|
343
|
+
| caveman | ✅ | ❌ | ❌ | ❌ 305% | ❌ |
|
|
344
|
+
| ponytail | ❌ | ✅ | ❌ | ❌ 493% | ❌ |
|
|
321
345
|
| headroom | ❌ | ❌ | ✅ proxy | n/a | ❌ |
|
|
322
|
-
| **Chisle** | ✅ | ✅ | ✅ hook | **
|
|
346
|
+
| **Chisle** | ✅ | ✅ | ✅ hook | **170%** | ✅ |
|
|
323
347
|
|
|
324
348
|
The row that matters is the last one. Every tool here looks good on its best day; the numbers above are the only ones in this class published alongside the run that went wrong. [Full comparison →](docs/comparison.md)
|
|
325
349
|
|
|
@@ -333,7 +357,7 @@ Measured over 171 real sessions ([receipts](benchmarks/results/2026-07-07-input-
|
|
|
333
357
|
| **elide** | oversized output → head + tail, error-like lines salvaged from the cut | bounded, guarded |
|
|
334
358
|
| **dedup** | byte-identical repeat of a tool's previous output (same session) → one-line marker | none, the copy is already in context |
|
|
335
359
|
|
|
336
|
-
Replayed over the same 171 Claude Code sessions: **~61k tokens** saved one-shot, ~46% off every eligible output: a floor, not an estimate, since each saved byte also stops being re-sent on every later request. Correctness rules: allowlist only (`Bash`, `Agent`, `WebFetch`, `WebSearch`, `Grep`, `Glob`, `mcp__*`), never `Read`/`Edit`, whose exact bytes feed later edits. Honest ledger: dedup scored **0 hits** on this corpus (rtk-filtered at source); it's kept for the test-rerun case, kill-switchable, and labeled speculative until it earns a number.
|
|
360
|
+
Replayed over the same 171 Claude Code sessions: **~61k tokens** saved one-shot, ~46% off every eligible output: a floor, not an estimate, since each saved byte also stops being re-sent on every later request. Correctness rules: allowlist only (`Bash`, `Agent`, `WebFetch`, `WebSearch`, `Grep`, `Glob`, `mcp__*`), never `Read`/`Edit`, whose exact bytes feed later edits. Successful calls only: Claude Code sends a failed call (a non-zero `Bash` exit included) to `PostToolUseFailure`, whose output a hook can add to but not replace (checked on 2.1.285), so a failing test run reaches the model through Claude Code's own ~10k-char head + tail truncation instead; the replay counts those as unreachable. Honest ledger: dedup scored **0 hits** on this corpus (rtk-filtered at source); it's kept for the test-rerun case, kill-switchable, and labeled speculative until it earns a number.
|
|
337
361
|
|
|
338
362
|
Pi already truncates built-in output at 50KB/2,000 lines. Replay over 8,044 persisted Pi results measured the compressor's **marginal** saving after that truncation: **27.4% of tool-output chars** ([receipt + raw live Pi arm](benchmarks/results/2026-09-11-pi.md)). Pi compresses `bash`, `powershell`, `grep`, `find`, `ls`, MCP, and explicitly allowlisted extension-tool results; `read`/`edit`/`write` remain untouched. `CHISLE_COMPRESS_TOOLS` supplies the same explicit override for runtime and replay. Runtime dedup only compares earlier turns, so concurrently completed sibling calls cannot dedup one another.
|
|
339
363
|
|
|
@@ -448,6 +472,41 @@ export CHISLE_DEFAULT_MODE=off
|
|
|
448
472
|
|
|
449
473
|
Resolution: env var → config file → `on`. Valid: `off`, `on`.
|
|
450
474
|
|
|
475
|
+
**Output styles are detected automatically.** Claude Code's output-style mechanism also governs
|
|
476
|
+
prose structure, so an active style and Chisle's prose section give directly conflicting
|
|
477
|
+
instructions — the style mandates tables and bullets for comparisons, Chisle's prose section forbids
|
|
478
|
+
manufactured tables and bullets the question didn't ask for. Chisle now reads the active style and
|
|
479
|
+
steps its prose rules aside on its own. Nothing to configure: run `/output-style Explanatory` and the
|
|
480
|
+
prose section stops shipping; switch back to `default` and it returns.
|
|
481
|
+
|
|
482
|
+
The code rules, the ladder, the context diet and the compressor are all unaffected — output styles
|
|
483
|
+
govern prose, so only the prose section yields.
|
|
484
|
+
|
|
485
|
+
Detection reads the `outputStyle` key across Claude Code's own settings precedence
|
|
486
|
+
(`.claude/settings.local.json`, where `/output-style` writes, then `.claude/settings.json`, then
|
|
487
|
+
`~/.claude/settings.json`). Claude Code only — Pi and OpenCode have no output-style mechanism, so
|
|
488
|
+
there is nothing to detect and nothing changes for them.
|
|
489
|
+
|
|
490
|
+
**Suppress a rule group manually.** The same `sections` key gives explicit control, and it always
|
|
491
|
+
beats detection — set `prose: true` to keep Chisle's prose rules even with a style active:
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{ "sections": { "prose": false } }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
`sections.prose: false` suppresses the prose rules; `sections.code: false` suppresses the code
|
|
498
|
+
rules. Config file only, no env override. Anything absent, malformed, or non-boolean falls back to
|
|
499
|
+
enabled, which is also what lets automatic detection apply — only an explicitly written boolean
|
|
500
|
+
overrides it. With no style active and nothing configured, the emitted ruleset is byte-identical to
|
|
501
|
+
before either feature existed. The always-on sections
|
|
502
|
+
(Persistence, Thinking Is Billed Too, Auto-Clarity, When NOT to be lazy, Boundaries) ship regardless
|
|
503
|
+
of either setting.
|
|
504
|
+
|
|
505
|
+
Scope: this only affects the runtime-hook agents that read `skills/chisle/SKILL.md` live — Claude
|
|
506
|
+
Code and Pi. The seven static-rule agents built by `scripts/build-rules.js` (Cursor, Windsurf,
|
|
507
|
+
Cline, Kiro, Codex, Gemini, Copilot) get flat files baked at build time with no runtime config to
|
|
508
|
+
read, so they can't honor this key.
|
|
509
|
+
|
|
451
510
|
---
|
|
452
511
|
|
|
453
512
|
## Prior art & what stacks with it
|
|
@@ -466,12 +525,12 @@ Chisle borrows the best published token-saving techniques and implements the one
|
|
|
466
525
|
|
|
467
526
|
## Multi-agent
|
|
468
527
|
|
|
469
|
-
Ships to eleven agents: Claude Code and Pi get both axes, live `/chisle` toggling, and a status badge; Cursor, Windsurf, Cline, Kiro, Codex, Gemini, and Copilot get the always-on ruleset;
|
|
528
|
+
Ships to eleven agents: Claude Code and Pi get both axes, live `/chisle` toggling, and a status badge; OpenCode gets both axes too — the global fenced ruleset, on-demand skills, and a native compression plugin — but no live toggle; Cursor, Windsurf, Cline, Kiro, Codex, Gemini, and Copilot get the always-on ruleset; Hermes gets the skills as `/chisle` commands. Per-agent static copies come from `scripts/build-rules.js`; Pi uses its package extension plus the Agent Skills standard. See [`docs/agent-portability.md`](./docs/agent-portability.md).
|
|
470
529
|
|
|
471
530
|
## FAQ
|
|
472
531
|
|
|
473
532
|
**Doesn't injecting a persona every turn cost tokens?**
|
|
474
|
-
Claude Code uses a ruleset at session start (~
|
|
533
|
+
Claude Code uses a ruleset at session start (~900 tokens) plus a ~50-token reminder per turn. Pi injects one persistent rules message only when project `AGENTS.md` does not already provide it; resume/reload does not duplicate it, and compaction restores it only if removed.
|
|
475
534
|
|
|
476
535
|
Worth reading the dissent before you take that on faith: [@enc0ded](https://github.com/enc0ded) measured 173 of their own sessions ([#2](https://github.com/JayPokale/Chisle/issues/2)) and found the injection overhead roughly cancelling the compressor's savings, because the ruleset was being re-sent on every resume and clear, not just at startup. That re-injection is fixed, which removes most of the overhead they measured, but their wider point stands: prose is only ~25% of what the model emits, so the ceiling on the output axis is lower than the headline suggests, and on a one-line throwaway prompt the overhead still exceeds the saving.
|
|
477
536
|
|
|
@@ -501,6 +560,16 @@ Built by [Jay Pokale](https://github.com/JayPokale) with [Claude](https://claude
|
|
|
501
560
|
npm test # 67 tests: flag safety, tracker, settings merge, installer, compressor
|
|
502
561
|
```
|
|
503
562
|
|
|
563
|
+
## Star History
|
|
564
|
+
|
|
565
|
+
<a href="https://www.star-history.com/?repos=JayPokale%2FChisle&type=date&legend=top-left">
|
|
566
|
+
<picture>
|
|
567
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=JayPokale/Chisle&type=date&legend=top-left&theme=dark">
|
|
568
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=JayPokale/Chisle&type=date&legend=top-left">
|
|
569
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=JayPokale/Chisle&type=date&legend=top-left">
|
|
570
|
+
</picture>
|
|
571
|
+
</a>
|
|
572
|
+
|
|
504
573
|
## License
|
|
505
574
|
|
|
506
575
|
[MIT](LICENSE). The shortest license that works.
|
package/hooks/chisle-activate.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const { getDefaultMode, getClaudeDir, safeWriteFlag, legacySetting } = require('./chisle-config');
|
|
12
|
+
const { getDefaultMode, getClaudeDir, safeWriteFlag, legacySetting, resolveSections, filterSections } = require('./chisle-config');
|
|
13
13
|
|
|
14
14
|
// ── Update-notice helpers (pure — tested directly) ─────────────────────────
|
|
15
15
|
// Minor/patch releases stay quiet: a nudge per major is signal, more is spam.
|
|
@@ -58,7 +58,7 @@ async function latestVersion(cachePath) {
|
|
|
58
58
|
// SessionStart fires on startup, resume, clear and compact. Only a genuinely
|
|
59
59
|
// new session needs the whole ruleset: on the other three the conversation
|
|
60
60
|
// already carries it, and the UserPromptSubmit reminder re-states the active
|
|
61
|
-
// behaviour every turn anyway. Re-sending
|
|
61
|
+
// behaviour every turn anyway. Re-sending the full ruleset each time was the bulk
|
|
62
62
|
// of the plugin's own overhead and piled standing instructions on top of what
|
|
63
63
|
// the user actually asked for. Measured and reported by @enc0ded (#2).
|
|
64
64
|
const FULL_INJECT_SOURCES = new Set(['startup']);
|
|
@@ -90,7 +90,9 @@ function run(source) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// 3. Read SKILL.md — single source of truth for behaviour. No level
|
|
93
|
-
// filtering any more: there is one mode, so the whole body ships
|
|
93
|
+
// filtering any more: there is one mode, so the whole body ships, minus
|
|
94
|
+
// whatever `sections` in config.json opts out of (prose and/or code rules;
|
|
95
|
+
// config-file only, defaults to both enabled — see chisle-config.js).
|
|
94
96
|
let skillContent = '';
|
|
95
97
|
try {
|
|
96
98
|
skillContent = fs.readFileSync(
|
|
@@ -98,14 +100,18 @@ function run(source) {
|
|
|
98
100
|
);
|
|
99
101
|
} catch (e) {}
|
|
100
102
|
|
|
103
|
+
// Yields the prose section to an active Claude Code output style, which
|
|
104
|
+
// governs the same thing and would otherwise contradict it (#15/#17).
|
|
105
|
+
// An explicit sections.prose in config.json still wins.
|
|
106
|
+
const sections = resolveSections({ cwd: process.cwd(), claudeDir });
|
|
101
107
|
let output;
|
|
102
108
|
|
|
103
109
|
if (skillContent) {
|
|
104
|
-
output = 'CHISLE ACTIVE\n\n' +
|
|
110
|
+
output = 'CHISLE ACTIVE\n\n' +
|
|
111
|
+
filterSections(skillContent.replace(/^---[\s\S]*?---\s*/, ''), sections);
|
|
105
112
|
} else {
|
|
106
113
|
// Fallback ruleset when SKILL.md not found
|
|
107
|
-
|
|
108
|
-
'CHISLE ACTIVE\n\n' +
|
|
114
|
+
const fallbackBody =
|
|
109
115
|
'Chisle: maximum-efficiency dev mode. Zero-fluff prose. YAGNI-first code.\n\n' +
|
|
110
116
|
'## Persistence\n\n' +
|
|
111
117
|
'ACTIVE EVERY RESPONSE. Off only: "stop chisle" / "normal mode".\n\n' +
|
|
@@ -114,6 +120,7 @@ function run(source) {
|
|
|
114
120
|
'## Code\n\n' +
|
|
115
121
|
'Ladder: YAGNI → reuse → stdlib → native → installed dep → one line → min code.\n' +
|
|
116
122
|
'No unrequested abstractions. Deletion over addition. Shortest diff wins.';
|
|
123
|
+
output = 'CHISLE ACTIVE\n\n' + filterSections(fallbackBody, sections, ['Prose'], ['Code']);
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
// 3. Detect missing statusline config
|
package/hooks/chisle-config.js
CHANGED
|
@@ -92,6 +92,165 @@ function getDefaultMode() {
|
|
|
92
92
|
return 'on';
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// ── Section suppression ─────────────────────────────────────────────────────
|
|
96
|
+
// Optional config key letting a user drop Chisle's prose rules or code rules
|
|
97
|
+
// independently, while keeping the other. Config file only — no env override,
|
|
98
|
+
// unlike defaultMode. Absent, malformed, or non-boolean always falls back to
|
|
99
|
+
// enabled (true); never throws.
|
|
100
|
+
// { "sections": { "prose": false } } // suppress prose rules, keep code
|
|
101
|
+
|
|
102
|
+
// Headings from skills/chisle/SKILL.md, grouped by section.
|
|
103
|
+
const PROSE_HEADINGS = [
|
|
104
|
+
'Prose: Maximum Signal Per Token',
|
|
105
|
+
'Output Format',
|
|
106
|
+
];
|
|
107
|
+
const CODE_HEADINGS = [
|
|
108
|
+
'Code: The Efficiency Ladder',
|
|
109
|
+
'Code Rules',
|
|
110
|
+
'Context Diet: Read Less Into the Window',
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
function readSectionsFromConfigFile(configPath) {
|
|
114
|
+
try {
|
|
115
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
116
|
+
const config = JSON.parse(raw);
|
|
117
|
+
const s = config && typeof config === 'object' ? config.sections : null;
|
|
118
|
+
return {
|
|
119
|
+
prose: !!(s && typeof s === 'object' && typeof s.prose === 'boolean' ? s.prose : true),
|
|
120
|
+
code: !!(s && typeof s === 'object' && typeof s.code === 'boolean' ? s.code : true),
|
|
121
|
+
};
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return { prose: true, code: true };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getSections() {
|
|
128
|
+
return readSectionsFromConfigFile(path.join(getConfigDir(), 'config.json'));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Drops whole `## Heading` sections (heading line through the line before the
|
|
132
|
+
// next `## ` heading) whose heading is in an excluded group. Byte-identical
|
|
133
|
+
// to `content` when nothing is excluded. Never throws — falls back to the
|
|
134
|
+
// unfiltered content. Assumes no `## ` line appears inside a fenced code
|
|
135
|
+
// block (true for SKILL.md and the fallback ruleset at 07c603b).
|
|
136
|
+
function filterSections(content, sections, proseHeadings, codeHeadings) {
|
|
137
|
+
try {
|
|
138
|
+
if (typeof content !== 'string' || !content) return content;
|
|
139
|
+
const prose = sections ? sections.prose !== false : true;
|
|
140
|
+
const code = sections ? sections.code !== false : true;
|
|
141
|
+
if (prose && code) return content;
|
|
142
|
+
|
|
143
|
+
const dropProse = proseHeadings || PROSE_HEADINGS;
|
|
144
|
+
const dropCode = codeHeadings || CODE_HEADINGS;
|
|
145
|
+
|
|
146
|
+
const chunks = content.split(/(?=^## .+$)/m);
|
|
147
|
+
const kept = chunks.filter((chunk) => {
|
|
148
|
+
const m = /^## (.+)$/m.exec(chunk);
|
|
149
|
+
if (!m) return true; // preamble chunk (no heading)
|
|
150
|
+
const heading = m[1].trim();
|
|
151
|
+
if (!prose && dropProse.includes(heading)) return false;
|
|
152
|
+
if (!code && dropCode.includes(heading)) return false;
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
return kept.join('');
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return content;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Output-style detection ──────────────────────────────────────────────────
|
|
162
|
+
// Claude Code's output-style mechanism also dictates prose structure, so an
|
|
163
|
+
// active style and Chisle's prose section issue contradicting instructions with
|
|
164
|
+
// no precedence between them (#15). `outputStyle` is a documented settings key
|
|
165
|
+
// (schemastore: claude-code-settings.json), and `/output-style` persists the
|
|
166
|
+
// selection to .claude/settings.local.json at the local project level, so the
|
|
167
|
+
// active style IS readable — it just isn't in the SessionStart payload.
|
|
168
|
+
//
|
|
169
|
+
// Kept dependency-free on purpose: this file is copied standalone into
|
|
170
|
+
// ~/.config/opencode/plugins/chisle-hooks/, so it must not require anything
|
|
171
|
+
// outside itself.
|
|
172
|
+
|
|
173
|
+
// Claude Code parses settings.json with strict JSON, but users do leave
|
|
174
|
+
// comments in it. Tolerate that rather than silently reading no style.
|
|
175
|
+
function parseJsonLoose(raw) {
|
|
176
|
+
try { return JSON.parse(raw.replace(/^\uFEFF/, '')); } catch (e) {}
|
|
177
|
+
try {
|
|
178
|
+
const stripped = raw
|
|
179
|
+
.replace(/^\uFEFF/, '')
|
|
180
|
+
.replace(/"(?:[^"\\]|\\.)*"|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g,
|
|
181
|
+
(m) => (m[0] === '"' ? m : ' '));
|
|
182
|
+
return JSON.parse(stripped);
|
|
183
|
+
} catch (e) { return null; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function readJsonFile(p) {
|
|
187
|
+
try {
|
|
188
|
+
const st = fs.lstatSync(p);
|
|
189
|
+
if (st.isSymbolicLink() || !st.isFile()) return null;
|
|
190
|
+
return parseJsonLoose(fs.readFileSync(p, 'utf8'));
|
|
191
|
+
} catch (e) { return null; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Claude Code's own precedence, most specific first. A style set for this
|
|
195
|
+
// project must beat a different one set globally.
|
|
196
|
+
function outputStyleSources(opts) {
|
|
197
|
+
const o = opts || {};
|
|
198
|
+
const cwd = o.cwd || process.cwd();
|
|
199
|
+
const claudeDir = o.claudeDir || getClaudeDir();
|
|
200
|
+
return [
|
|
201
|
+
path.join(cwd, '.claude', 'settings.local.json'),
|
|
202
|
+
path.join(cwd, '.claude', 'settings.json'),
|
|
203
|
+
path.join(claudeDir, 'settings.json'),
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The active output style, or null when none is. "default" is the built-in
|
|
208
|
+
// no-op style, so it counts as none.
|
|
209
|
+
function getActiveOutputStyle(opts) {
|
|
210
|
+
try {
|
|
211
|
+
for (const file of outputStyleSources(opts)) {
|
|
212
|
+
const json = readJsonFile(file);
|
|
213
|
+
if (!json || typeof json !== 'object') continue;
|
|
214
|
+
const style = json.outputStyle;
|
|
215
|
+
if (typeof style !== 'string') continue;
|
|
216
|
+
const trimmed = style.trim();
|
|
217
|
+
if (!trimmed) continue;
|
|
218
|
+
return trimmed.toLowerCase() === 'default' ? null : trimmed;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
} catch (e) { return null; }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// What the user wrote, distinguishing "absent" from "explicitly true". Only an
|
|
225
|
+
// absent value may be overridden by output-style detection.
|
|
226
|
+
function explicitSections() {
|
|
227
|
+
try {
|
|
228
|
+
const json = readJsonFile(path.join(getConfigDir(), 'config.json'));
|
|
229
|
+
const s = json && typeof json === 'object' ? json.sections : null;
|
|
230
|
+
if (!s || typeof s !== 'object') return {};
|
|
231
|
+
const out = {};
|
|
232
|
+
if (typeof s.prose === 'boolean') out.prose = s.prose;
|
|
233
|
+
if (typeof s.code === 'boolean') out.code = s.code;
|
|
234
|
+
return out;
|
|
235
|
+
} catch (e) { return {}; }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Final prose/code decision for the Claude Code hooks. Explicit config always
|
|
239
|
+
// wins — a user who wrote `sections.prose: true` has said they want the prose
|
|
240
|
+
// rules even alongside a style, and this must not overrule that. Only an unset
|
|
241
|
+
// prose key yields to an active style. `code` is never touched: output styles
|
|
242
|
+
// govern prose structure, not the efficiency ladder.
|
|
243
|
+
//
|
|
244
|
+
// Pi has no output-style mechanism, so pi-extension keeps calling getSections().
|
|
245
|
+
function resolveSections(opts) {
|
|
246
|
+
const explicit = explicitSections();
|
|
247
|
+
const base = getSections();
|
|
248
|
+
if (explicit.prose !== undefined) return { ...base, outputStyle: null };
|
|
249
|
+
const style = getActiveOutputStyle(opts);
|
|
250
|
+
if (!style) return { ...base, outputStyle: null };
|
|
251
|
+
return { prose: false, code: base.code, outputStyle: style };
|
|
252
|
+
}
|
|
253
|
+
|
|
95
254
|
// Symlink-safe, atomic flag write with 0600 perms.
|
|
96
255
|
// Defends against local attacker replacing predictable path with symlink to clobber other files.
|
|
97
256
|
function safeWriteFlag(flagPath, content) {
|
|
@@ -193,4 +352,6 @@ function readFlag(flagPath) {
|
|
|
193
352
|
module.exports = {
|
|
194
353
|
LEGACY_MODES, legacySetting, getDefaultMode, getClaudeDir, getCopilotDir, getOpencodeDir,
|
|
195
354
|
VALID_MODES, safeWriteFlag, readFlag,
|
|
355
|
+
PROSE_HEADINGS, CODE_HEADINGS, getSections, filterSections,
|
|
356
|
+
getActiveOutputStyle, explicitSections, resolveSections,
|
|
196
357
|
};
|
|
@@ -5,9 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const { getClaudeDir, safeWriteFlag, readFlag } = require('./chisle-config');
|
|
8
|
+
const { getClaudeDir, safeWriteFlag, readFlag, resolveSections } = require('./chisle-config');
|
|
9
9
|
const { requestedMode } = require('./chisle-mode');
|
|
10
10
|
|
|
11
|
+
// Per-turn reinforcement clauses. `Code/commits/security: write normal.` is
|
|
12
|
+
// the Boundaries carve-out (always-on in SKILL.md) and ships regardless of
|
|
13
|
+
// `sections`; the prose/code clauses honor it the same way the SessionStart
|
|
14
|
+
// ruleset does.
|
|
15
|
+
const PROSE_CLAUSE = 'Prose: drop articles/filler/pleasantries/hedging. Fragments OK.';
|
|
16
|
+
const CODE_CLAUSE = 'Code: YAGNI ladder first (reuse → stdlib → native → dep → one line → min code).';
|
|
17
|
+
const BOUNDARIES_CLAUSE = 'Code/commits/security: write normal.';
|
|
18
|
+
|
|
19
|
+
function reinforcementLine(sections) {
|
|
20
|
+
const prose = sections ? sections.prose !== false : true;
|
|
21
|
+
const code = sections ? sections.code !== false : true;
|
|
22
|
+
const parts = ['CHISLE ACTIVE.'];
|
|
23
|
+
if (prose) parts.push(PROSE_CLAUSE);
|
|
24
|
+
if (code) parts.push(CODE_CLAUSE);
|
|
25
|
+
parts.push(BOUNDARIES_CLAUSE);
|
|
26
|
+
return parts.join(' ');
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
const claudeDir = getClaudeDir();
|
|
12
30
|
const flagPath = path.join(claudeDir, '.chisle-active');
|
|
13
31
|
|
|
@@ -32,11 +50,7 @@ process.stdin.on('end', () => {
|
|
|
32
50
|
process.stdout.write(JSON.stringify({
|
|
33
51
|
hookSpecificOutput: {
|
|
34
52
|
hookEventName: 'UserPromptSubmit',
|
|
35
|
-
additionalContext:
|
|
36
|
-
'CHISLE ACTIVE. ' +
|
|
37
|
-
'Prose: drop articles/filler/pleasantries/hedging. Fragments OK. ' +
|
|
38
|
-
'Code: YAGNI ladder first (reuse → stdlib → native → dep → one line → min code). ' +
|
|
39
|
-
'Code/commits/security: write normal.'
|
|
53
|
+
additionalContext: reinforcementLine(resolveSections({ cwd: process.cwd(), claudeDir }))
|
|
40
54
|
}
|
|
41
55
|
}));
|
|
42
56
|
}
|
package/package.json
CHANGED
package/pi-extension/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
-
const { getDefaultMode } = require('../hooks/chisle-config');
|
|
6
|
+
const { getDefaultMode, getSections, filterSections } = require('../hooks/chisle-config');
|
|
7
7
|
const { duplicateMarker, limitsFor, transform } = require('../hooks/chisle-compress-output');
|
|
8
8
|
const { requestedMode } = require('../hooks/chisle-mode');
|
|
9
9
|
|
|
@@ -12,20 +12,52 @@ const MODE_TYPE = 'chisle-mode';
|
|
|
12
12
|
const SAFE_TOOLS = new Set(['bash', 'powershell', 'grep', 'find', 'ls']);
|
|
13
13
|
const NEVER_COMPRESS = new Set(['read', 'edit', 'write']);
|
|
14
14
|
const DEDUP_MIN = 2048;
|
|
15
|
-
|
|
15
|
+
// Sectioned so the `sections` config applies here too. Flat prose/code lines
|
|
16
|
+
// would leave the fallback ignoring a user's sections.prose:false the moment
|
|
17
|
+
// SKILL.md became unreadable — chisle-activate.js's fallback is shaped the
|
|
18
|
+
// same way for the same reason. The safety line stays outside both groups.
|
|
19
|
+
const FALLBACK_BODY = `Chisle: maximum-efficiency dev mode. Zero-fluff prose. YAGNI-first code.
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
## Prose
|
|
18
22
|
|
|
19
23
|
Drop articles, filler, pleasantries, and hedging. Fragments OK; technical terms exact.
|
|
24
|
+
|
|
25
|
+
## Code
|
|
26
|
+
|
|
20
27
|
Code ladder: YAGNI → reuse → stdlib → native → installed dependency → one line → minimum code.
|
|
28
|
+
|
|
29
|
+
## Boundaries
|
|
30
|
+
|
|
21
31
|
Never simplify away input validation, data-loss prevention, security, accessibility, or explicit requirements.`;
|
|
22
32
|
|
|
23
33
|
function loadRules() {
|
|
24
34
|
try {
|
|
25
35
|
const skill = fs.readFileSync(path.join(__dirname, '..', 'skills', 'chisle', 'SKILL.md'), 'utf8');
|
|
26
|
-
|
|
36
|
+
// Shares the SKILL.md emission path with chisle-activate.js, so it honors
|
|
37
|
+
// the same `sections` config (prose/code rule suppression) the same way.
|
|
38
|
+
return 'CHISLE ACTIVE\n\n' +
|
|
39
|
+
filterSections(skill.replace(/^---[\s\S]*?---\s*/, ''), getSections());
|
|
40
|
+
} catch (_) {
|
|
41
|
+
return 'CHISLE ACTIVE\n\n' +
|
|
42
|
+
filterSections(FALLBACK_BODY, getSections(), ['Prose'], ['Code']);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// OMP (oh-my-pi) ships a subset of Pi's sessionManager: buildContextEntries and
|
|
47
|
+
// getBranch are absent there, so calling one throws inside session_start and
|
|
48
|
+
// takes the whole extension down —
|
|
49
|
+
// "ctx.sessionManager.buildContextEntries is not a function" (issue #14).
|
|
50
|
+
// Treat both as optional readers. An absent one means we cannot prove the
|
|
51
|
+
// session state, and every caller here already behaves correctly on "unknown":
|
|
52
|
+
// no mode entry leaves `active` at getDefaultMode(), and rulesInContext=false
|
|
53
|
+
// re-injects the ruleset, which promptHasRules still de-duplicates.
|
|
54
|
+
function sessionEntries(ctx, method) {
|
|
55
|
+
try {
|
|
56
|
+
const sm = ctx && ctx.sessionManager;
|
|
57
|
+
if (!sm || typeof sm[method] !== 'function') return [];
|
|
58
|
+
return sm[method]() || [];
|
|
27
59
|
} catch (_) {
|
|
28
|
-
return
|
|
60
|
+
return [];
|
|
29
61
|
}
|
|
30
62
|
}
|
|
31
63
|
|
|
@@ -77,12 +109,12 @@ function chisleExtension(pi) {
|
|
|
77
109
|
lastByToolBlock = new Map();
|
|
78
110
|
dedupBaseline = new Map();
|
|
79
111
|
|
|
80
|
-
const modeEntry = ctx
|
|
112
|
+
const modeEntry = sessionEntries(ctx, 'getBranch')
|
|
81
113
|
.filter(entry => entry.type === 'custom' && entry.customType === MODE_TYPE).pop();
|
|
82
114
|
if (modeEntry && modeEntry.data && ['on', 'off'].includes(modeEntry.data.mode)) {
|
|
83
115
|
active = modeEntry.data.mode === 'on';
|
|
84
116
|
}
|
|
85
|
-
rulesInContext = hasRulesEntry(ctx
|
|
117
|
+
rulesInContext = hasRulesEntry(sessionEntries(ctx, 'buildContextEntries'));
|
|
86
118
|
updateStatus(ctx);
|
|
87
119
|
}
|
|
88
120
|
|
|
@@ -116,7 +148,7 @@ function chisleExtension(pi) {
|
|
|
116
148
|
pi.on('session_compact', async (_event, ctx) => {
|
|
117
149
|
lastByToolBlock.clear();
|
|
118
150
|
dedupBaseline.clear();
|
|
119
|
-
rulesInContext = hasRulesEntry(ctx
|
|
151
|
+
rulesInContext = hasRulesEntry(sessionEntries(ctx, 'buildContextEntries'));
|
|
120
152
|
});
|
|
121
153
|
|
|
122
154
|
pi.on('input', async (event, ctx) => {
|
|
@@ -185,4 +217,5 @@ function chisleExtension(pi) {
|
|
|
185
217
|
module.exports = chisleExtension;
|
|
186
218
|
module.exports.loadRules = loadRules;
|
|
187
219
|
module.exports.hasRulesEntry = hasRulesEntry;
|
|
220
|
+
module.exports.sessionEntries = sessionEntries;
|
|
188
221
|
module.exports.piToolAllowed = piToolAllowed;
|
package/skills/chisle/SKILL.md
CHANGED
|
@@ -1,142 +1,75 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chisle
|
|
3
3
|
description: >
|
|
4
|
-
Maximum-efficiency dev mode
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Maximum-efficiency dev mode: terse zero-fluff prose + YAGNI/ladder-first code.
|
|
5
|
+
Use when user says "chisle mode", "activate chisle", "chislify", "be efficient",
|
|
6
|
+
"be minimal", "no fluff", "yagni", or invokes /chisle.
|
|
7
7
|
Deactivate: "stop chisle" / "normal mode".
|
|
8
|
-
Use when user says "chisle mode", "activate chisle", "chislify", "be efficient", "be minimal",
|
|
9
|
-
"no fluff", "yagni", or invokes /chisle.
|
|
10
8
|
---
|
|
11
9
|
|
|
12
10
|
# Chisle
|
|
13
11
|
|
|
14
|
-
Maximum signal. Minimum noise.
|
|
12
|
+
Maximum signal. Minimum noise.
|
|
15
13
|
|
|
16
14
|
## Persistence
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
Off only: "stop chisle" / "normal mode" / `/chisle off`. One mode, no levels to pick.
|
|
16
|
+
Active every response, even if unsure. Off only: "stop chisle" / "normal mode" / `/chisle off`.
|
|
20
17
|
|
|
21
18
|
## Prose: Maximum Signal Per Token
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
simply), pleasantries (sure/certainly/of course/happy to), hedging, and linking verbs
|
|
25
|
-
where meaning survives. One word over a phrase. Short synonyms (big not extensive). Show
|
|
26
|
-
causality with arrows (X → Y) instead of "because/therefore/which means". Standard
|
|
27
|
-
acronyms fine (DB/API/HTTP); never invent new ones. Technical terms, code, API names,
|
|
28
|
-
error strings: exact, verbatim, never abbreviated. Code blocks unchanged.
|
|
20
|
+
Fragments. Drop articles, filler, pleasantries, hedging. Causality as arrows (X → Y). Technical terms, code, API names, errors: exact, verbatim. Code blocks unchanged.
|
|
29
21
|
|
|
30
|
-
**Terse ≠ incomplete
|
|
31
|
-
gotcha, the caveat, the why. Cut the words *around* the facts, never the facts. A 3-word
|
|
32
|
-
answer that omits the fix loses to a 12-word one that keeps it. This is the edge: say
|
|
33
|
-
everything that matters, in the fewest tokens that still say it.
|
|
22
|
+
**Terse ≠ incomplete.** Keep every decisive fact: the fix, the gotcha, the caveat, the why. Cut words around facts, never facts.
|
|
34
23
|
|
|
35
|
-
**Structure is tokens.** Answer at the question's altitude.
|
|
36
|
-
bullet lists, numbered steps, "Pick A if / B if" scaffolding, recaps, or decorative
|
|
37
|
-
tables/emoji the question didn't ask for. Two tight paragraphs beat five headed sections.
|
|
38
|
-
"Summarize/compare X vs Y" is the trap: headed pro/con bullet walls triple the size
|
|
39
|
-
(measured: one such answer ran 173% of a no-tool baseline). Name the two or three
|
|
40
|
-
decisive tradeoffs in prose, give the verdict, stop.
|
|
24
|
+
**Structure is tokens.** Answer at the question's altitude: no headings, bullets, numbered steps, tables or recaps it didn't ask for. Two tight paragraphs beat five headed sections. "Compare X vs Y": decisive tradeoffs in prose, verdict, stop. Never announce the mode.
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Not: "
|
|
45
|
-
Yes: "
|
|
46
|
-
Not: "A deadlock is a situation where two or more threads are each waiting..."
|
|
47
|
-
Yes: "Deadlock: two threads each hold a lock the other needs → both wait forever. Fix: consistent lock order."
|
|
26
|
+
Not: "Sure! I'd be happy to help. The issue you're seeing is likely caused by..."
|
|
27
|
+
Yes: "Timestamp off by hours: `toISOString()` is UTC, UI wants local. Fix:"
|
|
28
|
+
Not: "A content delivery network is a geographically distributed group of servers that..."
|
|
29
|
+
Yes: "CDN: copies of static files on servers near users → shorter round trips, less origin load. Cost: stale content, so set cache headers."
|
|
48
30
|
|
|
49
31
|
## Code: The Efficiency Ladder
|
|
50
32
|
|
|
51
|
-
Stop at first rung that holds:
|
|
52
|
-
|
|
53
|
-
1. **Does this need to exist at all?** Speculative need = skip it, say so in one line. (YAGNI)
|
|
54
|
-
2. **Already in this codebase?** Reuse it. Look before writing; re-implementing what's nearby is most common slop.
|
|
55
|
-
3. **Stdlib does it?** Use it.
|
|
56
|
-
4. **Native platform feature covers it?** `<input type="date">` over picker lib, CSS over JS, DB constraint over app code.
|
|
57
|
-
5. **Already-installed dependency solves it?** Use it. Never add new dep for what few lines can do.
|
|
58
|
-
6. **Can it be one line?** One line.
|
|
59
|
-
7. **Only then:** minimum code that works.
|
|
33
|
+
Stop at the first rung that holds:
|
|
60
34
|
|
|
61
|
-
|
|
35
|
+
1. Needs to exist at all? No → skip, say so in one line.
|
|
36
|
+
2. Already in this codebase? Reuse.
|
|
37
|
+
3. Stdlib does it?
|
|
38
|
+
4. Native platform feature? CSS over JS, DB constraint over app code.
|
|
39
|
+
5. Installed dependency? Use it. Never add a dep for a few lines.
|
|
40
|
+
6. One line?
|
|
41
|
+
7. Only then: minimum code that works.
|
|
62
42
|
|
|
63
|
-
|
|
43
|
+
Ladder runs after understanding the problem, never instead. Bug fix = root cause, not symptom.
|
|
64
44
|
|
|
65
45
|
## Thinking Is Billed Too
|
|
66
46
|
|
|
67
|
-
Reasoning
|
|
68
|
-
|
|
69
|
-
The ladder is a stopping rule, not a checklist to walk aloud. Stop at the first rung that holds and do not re-derive the rungs above it, weigh alternatives already excluded, or draft an answer twice to pick the shorter. Obvious fix → give it. Match deliberation to the stakes: a one-line change does not get a design review.
|
|
70
|
-
|
|
71
|
-
This is the ladder's own exception, unchanged: never think less about *understanding* the problem. Root-cause a bug, read what you are about to edit, resolve genuine ambiguity. Depth where the problem is actually hard, nowhere else.
|
|
47
|
+
Reasoning costs the same as output. The ladder is a stopping rule, not a checklist to walk aloud: don't re-derive excluded rungs or draft twice. Obvious fix → give it. Never think less about understanding: root-cause bugs, read what you edit.
|
|
72
48
|
|
|
73
49
|
## Code Rules
|
|
74
50
|
|
|
75
|
-
- No unrequested abstractions
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- Mark deliberate simplifications: `// chisle: global lock, per-account locks if throughput matters`.
|
|
81
|
-
- Non-trivial logic leaves ONE runnable check: smallest thing that fails if logic breaks. No frameworks unless asked.
|
|
51
|
+
- No unrequested abstractions, no boilerplate "for later". Deletion over addition, boring over clever.
|
|
52
|
+
- Fewest files, shortest working diff.
|
|
53
|
+
- Big request, unproven need → ship the lazy version, ask before building the full one.
|
|
54
|
+
- Mark deliberate shortcuts: `// chisle: global lock; per-account if throughput matters`.
|
|
55
|
+
- Non-trivial logic leaves one runnable check, smallest that fails if the logic breaks.
|
|
82
56
|
|
|
83
57
|
## Context Diet: Read Less Into the Window
|
|
84
58
|
|
|
85
|
-
Tool output
|
|
86
|
-
|
|
87
|
-
- Grep/search for the symbol first; Read only the matching region (offset/limit), not the whole file. Whole-file reads only when the whole file is the task.
|
|
88
|
-
- Narrow at the source: `ls dir` not `ls -R`, `git log --oneline -10` not `git log`, pipe long output through `| tail -50` / `| grep pattern`.
|
|
89
|
-
- Never re-read a file already in context unless it changed.
|
|
90
|
-
- Big-output commands (builds, test suites, installs): filter to failures/summary, not full logs.
|
|
91
|
-
|
|
92
|
-
Exception, same as the ladder: never skim what you're about to edit or debug. Diet trims *transport*, never *understanding*.
|
|
59
|
+
Tool output re-bills every later turn. Grep for the symbol, then Read with offset/limit; whole file only when the whole file is the task. Narrow at the source (`ls dir`, `git log --oneline -10`, `| tail -50`). Builds, tests, installs: failures and summary only. Never re-read an unchanged file. Never skim what you're about to edit.
|
|
93
60
|
|
|
94
61
|
## Output Format
|
|
95
62
|
|
|
96
|
-
Code first
|
|
97
|
-
Pattern: `[code] → skipped: [X], add when [Y].`
|
|
98
|
-
|
|
99
|
-
No essays. No feature tours. No design notes. Explanation longer than code → delete explanation.
|
|
100
|
-
|
|
101
|
-
## What it sounds like
|
|
102
|
-
|
|
103
|
-
Example: "Why does this React component re-render?"
|
|
104
|
-
> New object ref each render. Inline object prop = new ref = re-render. `useMemo`.
|
|
105
|
-
|
|
106
|
-
Example: "Add a cache for API responses."
|
|
107
|
-
> `@lru_cache(maxsize=1000)` on the fetch fn. Skipped a custom cache class; add one when `lru_cache` measurably falls short.
|
|
108
|
-
|
|
109
|
-
Example: "Summarize the tradeoffs between REST and GraphQL."
|
|
110
|
-
> 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.
|
|
63
|
+
Code first, then at most three short lines: `skipped: X, add when Y`. Explanation longer than the code → cut it.
|
|
111
64
|
|
|
112
65
|
## Auto-Clarity
|
|
113
66
|
|
|
114
|
-
|
|
115
|
-
- Security warnings
|
|
116
|
-
- Irreversible action confirmations
|
|
117
|
-
- Multi-step sequences where fragment order risks misread
|
|
118
|
-
- Compression creates technical ambiguity
|
|
119
|
-
- User asks to clarify or repeats question
|
|
120
|
-
|
|
121
|
-
Resume after clear part done.
|
|
122
|
-
|
|
123
|
-
Example, destructive op:
|
|
124
|
-
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
|
|
125
|
-
> ```sql
|
|
126
|
-
> DROP TABLE users;
|
|
127
|
-
> ```
|
|
128
|
-
> Verify backup exist first.
|
|
67
|
+
Write in full for security warnings, irreversible actions, order-sensitive steps, anything compression makes ambiguous, or a repeated question. Then resume.
|
|
129
68
|
|
|
130
69
|
## When NOT to be lazy
|
|
131
70
|
|
|
132
|
-
Never simplify away:
|
|
133
|
-
data loss, security measures, accessibility basics, anything explicitly requested.
|
|
134
|
-
User insists on full version → build it, no re-arguing.
|
|
135
|
-
|
|
136
|
-
Never lazy about understanding. Ladder shortens solution, never the reading.
|
|
71
|
+
Never simplify away: validation at trust boundaries, error handling that prevents data loss, security, accessibility, anything explicitly requested. User insists on the full version → build it, no re-arguing.
|
|
137
72
|
|
|
138
73
|
## Boundaries
|
|
139
74
|
|
|
140
|
-
Code
|
|
141
|
-
|
|
142
|
-
Shortest path to done. Fewest words to say it.
|
|
75
|
+
Code, commits, PRs: write normal. "stop chisle" / "normal mode": revert.
|