acp-kernel 0.0.99-pr.491.350 → 0.0.99-pr.494.352

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/DESIGN.md CHANGED
@@ -260,17 +260,12 @@ Copyright protects _expression_, not ideas, methods, or algorithms (17 USC §102
260
260
 
261
261
  `prune` rebuilds the visible view every turn: messages covered by active blocks are dropped, and each active block's rendered summary (`[Compressed conversation section]`, id `acp_summary_bN`) is inserted at the block's earliest covered position (pair-safe, per `pairSafeAnchorIndex`). One behavior of the rebuilt view is a load-bearing contract for downstream consumers:
262
262
 
263
- ### 8.1 First-user-message pin (conditional since #489)
263
+ ### 8.1 First-user-message pin
264
264
 
265
- The contract is a **property**, not a specific-message guarantee: the rebuilt wire retains a **leading user message** whenever one can lead — strict providers reject conversations with no user message or that start from an assistant. An _uncovered_ first user survives prune via the ordinary covered check anyway; the pin only bites when the first user is **covered by an active block**, and then only conditionally (`firstUserDropSafe`, `src/prune.ts`):
265
+ The session's **first user message survives prune unconditionally** — even when it is covered by an active block. In `rebuildMessages` (`src/prune.ts`) the pin check runs _before_ the covered-by-active-block check, so after compressing a range that includes the first user message, the rebuilt wire contains **both** the rendered summary **and** the first user message verbatim; all other covered messages drop as usual. Only the _first_ user message is pinned — later covered user messages are pruned normally.
266
266
 
267
- - **Drop-safe → dropped.** When the earliest message that would survive _after_ the first user (non-covered, non-rendered-summary) is itself a user, the covered first user drops like any other covered message. This covers both #489 trigger shapes: a divergent resend that shifts an earlier-folded message into the first-user slot, and a direct fold of the opening message. Its coverage now takes effect — the payload stops re-sending while its block stays active.
268
- - **Degenerate → pinned (residual limitation).** When no user would lead after it (the earliest survivor is an assistant/tool message, or nothing survives), the covered first user stays pinned verbatim next to its rendered summary, exactly as before #489. Dropping it there would hand the provider an assistant-led or user-less conversation; the cost is the pre-existing one (payload re-sends while the block covers it).
269
-
270
- Pre-#489 the pin was **unconditional**: it ran before the covered check for every covered first user. Combined with `syncBlocks` keeping the block active (its base id is present in the pre-prune view by definition), this voided fold coverage whenever a covered message became the view's first user — the fold reported success but the payload re-sent itself verbatim on every turn (#489, found analyzing billion-context#1869). The fix keeps the property, drops the over-guarantee.
271
-
272
- - **Why:** deliberate since prune's first implementation (v0.0.2) — protecting the leading-user property is part of the contract, not incidental. What changed in #489 is _which_ message carries the property: the property now moves to the earliest surviving user instead of being welded to the specific first message. Regression-tested by `tests/state-prune.test.ts` ("prune drops covered first user when another user survives", "prune keeps covered first user when no user survives behind"), `tests/orphan-fixes.test.ts` ("prune: first user message pruned when covered (no duplication)" — degenerate assistant-led shape), and `tests/regression-first-user-pin.test.ts` (#489 pipeline A/B, both trigger shapes), and `tests/issue1476-reid.test.ts` ("first-user pinned echo keeps its id and ref across post-fold passes" — degenerate shape, pinned echo id/ref stability across passes).
267
+ - **Why:** strict providers reject conversations with no user message (e.g., Anthropic requires the conversation to start from the user role). Pinning guarantees the rebuilt wire retains at least one user message whenever the input had one. Deliberate since prune's first implementation (v0.0.2) — the ordering of the two checks is part of the contract, not incidental. Regression-tested by `tests/state-prune.test.ts` ("prune preserves first user message even when covered") and `tests/orphan-fixes.test.ts` ("prune: first user message pruned when covered (no duplication)").
273
268
  - **Consequences for consumers:**
274
- - Consumers may now observe byte-level disappearance of the first user message after compression whenever another user message survives behind it; post-compress wire assertions must expect the rendered summary alone in that case. Token-size and prefix-cache estimates should drop the pinned-message allowance for those sessions.
275
- - Degenerate shapes (assistant-led survivors) still carry the pinned message verbatim next to its summary — estimates must keep the allowance there.
276
- - Boundary resolution is unchanged: `blockVisibleInRange` (`src/boundaries.ts`) treats a block as present in a range via its rendered summary **or** its earliest surviving raw — still required for the degenerate pinned shape, where the summary anchors _before_ the pinned raw.
269
+ - Consumers cannot assert byte-level disappearance of the first user message after compression; post-compress wire assertions must expect the rendered summary **plus** the pinned message verbatim.
270
+ - Token-size and prefix-cache stability estimates must account for the pinned message staying verbatim (it is often the task description and can be long).
271
+ - Boundary resolution compensates: `blockVisibleInRange` (`src/boundaries.ts`) treats a block as present in a range via its rendered summary **or** its earliest surviving raw — required precisely because the summary anchors _before_ the pinned raw.
@@ -1 +1 @@
1
- {"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAehE;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;gBAGjC,IAAI,EAAE,SAAS,GAAG,UAAU,EAC5B,QAAQ,EAAE,OAAO,GAAG,KAAK,EACzB,OAAO,EAAE,MAAM;CAQlB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,aAAa,CAiFf;AAsMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CAIf;AAQD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAcT;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EAAE,EACb,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CASf;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAMzE"}
1
+ {"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAehE;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;gBAGjC,IAAI,EAAE,SAAS,GAAG,UAAU,EAC5B,QAAQ,EAAE,OAAO,GAAG,KAAK,EACzB,OAAO,EAAE,MAAM;CAQlB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,aAAa,CAiFf;AAsMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CAIf;AAOD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAcT;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EAAE,EACb,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CASf;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAMzE"}
@@ -41,11 +41,14 @@ var COMPRESS_PHILOSOPHY = `Compression Philosophy:
41
41
  - All compression serves the primary task, but be frugal.
42
42
  - Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
43
43
  - Compress by need, not by percentage.
44
- - Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
44
+ - Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.
45
+ - Preserve the conversation's language: write each summary in the primary language of the conversation it replaces; do not translate a monolingual conversation unless the user explicitly asks.`;
45
46
  var HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS
46
47
 
47
48
  When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original. The summary records the PAST as of this block's creation: label recorded task state as history ("TASK AS OF THIS BLOCK: ...") \u2014 never as a live instruction, so a later reader treats it as settled context, not something to re-execute. Write plain text with real unicode characters; never copy \\uXXXX escape sequences or JSON-escaped fragments out of tool output.
48
49
 
50
+ LANGUAGE \u2014 write the summary in the PRIMARY LANGUAGE of the conversation being compressed. Do not translate a monolingual conversation into another language unless the user explicitly asked for translation (a Chinese conversation stays summarized in Chinese; an English one stays English). In a mixed-language conversation, keep each passage in its original language \u2014 a bilingual summary is fine. Code, commands, identifiers, error strings, config keys, and verbatim quotes always stay exactly as written, whatever the surrounding language.
51
+
49
52
  KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
50
53
  - Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
51
54
  - Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
@@ -83,6 +86,8 @@ var TIER2_DISTILL_RULES = `TIER 2 COMPRESSION \u2014 DISTILLATION
83
86
 
84
87
  You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
85
88
 
89
+ LANGUAGE \u2014 keep every distilled fact in the language of its source summaries (the conversation's primary language); do not translate when distilling across tiers. Code, identifiers, error strings, and quoted text stay exactly as written.
90
+
86
91
  KEEP \u2014 these are the only things that survive distillation:
87
92
  - Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
88
93
  - Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
@@ -115,6 +120,8 @@ var TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
115
120
 
116
121
  You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
117
122
 
123
+ LANGUAGE \u2014 write each fact in the language of its source blocks (the conversation's primary language); do not translate. Identifiers, versions, error strings, and quoted fragments stay exactly as written.
124
+
118
125
  PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
119
126
  1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
120
127
  2. Open work \u2014 PRs/issues still pending AND still-open user-requested objectives (re-carry any source block's \`Open objectives:\` entries verbatim); these may need follow-up.
@@ -441,4 +448,4 @@ export {
441
448
  VIABLE_RANGE_MIN_TOKENS,
442
449
  viableRanges
443
450
  };
444
- //# sourceMappingURL=chunk-HNWTM2K4.js.map
451
+ //# sourceMappingURL=chunk-HYRYYGPD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokenize.ts","../src/compression-rules.ts","../src/prompts.ts","../src/nudge-text.ts","../src/viable.ts","../src/truncate.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\n\nexport function defaultCountTokens(text: string): number {\n if (!text) return 0;\n // CJK chars tokenize ~1:1 (chars/4 badly underestimates them). Count them\n // directly, then estimate the non-CJK remainder with chars/4 so digits,\n // punctuation, and symbols in code/JSON are not dropped to zero.\n const cjk = text.match(/[\\u4e00-\\u9fff\\u3040-\\u30ff\\uac00-\\ud7af]/g);\n const cjkCount = cjk?.length ?? 0;\n return cjkCount + Math.ceil((text.length - cjkCount) / 4);\n}\n\nexport function estimateMessageTokens(text: string | undefined): number {\n return defaultCountTokens(text ?? \"\");\n}\n\n/** Guarded contribution of a host-projected thinking payload to the metered\n * message size. Non-finite or non-positive values are treated as absent (0),\n * so a bad host value can never poison the accounting. */\nexport function thinkingTokenValue(thinking: number | undefined): number {\n return typeof thinking === \"number\" &&\n Number.isFinite(thinking) &&\n thinking > 0\n ? thinking\n : 0;\n}\n\n/** Total metered size of a core message: visible text plus any host-projected\n * thinking payload (CoreMessage.thinkingTokens). Every per-message counting\n * site (range recommendations, block compressedTokens, status reports, context\n * breakdown) goes through this so all surfaces share one caliber. */\nexport function countMessageTokens(\n message: { text?: string; thinkingTokens?: number },\n countTokens: TokenCountFn = defaultCountTokens,\n): number {\n return (\n countTokens(message.text ?? \"\") + thinkingTokenValue(message.thinkingTokens)\n );\n}\n\nexport function estimateTokensFast(text: string): number {\n if (!text) return 0;\n return Math.ceil(text.length / 4);\n}\n\nexport type TokenCountFn = (text: string) => number;\n\nconst BPE_SIZE_GUARD = 100_000;\n\nexport function createBpeTokenizer(): TokenCountFn {\n try {\n const mod = require(\"@anthropic-ai/tokenizer\");\n const bpeCount = mod.countTokens ?? mod.default?.countTokens;\n if (typeof bpeCount !== \"function\") return defaultCountTokens;\n return (text: string) => {\n if (text.length > BPE_SIZE_GUARD) return defaultCountTokens(text);\n try {\n return bpeCount(text);\n } catch {\n return defaultCountTokens(text);\n }\n };\n } catch {\n return defaultCountTokens;\n }\n}\n","/**\n * Compression rule texts — VERBATIM copy from context-compress-algorithms (MIT, ours).\n * These were tuned over months of production use.\n *\n * 2026-09-06 amendment (owner-approved; billion-context-pi#309 incident): write-side\n * quote-fidelity rules merged INTO the tuned text. Verbatim user quotes now require\n * a message ref; recorded task state is labeled as history; unicode written directly.\n * Motivation: session 01a071dc blocks b60/b61 stored a fabricated\n * `user verbatim '合并了 下一个'` as a live \"CURRENT TASK\", causing loop relapses.\n *\n * 2026-09-26 amendment (#442 / billion-context#1398 sidecar readout): open-objectives\n * carry-forward rule added at every tier; \"never current directives\" clauses softened.\n * Sidecar showed a multi-block merge (b22 ⊇ b13) dropping a still-open user pivot that\n * b13 had recorded, and the trust-guardrail wording deprioritized objectives living only\n * in summaries. Quotes stay historical; open-objective STATUS is current.\n *\n * 2026-10-02 amendment (#493): source-language preservation added to the shared\n * philosophy and every tier (T1/T2/T3). Incident: a Chinese conversation switched to\n * English on the model turn right after a compression fold — the contract governed\n * summary content and structure but never the language a summary must be written in.\n * A monolingual conversation stays summarized in its own language unless the user asks\n * for translation; mixed-language passages, code, commands, identifiers, and quoted\n * text keep their original form.\n */\n\nexport const COMPRESS_PHILOSOPHY = `Compression Philosophy:\n- All compression serves the primary task, but be frugal.\n- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.\n- Compress by need, not by percentage.\n- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format — the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.\n- Preserve the conversation's language: write each summary in the primary language of the conversation it replaces; do not translate a monolingual conversation unless the user explicitly asks.`;\n\nexport const HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS\n\nWhen you call \\`compress\\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original. The summary records the PAST as of this block's creation: label recorded task state as history (\"TASK AS OF THIS BLOCK: ...\") — never as a live instruction, so a later reader treats it as settled context, not something to re-execute. Write plain text with real unicode characters; never copy \\\\uXXXX escape sequences or JSON-escaped fragments out of tool output.\n\nLANGUAGE — write the summary in the PRIMARY LANGUAGE of the conversation being compressed. Do not translate a monolingual conversation into another language unless the user explicitly asked for translation (a Chinese conversation stays summarized in Chinese; an English one stays English). In a mixed-language conversation, keep each passage in its original language — a bilingual summary is fine. Code, commands, identifiers, error strings, config keys, and verbatim quotes always stay exactly as written, whatever the surrounding language.\n\nKEEP VERBATIM — never paraphrase or abbreviate these:\n- Full file paths with line numbers, directory prefix on every mention (\\`lib/hooks.ts:347\\`, \\`src/index.ts:12-18\\`, \\`gatenet_v3/model.py:45\\`). Never abbreviate to a bare filename (\\`hooks.ts\\`, \\`model.py\\`) — they are ambiguous and cannot be grepped or decompressed-to later.\n- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic — the line that IS the finding, not just the function name (e.g. \\`kv_keys += define_gate * a_key[i](emb)\\` is more useful than \"see model_kvnet.py\").\n- Error messages and stack traces (exact text — you need the literal string to grep for it later).\n- Key details from reports and analyses — not just the conclusion. Keep the comparison numbers and the mechanism, not \"X is worse\" alone (write \"1.76× PPL gap because KV store is static\", not \"KVNet underperforms\").\n- Decisions and their rationale (\"chose X over Y because Z\" — the \"because\" is load-bearing; without it the decision looks arbitrary).\n- Constraints discovered (\"must support Node 22\", \"no new dependencies\", \"AGENTS.md forbids \\`as any\\`\").\n- Exact values: versions, config keys, thresholds, magic numbers.\n- User intent — quote short user messages verbatim ONLY WITH their message ref, e.g. \\`User said (m00132): \"ship it tonight\"\\`. Without a verifiable ref, paraphrase (\\`user previously asked (paraphrased): ...\\`) — this is the one exception to the verbatim rule above; never present a reconstructed or half-remembered phrase as a verbatim quote. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Quotes are historical records, not live instructions — but open-objective STATUS is current (still-open vs completed/superseded) and must be tracked. Losing these changes the task itself.\n- Open objectives carry-forward — if the range (or, when distilling, any source block's summary) contains a user-requested objective that is neither completed nor superseded by the end of the range, the summary MUST keep a one-line \\`Open objectives:\\` entry naming each still-open objective with its message ref (\\`Open objectives: refactor runner into eight arms (m00746)\\`). Distillation re-carries open objectives verbatim from source blocks — they are the last thing to drop and the first thing to restore, at every tier.\n- The user's overall goal and any changes to it — the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., \"initially: fix bug X → pivoted to: refactor module Y after discovering root cause\"). Losing the goal or its evolution makes all subsequent work appear unmotivated.\n- Purpose behind each significant action — preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.\n- Open questions and unresolved TODOs — losing these changes what work appears to remain.\n- Message refs of key anchors (\\`m00420\\`, \\`m00510–m00520\\`) — they let you or a later reader jump back via decompress to the exact original.\n\nDROP — extract the signal, discard the vessel:\n- Verbose logs (build/test/\\`npm\\` output) once you have captured the error line or the result.\n- Duplicate file reads once the needed content is recorded.\n- Consumed exploration — search hits, agent return values, successful tool outputs — once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).\n- Dead-end exploration — but PRESERVE the lesson in one line: \"tried X, failed because Y\".\n- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).\n- Repeated status checks (\\`git status\\`, \\`ls\\`) once state is known.\n\nFor each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers — not where it lives. Bad: \"probe script at /path/probe_kvnet.py\". Good: \"probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention.\" This lets a later decompress target the right block by relevance, not by guessing locations.\n\nPRIORITY — when the summary must be compact, preserve in this order:\n1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).\n2. Decisions and rationale.\n3. Exact technical artifacts: paths, signatures, errors, values.\n4. Conclusions and key findings.\n5. Lessons learned: what failed and why.\n\nWrite dense, scannable bullets — not narrative prose. If the range spans distinct concerns (request → findings → decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;\n\nexport const TIER2_DISTILL_RULES = `TIER 2 COMPRESSION — DISTILLATION\n\nYou are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.\n\nLANGUAGE — keep every distilled fact in the language of its source summaries (the conversation's primary language); do not translate when distilling across tiers. Code, identifiers, error strings, and quoted text stay exactly as written.\n\nKEEP — these are the only things that survive distillation:\n- Decisions and their rationale (\"chose X over Y because Z\" — the \"because\" is load-bearing).\n- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.\n- Key lessons: what failed and why (\"tried X, failed because Y\"). These prevent repeating mistakes.\n- Critical constraints discovered (\"must support Node 22\", \"AGENTS.md forbids as any\").\n- Design decisions with architectural impact (\"chose compress-as-anchor over synthetic messages because prefix cache\").\n- User quotes and task state only as attributed history: keep the source ref with any user quote; never carry an UNVERIFIED tier-1 \"CURRENT TASK\" claim forward as a live directive — relabel it \"TASK AS OF THIS BLOCK\". A ref-backed objective that no later source marks completed or superseded is not unverified: it carries in the \\`Open objectives:\\` entry (next bullet), not as a directive.\n- Open objectives — if ANY source block's summary names a user-requested objective that no later source block marks completed or superseded, the distilled summary MUST keep a one-line \\`Open objectives:\\` entry re-carrying each still-open objective verbatim with its original ref. They are the last thing to drop and the first thing to restore.\n- Whether content is OBSOLETE or SUPERSEDED — mark with one line: \"[SUPERSEDED by PR #NNN]\" or \"[OBSOLETE: deleted in vX.Y.Z]\". Do NOT keep the obsolete content's details — just the marker and reason.\n- Function/class/type names and module paths that are the SUBJECT of the work — e.g., \"fixed filterCompressedRanges in prune.ts\", \"added SessionStateRegistry in state.ts\". Not exact line numbers or full signatures — just enough to LOCATE the code without searching.\n- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line (\"explored X, not viable because Y\"). Do not keep the exploration process.\n\nDROP — these were useful during the work but are no longer needed:\n- Exact line numbers, diffs, verbose function signatures, full code listings.\n- Build/deploy process details, test execution steps.\n- Review process details (who reviewed, what rounds, test counts).\n- Verbose logs, command output, intermediate debugging steps.\n\nFORMAT:\n- Start each distilled block with a source header line:\n \\`Source: bN+bM+... (XK→YK tok, Zx). [original topic]\\`\n Example: \\`Source: b5+b7 (56K+44K→268 tok, 375x). [Tool-result recap + publish]\\`\n- 3-5 bullet points per source block, each a self-contained fact.\n- Dense, scannable — no narrative prose.\n- Start with the outcome, not the process: \"v1.13.0 shipped (7 PRs bundled)\" not \"implemented 7 PRs then reviewed then merged\".\n- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks — keep it once under the most relevant source header.\n\nSIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by \"[no actionable content].\"`;\n\nexport const TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION — ULTRA-CONDENSATION\n\nYou are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.\n\nLANGUAGE — write each fact in the language of its source blocks (the conversation's primary language); do not translate. Identifiers, versions, error strings, and quoted fragments stay exactly as written.\n\nPRIORITY — when a source block has more facts than the size target allows, keep in this order:\n1. Shipped outcomes (versions released, PRs merged) — these are permanent record.\n2. Open work — PRs/issues still pending AND still-open user-requested objectives (re-carry any source block's \\`Open objectives:\\` entries verbatim); these may need follow-up.\n3. Key decisions with architectural impact (\"chose X over Y because Z\").\n4. Critical constraints (\"must support Node 22\").\nDrop everything else. Tier 3 is a lookup index, not a knowledge base.\n\nFORMAT:\n- Start with a source header line:\n \\`Source: bN+bM+... (XK→YK tok, Zx). [original topic]\\`\n- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.\n- No explanations, no rationale, no process — just the fact.\n- Format: \"[PR/Issue/Version] — [outcome in ≤8 words]\"\n- Merge related facts from different source blocks if they concern the same topic.\n\nEXAMPLES:\n- \"v1.13.0 shipped — quality gate + GC fix (7 PRs)\"\n- \"PR #196 merged — preserve-first-user (supersedes #169)\"\n- \"Bug 1214 fixed — compress consumed all user messages\"\n- \"Objective (m00746) — eight-arm runner refactor, still open\"\n- \"Chose compress-as-anchor — prefix cache benefit over synthetic injection\"\n- \"Constraint: AGENTS.md forbids as any — never suppress types\"\n\nDROP:\n- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.\n- Lessons learned (\"tried X, failed because Y\") — drop UNLESS the failure is likely to recur and the block is <30 days old.\n- Design rationale details — keep the decision, drop the \"because\" unless it's a critical constraint.\n- Anything marked [OBSOLETE] or [SUPERSEDED] — drop entirely, note \"[N blocks obsolete]\" in the summary.\n\nSIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output ≈ N × 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;\n","import {\n COMPRESS_PHILOSOPHY,\n HOW_TO_COMPRESS_RULES,\n TIER2_DISTILL_RULES,\n TIER3_CONDENSE_RULES,\n} from \"./compression-rules.js\";\n\n/**\n * Overridable prompt text consumed by the kernel's nudge renderer and, via the\n * adapter, the system prompt. Every field here is LOAD-BEARING: these rules\n * were tuned over months of production use and are quality-critical. Overriding\n * them can degrade summary quality (loss of paths / signatures / decisions →\n * broken retrieval), so {@link resolvePrompts} requires `{ acknowledgeRisk: true }`.\n *\n * Surface-level text (summary section headers, status-report chrome, tool\n * descriptions) is intentionally NOT part of this interface — it is owned by\n * the adapter or a later \"prompt-set format\" layer and is safe to customize\n * freely. See DESIGN.md for the load-bearing vs surface classification.\n */\nexport interface Prompts {\n /** Core compression philosophy. Embedded in the system prompt + every nudge. */\n compressPhilosophy: string;\n /** Rules the model follows when writing a tier-1 summary. */\n howToCompressRules: string;\n /** Rules for tier-2 distillation of existing summaries. */\n tier2DistillRules: string;\n /** Rules for tier-3 ultra-condensation of distilled summaries. */\n tier3CondenseRules: string;\n}\n\n/**\n * The kernel's canonical prompt values (verbatim from compression-rules.ts).\n * Frozen so a buggy caller cannot mutate the shared singleton and corrupt\n * every other consumer of {@link defaultPrompts}.\n */\nexport const defaultPrompts: Prompts = Object.freeze({\n compressPhilosophy: COMPRESS_PHILOSOPHY,\n howToCompressRules: HOW_TO_COMPRESS_RULES,\n tier2DistillRules: TIER2_DISTILL_RULES,\n tier3CondenseRules: TIER3_CONDENSE_RULES,\n}) as Prompts;\n\nexport interface ResolvePromptsOptions {\n /**\n * Must be `true` to override any prompt field. Every {@link Prompts} field is\n * load-bearing; overriding without acknowledging the quality risk is a\n * programming error and throws.\n */\n acknowledgeRisk?: boolean;\n}\n\n/**\n * Merge prompt overrides onto the kernel defaults. All fields are load-bearing,\n * so ANY override requires `{ acknowledgeRisk: true }`.\n *\n * Only `string`-valued overrides take effect: an explicit `undefined`/`null` or\n * a wrong type is silently dropped (never clobbers a good default), so a\n * malformed partial never degrades the canonical rules. Resolve once at host\n * startup, then pass the resulting {@link Prompts} to {@link renderNudgeText}\n * and to the adapter's system-prompt composition so both layers stay consistent.\n */\nexport function resolvePrompts(\n overrides?: Partial<Prompts>,\n options: ResolvePromptsOptions = {},\n): Prompts {\n const clean: Partial<Prompts> = {};\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n if (typeof value === \"string\") {\n (clean as Record<string, unknown>)[key] = value;\n }\n }\n }\n const keys = Object.keys(clean) as (keyof Prompts)[];\n if (keys.length > 0 && !options.acknowledgeRisk) {\n throw new Error(\n `resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. ` +\n `Overridden keys: ${keys.join(\", \")}. These rules are quality-critical (tuned over months of production use); ` +\n `changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`,\n );\n }\n return { ...defaultPrompts, ...clean };\n}\n","import type {\n NudgeDecision,\n CompressibleRange,\n ProtectedRange,\n ContextBreakdown,\n CompressionBlock,\n BlockSpan,\n} from \"./types.js\";\nimport { defaultPrompts } from \"./prompts.js\";\nimport type { Prompts } from \"./prompts.js\";\n\nexport type NudgeVoice = \"gentle\" | \"emergency\";\n\nexport interface NudgePromptSections {\n efficiencyNote?: string | null;\n emergencyHeader?: string | null;\n t2Guidance?: string | null;\n t3Guidance?: string | null;\n}\n\nexport interface RenderedNudge {\n voice: NudgeVoice;\n text: string;\n}\n\nfunction efficiencyNote(\n prompts: Prompts,\n sections: NudgePromptSections,\n): string | null {\n if (sections.efficiencyNote !== undefined) return sections.efficiencyNote;\n return `This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full.\\n\\n${prompts.compressPhilosophy}`;\n}\n\nfunction emergencyHeader(\n prompts: Prompts,\n sections: NudgePromptSections,\n): string | null {\n if (sections.emergencyHeader !== undefined) return sections.emergencyHeader;\n return `⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.\\n\\n${prompts.compressPhilosophy}`;\n}\n\nfunction formatK(n: number): string {\n if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;\n return `${n}`;\n}\n\nfunction formatBreakdown(bd?: ContextBreakdown): string {\n if (!bd) return \"\";\n const parts: string[] = [];\n if (bd.system > 0) parts.push(`${formatK(bd.system)} system`);\n if (bd.tool > 0) parts.push(`${formatK(bd.tool)} tool`);\n if (bd.summaries > 0) parts.push(`${formatK(bd.summaries)} summaries`);\n if (bd.code > 0) parts.push(`${formatK(bd.code)} code`);\n if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);\n const growth =\n bd.growth > 0 ? `\\n+${formatK(bd.growth)} since last nudge` : \"\";\n return `Context breakdown: ${parts.join(\" | \")}${growth}`;\n}\n\nfunction formatTierTargetBlocks(blocks: CompressionBlock[]): string {\n if (blocks.length === 0) {\n return \"Target blocks: (none — no tier blocks found)\";\n }\n const lines = blocks.map((b) => {\n const summaryTokens = Math.ceil((b.summary ?? \"\").length / 4);\n const topic = b.topic ? ` \"${b.topic}\"` : \"\";\n return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}→${formatK(summaryTokens)}${topic}`;\n });\n return `Target ${blocks[0]!.tier === 1 ? \"tier-1\" : \"tier-2\"} blocks to distill (${blocks.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nconst BLOCK_MAP_MAX_SHOWN = 8;\n\nfunction formatBlockMap(spans: BlockSpan[]): string {\n if (spans.length === 0) return \"\";\n const hidden = Math.max(0, spans.length - BLOCK_MAP_MAX_SHOWN);\n const shown = hidden > 0 ? spans.slice(-BLOCK_MAP_MAX_SHOWN) : spans;\n const items = shown.map(\n (s) =>\n `${s.blockId}=${s.startRef}–${s.endRef}${s.tier > 1 ? ` t${s.tier}` : \"\"}`,\n );\n const prefix = hidden > 0 ? `…+${hidden} older · ` : \"\";\n return `Active blocks (${spans.length}): ${prefix}${items.join(\" · \")}`;\n}\n\nexport function formatRanges(\n compressible: CompressibleRange[],\n protectedRanges: ProtectedRange[],\n): string {\n if (compressible.length === 0 && protectedRanges.length === 0) {\n return \"[No specific ranges detected — compress any consumed content.]\";\n }\n\n // Merge compressible + protected into a single oldest-first list, mirroring\n // opencode-acp's formatCompressibleRanges. Splitting them into two sections\n // lost the time order and hid overlaps; a range can be partly compressible\n // and partly protected, which only the merged view shows correctly.\n interface Merged {\n startRef: string;\n endRef: string;\n startNum: number;\n endNum: number;\n startPos: number;\n endPos: number;\n count: number;\n tokens: number;\n userMsgs: number;\n compressibleTokens: number;\n compressibleCount: number;\n protectedTokens: number;\n protectedCount: number;\n protectedTools: string[];\n toolPct: number;\n textPct: number;\n dangerous: boolean;\n }\n const refNum = (ref: string): number => {\n const m = ref.match(/\\d+/);\n return m ? parseInt(m[0], 10) : 0;\n };\n const entries: Merged[] = [];\n for (const r of compressible) {\n entries.push({\n startRef: r.startRef,\n endRef: r.endRef,\n startNum: refNum(r.startRef),\n endNum: refNum(r.endRef),\n startPos: r.startIndex ?? refNum(r.startRef),\n endPos: r.endIndex ?? refNum(r.endRef),\n count: r.count,\n tokens: r.tokens,\n userMsgs: r.userMsgs ?? 0,\n toolPct: r.toolPct,\n textPct: r.textPct,\n compressibleTokens: r.tokens,\n compressibleCount: r.count,\n protectedTokens: 0,\n protectedCount: 0,\n protectedTools: [],\n dangerous: r.dangerous ?? false,\n });\n }\n for (const r of protectedRanges) {\n entries.push({\n startRef: r.startRef,\n endRef: r.endRef,\n startNum: refNum(r.startRef),\n endNum: refNum(r.endRef),\n startPos: r.startIndex ?? refNum(r.startRef),\n endPos: r.endIndex ?? refNum(r.endRef),\n count: r.count,\n tokens: r.tokens,\n userMsgs: 0,\n toolPct: 0,\n textPct: 0,\n compressibleTokens: 0,\n compressibleCount: 0,\n protectedTokens: r.tokens,\n protectedCount: r.count,\n protectedTools: [...r.tools],\n dangerous: false,\n });\n }\n // Order/merge by POSITION, never ref number: refs can be non-monotonic vs array\n // order (subagent interleaving / mid-array summary nodes), and ref-number merging\n // then yields endpoints resolveBoundaries collapses to a tiny slice (#887).\n entries.sort((a, b) => a.startPos - b.startPos || a.startNum - b.startNum);\n // Merge positionally adjacent/overlapping ranges (gap ≤ 1 slot).\n const merged: Merged[] = [];\n for (const e of entries) {\n const last = merged[merged.length - 1];\n if (last && e.startPos <= last.endPos + 1) {\n last.endRef = e.endRef;\n last.endNum = Math.max(last.endNum, e.endNum);\n last.endPos = Math.max(last.endPos, e.endPos);\n last.count += e.count;\n last.tokens += e.tokens;\n last.userMsgs += e.userMsgs;\n last.compressibleTokens += e.compressibleTokens;\n last.compressibleCount += e.compressibleCount;\n last.protectedTokens += e.protectedTokens;\n last.protectedCount += e.protectedCount;\n if (e.dangerous) last.dangerous = true;\n for (const t of e.protectedTools) {\n if (!last.protectedTools.includes(t)) last.protectedTools.push(t);\n }\n } else {\n merged.push({ ...e });\n }\n }\n const userNote = (n: number): string =>\n n > 0 ? ` · ${n} user msg${n > 1 ? \"s\" : \"\"}` : \"\";\n const lines = merged.map((e) => {\n const suffix =\n e.dangerous && e.compressibleTokens > 0\n ? \" ⚠️ NOT recommended unless you are certain.\"\n : \"\";\n if (e.protectedTokens > 0 && e.compressibleTokens === 0) {\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(\", \")} — not compressible]${suffix}`;\n }\n if (e.protectedTokens > 0 && e.compressibleTokens > 0) {\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(\", \")}]${userNote(e.userMsgs)}${suffix}`;\n }\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${userNote(e.userMsgs)}${suffix}`;\n });\n return `Compressible ranges (${merged.length}, oldest first):\\n${lines.join(\"\\n\")}`;\n}\n\nconst DEFAULT_T2_GUIDANCE = `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well — apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.`;\n\nconst DEFAULT_T3_GUIDANCE = `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well — apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`;\n\nfunction tierGuidance(\n tier: 2 | 3,\n sections: NudgePromptSections,\n): string | null {\n const value = tier === 2 ? sections.t2Guidance : sections.t3Guidance;\n if (value !== undefined) return value;\n return tier === 2 ? DEFAULT_T2_GUIDANCE : DEFAULT_T3_GUIDANCE;\n}\n\nfunction compact(parts: string[]): string[] {\n while (parts.length > 0 && parts[0] === \"\") parts.shift();\n return parts;\n}\n\nexport function renderNudgeText(\n decision: NudgeDecision,\n prompts: Prompts = defaultPrompts,\n sections: NudgePromptSections = {},\n): RenderedNudge {\n const breakdownStr = formatBreakdown(decision.contextBreakdown);\n const rangesStr = formatRanges(\n decision.compressibleRanges,\n decision.protectedRanges ?? [],\n );\n const blockMapStr = formatBlockMap(decision.activeBlockSpans ?? []);\n const isEmergency =\n !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;\n\n if (decision.tier !== null && decision.tier >= 2) {\n const isT2 = decision.tier === 2;\n const targets = decision.tierTargetBlocks ?? [];\n const blockList = formatTierTargetBlocks(targets);\n const startId = targets[0]?.blockId ?? \"b1\";\n const endId = targets[targets.length - 1]?.blockId ?? \"b5\";\n const voice: NudgeVoice = isEmergency ? \"emergency\" : \"gentle\";\n const triggerLine = isEmergency\n ? `[EMERGENCY — TIER ${decision.tier} ${isT2 ? \"DISTILLATION\" : \"CONDENSATION\"}] Context limit reached — distill NOW into a denser summary to reclaim tokens.`\n : `[TIER ${decision.tier} ${isT2 ? \"DISTILLATION\" : \"CONDENSATION\"} TRIGGER]`;\n const guidance = tierGuidance(isT2 ? 2 : 3, sections);\n const head = efficiencyNote(prompts, sections);\n return {\n voice,\n text: compact([\n ...(head === null ? [] : [head]),\n \"\",\n breakdownStr,\n \"\",\n triggerLine,\n ...(guidance === null ? [] : [guidance]),\n blockList,\n `Example: compress({ content: [{ startId: \"${startId}\", endId: \"${endId}\", summary: \"...\" }] })`,\n \"\",\n prompts.howToCompressRules,\n \"\",\n isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules,\n ]).join(\"\\n\"),\n };\n }\n\n if (isEmergency) {\n const head = emergencyHeader(prompts, sections);\n return {\n voice: \"emergency\",\n text: compact([\n ...(head === null ? [] : [head]),\n \"\",\n breakdownStr,\n \"\",\n prompts.howToCompressRules,\n \"\",\n `{ \"topic\": \"...\", \"content\": [{ \"startId\": \"<ID>\", \"endId\": \"<ID>\", \"summary\": \"...\" }] }`,\n \"Only use IDs from visible messages above. Compress older work first.\",\n \"\",\n rangesStr,\n ...(blockMapStr ? [\"\", blockMapStr] : []),\n ]).join(\"\\n\"),\n };\n }\n\n const gentleHead = efficiencyNote(prompts, sections);\n return {\n voice: \"gentle\",\n text: compact([\n ...(gentleHead === null ? [] : [gentleHead]),\n \"\",\n breakdownStr,\n \"\",\n prompts.howToCompressRules,\n \"\",\n rangesStr,\n ...(blockMapStr ? [\"\", blockMapStr] : []),\n \"\",\n `💡 If you compress, fold the ranges you keep in ONE call — pass multiple content entries (\\`content: [{...}, {...}]\\`) or ONE plain string holding every range, each block starting with its 'mNNNNN–mNNNNN topic' header line (most robust through lossy gateways). Ranges the task still needs can wait — they reappear in later nudges.`,\n ]).join(\"\\n\"),\n };\n}\n","/** Minimum size for a compressible range to be worth recommending. Ranges\n * below this are fragmented leftovers (a 16-token ack, a one-line tool\n * result): the model cannot write a meaningful >=50-char summary for them,\n * and a batched compress call that includes one gets atomically rejected\n * (the kernel validates the whole batch). Observed in the wild: a 14-range\n * recommendation list containing a 16-token range → every batch attempt\n * failed with \"Summary too short\". Apply on every surface that recommends\n * ranges: the injected nudge, acp_status, and the /acp panel. */\nexport const VIABLE_RANGE_MIN_TOKENS = 200;\n\nexport function viableRanges<T extends { tokens: number }>(ranges: T[]): T[] {\n return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);\n}\n","// UTF-16-safe truncation. A plain slice can land between the two code units\n// of an astral character (emoji, CJK ext-B+), stranding a lone surrogate:\n// JSON.stringify escapes it (\\ud83e), and gateways that re-encode request\n// bodies to strict UTF-8 then throw UnicodeEncodeError on every retry\n// (ranxianglei/billion-context#816) — a deterministic poison that never heals.\n\nfunction isHighSurrogate(c: number): boolean {\n return c >= 0xd800 && c <= 0xdbff;\n}\n\nfunction isLowSurrogate(c: number): boolean {\n return c >= 0xdc00 && c <= 0xdfff;\n}\n\n/** At most maxUnits code units; never ends on a stranded high surrogate. */\nexport function clampPrefix(text: string, maxUnits: number): string {\n const cut = Math.min(maxUnits, text.length);\n if (cut > 0 && isHighSurrogate(text.charCodeAt(cut - 1)))\n return text.slice(0, cut - 1);\n return text.slice(0, cut);\n}\n\n/** A [start, end) window with both edges snapped off surrogate pairs. */\nexport function clampWindow(text: string, start: number, end: number): string {\n let s = Math.max(0, Math.min(start, text.length));\n let e = Math.min(text.length, Math.max(s, end));\n if (s > 0 && isLowSurrogate(text.charCodeAt(s))) s += 1;\n if (e > s && isHighSurrogate(text.charCodeAt(e - 1))) e -= 1;\n return text.slice(s, Math.max(s, e));\n}\n"],"mappings":"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","names":["require","require"]}
@@ -13,9 +13,17 @@
13
13
  * Sidecar showed a multi-block merge (b22 ⊇ b13) dropping a still-open user pivot that
14
14
  * b13 had recorded, and the trust-guardrail wording deprioritized objectives living only
15
15
  * in summaries. Quotes stay historical; open-objective STATUS is current.
16
+ *
17
+ * 2026-10-02 amendment (#493): source-language preservation added to the shared
18
+ * philosophy and every tier (T1/T2/T3). Incident: a Chinese conversation switched to
19
+ * English on the model turn right after a compression fold — the contract governed
20
+ * summary content and structure but never the language a summary must be written in.
21
+ * A monolingual conversation stays summarized in its own language unless the user asks
22
+ * for translation; mixed-language passages, code, commands, identifiers, and quoted
23
+ * text keep their original form.
16
24
  */
17
- export declare const COMPRESS_PHILOSOPHY = "Compression Philosophy:\n- All compression serves the primary task, but be frugal.\n- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.\n- Compress by need, not by percentage.\n- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.";
18
- export declare const HOW_TO_COMPRESS_RULES = "HOW TO COMPRESS\n\nWhen you call `compress`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original. The summary records the PAST as of this block's creation: label recorded task state as history (\"TASK AS OF THIS BLOCK: ...\") \u2014 never as a live instruction, so a later reader treats it as settled context, not something to re-execute. Write plain text with real unicode characters; never copy \\uXXXX escape sequences or JSON-escaped fragments out of tool output.\n\nKEEP VERBATIM \u2014 never paraphrase or abbreviate these:\n- Full file paths with line numbers, directory prefix on every mention (`lib/hooks.ts:347`, `src/index.ts:12-18`, `gatenet_v3/model.py:45`). Never abbreviate to a bare filename (`hooks.ts`, `model.py`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.\n- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. `kv_keys += define_gate * a_key[i](emb)` is more useful than \"see model_kvnet.py\").\n- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).\n- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not \"X is worse\" alone (write \"1.76\u00D7 PPL gap because KV store is static\", not \"KVNet underperforms\").\n- Decisions and their rationale (\"chose X over Y because Z\" \u2014 the \"because\" is load-bearing; without it the decision looks arbitrary).\n- Constraints discovered (\"must support Node 22\", \"no new dependencies\", \"AGENTS.md forbids `as any`\").\n- Exact values: versions, config keys, thresholds, magic numbers.\n- User intent \u2014 quote short user messages verbatim ONLY WITH their message ref, e.g. `User said (m00132): \"ship it tonight\"`. Without a verifiable ref, paraphrase (`user previously asked (paraphrased): ...`) \u2014 this is the one exception to the verbatim rule above; never present a reconstructed or half-remembered phrase as a verbatim quote. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Quotes are historical records, not live instructions \u2014 but open-objective STATUS is current (still-open vs completed/superseded) and must be tracked. Losing these changes the task itself.\n- Open objectives carry-forward \u2014 if the range (or, when distilling, any source block's summary) contains a user-requested objective that is neither completed nor superseded by the end of the range, the summary MUST keep a one-line `Open objectives:` entry naming each still-open objective with its message ref (`Open objectives: refactor runner into eight arms (m00746)`). Distillation re-carries open objectives verbatim from source blocks \u2014 they are the last thing to drop and the first thing to restore, at every tier.\n- The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., \"initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause\"). Losing the goal or its evolution makes all subsequent work appear unmotivated.\n- Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.\n- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.\n- Message refs of key anchors (`m00420`, `m00510\u2013m00520`) \u2014 they let you or a later reader jump back via decompress to the exact original.\n\nDROP \u2014 extract the signal, discard the vessel:\n- Verbose logs (build/test/`npm` output) once you have captured the error line or the result.\n- Duplicate file reads once the needed content is recorded.\n- Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).\n- Dead-end exploration \u2014 but PRESERVE the lesson in one line: \"tried X, failed because Y\".\n- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).\n- Repeated status checks (`git status`, `ls`) once state is known.\n\nFor each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: \"probe script at /path/probe_kvnet.py\". Good: \"probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention.\" This lets a later decompress target the right block by relevance, not by guessing locations.\n\nPRIORITY \u2014 when the summary must be compact, preserve in this order:\n1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).\n2. Decisions and rationale.\n3. Exact technical artifacts: paths, signatures, errors, values.\n4. Conclusions and key findings.\n5. Lessons learned: what failed and why.\n\nWrite dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.";
19
- export declare const TIER2_DISTILL_RULES = "TIER 2 COMPRESSION \u2014 DISTILLATION\n\nYou are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.\n\nKEEP \u2014 these are the only things that survive distillation:\n- Decisions and their rationale (\"chose X over Y because Z\" \u2014 the \"because\" is load-bearing).\n- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.\n- Key lessons: what failed and why (\"tried X, failed because Y\"). These prevent repeating mistakes.\n- Critical constraints discovered (\"must support Node 22\", \"AGENTS.md forbids as any\").\n- Design decisions with architectural impact (\"chose compress-as-anchor over synthetic messages because prefix cache\").\n- User quotes and task state only as attributed history: keep the source ref with any user quote; never carry an UNVERIFIED tier-1 \"CURRENT TASK\" claim forward as a live directive \u2014 relabel it \"TASK AS OF THIS BLOCK\". A ref-backed objective that no later source marks completed or superseded is not unverified: it carries in the `Open objectives:` entry (next bullet), not as a directive.\n- Open objectives \u2014 if ANY source block's summary names a user-requested objective that no later source block marks completed or superseded, the distilled summary MUST keep a one-line `Open objectives:` entry re-carrying each still-open objective verbatim with its original ref. They are the last thing to drop and the first thing to restore.\n- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: \"[SUPERSEDED by PR #NNN]\" or \"[OBSOLETE: deleted in vX.Y.Z]\". Do NOT keep the obsolete content's details \u2014 just the marker and reason.\n- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., \"fixed filterCompressedRanges in prune.ts\", \"added SessionStateRegistry in state.ts\". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.\n- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line (\"explored X, not viable because Y\"). Do not keep the exploration process.\n\nDROP \u2014 these were useful during the work but are no longer needed:\n- Exact line numbers, diffs, verbose function signatures, full code listings.\n- Build/deploy process details, test execution steps.\n- Review process details (who reviewed, what rounds, test counts).\n- Verbose logs, command output, intermediate debugging steps.\n\nFORMAT:\n- Start each distilled block with a source header line:\n `Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]`\n Example: `Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]`\n- 3-5 bullet points per source block, each a self-contained fact.\n- Dense, scannable \u2014 no narrative prose.\n- Start with the outcome, not the process: \"v1.13.0 shipped (7 PRs bundled)\" not \"implemented 7 PRs then reviewed then merged\".\n- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.\n\nSIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by \"[no actionable content].\"";
20
- export declare const TIER3_CONDENSE_RULES = "TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION\n\nYou are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.\n\nPRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:\n1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.\n2. Open work \u2014 PRs/issues still pending AND still-open user-requested objectives (re-carry any source block's `Open objectives:` entries verbatim); these may need follow-up.\n3. Key decisions with architectural impact (\"chose X over Y because Z\").\n4. Critical constraints (\"must support Node 22\").\nDrop everything else. Tier 3 is a lookup index, not a knowledge base.\n\nFORMAT:\n- Start with a source header line:\n `Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]`\n- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.\n- No explanations, no rationale, no process \u2014 just the fact.\n- Format: \"[PR/Issue/Version] \u2014 [outcome in \u22648 words]\"\n- Merge related facts from different source blocks if they concern the same topic.\n\nEXAMPLES:\n- \"v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)\"\n- \"PR #196 merged \u2014 preserve-first-user (supersedes #169)\"\n- \"Bug 1214 fixed \u2014 compress consumed all user messages\"\n- \"Objective (m00746) \u2014 eight-arm runner refactor, still open\"\n- \"Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection\"\n- \"Constraint: AGENTS.md forbids as any \u2014 never suppress types\"\n\nDROP:\n- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.\n- Lessons learned (\"tried X, failed because Y\") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.\n- Design rationale details \u2014 keep the decision, drop the \"because\" unless it's a critical constraint.\n- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note \"[N blocks obsolete]\" in the summary.\n\nSIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \u00D7 40 tokens. If a source block has only one trivial fact, output just the header + one line.";
25
+ export declare const COMPRESS_PHILOSOPHY = "Compression Philosophy:\n- All compression serves the primary task, but be frugal.\n- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.\n- Compress by need, not by percentage.\n- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.\n- Preserve the conversation's language: write each summary in the primary language of the conversation it replaces; do not translate a monolingual conversation unless the user explicitly asks.";
26
+ export declare const HOW_TO_COMPRESS_RULES = "HOW TO COMPRESS\n\nWhen you call `compress`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original. The summary records the PAST as of this block's creation: label recorded task state as history (\"TASK AS OF THIS BLOCK: ...\") \u2014 never as a live instruction, so a later reader treats it as settled context, not something to re-execute. Write plain text with real unicode characters; never copy \\uXXXX escape sequences or JSON-escaped fragments out of tool output.\n\nLANGUAGE \u2014 write the summary in the PRIMARY LANGUAGE of the conversation being compressed. Do not translate a monolingual conversation into another language unless the user explicitly asked for translation (a Chinese conversation stays summarized in Chinese; an English one stays English). In a mixed-language conversation, keep each passage in its original language \u2014 a bilingual summary is fine. Code, commands, identifiers, error strings, config keys, and verbatim quotes always stay exactly as written, whatever the surrounding language.\n\nKEEP VERBATIM \u2014 never paraphrase or abbreviate these:\n- Full file paths with line numbers, directory prefix on every mention (`lib/hooks.ts:347`, `src/index.ts:12-18`, `gatenet_v3/model.py:45`). Never abbreviate to a bare filename (`hooks.ts`, `model.py`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.\n- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. `kv_keys += define_gate * a_key[i](emb)` is more useful than \"see model_kvnet.py\").\n- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).\n- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not \"X is worse\" alone (write \"1.76\u00D7 PPL gap because KV store is static\", not \"KVNet underperforms\").\n- Decisions and their rationale (\"chose X over Y because Z\" \u2014 the \"because\" is load-bearing; without it the decision looks arbitrary).\n- Constraints discovered (\"must support Node 22\", \"no new dependencies\", \"AGENTS.md forbids `as any`\").\n- Exact values: versions, config keys, thresholds, magic numbers.\n- User intent \u2014 quote short user messages verbatim ONLY WITH their message ref, e.g. `User said (m00132): \"ship it tonight\"`. Without a verifiable ref, paraphrase (`user previously asked (paraphrased): ...`) \u2014 this is the one exception to the verbatim rule above; never present a reconstructed or half-remembered phrase as a verbatim quote. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Quotes are historical records, not live instructions \u2014 but open-objective STATUS is current (still-open vs completed/superseded) and must be tracked. Losing these changes the task itself.\n- Open objectives carry-forward \u2014 if the range (or, when distilling, any source block's summary) contains a user-requested objective that is neither completed nor superseded by the end of the range, the summary MUST keep a one-line `Open objectives:` entry naming each still-open objective with its message ref (`Open objectives: refactor runner into eight arms (m00746)`). Distillation re-carries open objectives verbatim from source blocks \u2014 they are the last thing to drop and the first thing to restore, at every tier.\n- The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., \"initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause\"). Losing the goal or its evolution makes all subsequent work appear unmotivated.\n- Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.\n- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.\n- Message refs of key anchors (`m00420`, `m00510\u2013m00520`) \u2014 they let you or a later reader jump back via decompress to the exact original.\n\nDROP \u2014 extract the signal, discard the vessel:\n- Verbose logs (build/test/`npm` output) once you have captured the error line or the result.\n- Duplicate file reads once the needed content is recorded.\n- Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).\n- Dead-end exploration \u2014 but PRESERVE the lesson in one line: \"tried X, failed because Y\".\n- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).\n- Repeated status checks (`git status`, `ls`) once state is known.\n\nFor each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: \"probe script at /path/probe_kvnet.py\". Good: \"probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention.\" This lets a later decompress target the right block by relevance, not by guessing locations.\n\nPRIORITY \u2014 when the summary must be compact, preserve in this order:\n1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).\n2. Decisions and rationale.\n3. Exact technical artifacts: paths, signatures, errors, values.\n4. Conclusions and key findings.\n5. Lessons learned: what failed and why.\n\nWrite dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.";
27
+ export declare const TIER2_DISTILL_RULES = "TIER 2 COMPRESSION \u2014 DISTILLATION\n\nYou are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.\n\nLANGUAGE \u2014 keep every distilled fact in the language of its source summaries (the conversation's primary language); do not translate when distilling across tiers. Code, identifiers, error strings, and quoted text stay exactly as written.\n\nKEEP \u2014 these are the only things that survive distillation:\n- Decisions and their rationale (\"chose X over Y because Z\" \u2014 the \"because\" is load-bearing).\n- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.\n- Key lessons: what failed and why (\"tried X, failed because Y\"). These prevent repeating mistakes.\n- Critical constraints discovered (\"must support Node 22\", \"AGENTS.md forbids as any\").\n- Design decisions with architectural impact (\"chose compress-as-anchor over synthetic messages because prefix cache\").\n- User quotes and task state only as attributed history: keep the source ref with any user quote; never carry an UNVERIFIED tier-1 \"CURRENT TASK\" claim forward as a live directive \u2014 relabel it \"TASK AS OF THIS BLOCK\". A ref-backed objective that no later source marks completed or superseded is not unverified: it carries in the `Open objectives:` entry (next bullet), not as a directive.\n- Open objectives \u2014 if ANY source block's summary names a user-requested objective that no later source block marks completed or superseded, the distilled summary MUST keep a one-line `Open objectives:` entry re-carrying each still-open objective verbatim with its original ref. They are the last thing to drop and the first thing to restore.\n- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: \"[SUPERSEDED by PR #NNN]\" or \"[OBSOLETE: deleted in vX.Y.Z]\". Do NOT keep the obsolete content's details \u2014 just the marker and reason.\n- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., \"fixed filterCompressedRanges in prune.ts\", \"added SessionStateRegistry in state.ts\". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.\n- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line (\"explored X, not viable because Y\"). Do not keep the exploration process.\n\nDROP \u2014 these were useful during the work but are no longer needed:\n- Exact line numbers, diffs, verbose function signatures, full code listings.\n- Build/deploy process details, test execution steps.\n- Review process details (who reviewed, what rounds, test counts).\n- Verbose logs, command output, intermediate debugging steps.\n\nFORMAT:\n- Start each distilled block with a source header line:\n `Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]`\n Example: `Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]`\n- 3-5 bullet points per source block, each a self-contained fact.\n- Dense, scannable \u2014 no narrative prose.\n- Start with the outcome, not the process: \"v1.13.0 shipped (7 PRs bundled)\" not \"implemented 7 PRs then reviewed then merged\".\n- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.\n\nSIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by \"[no actionable content].\"";
28
+ export declare const TIER3_CONDENSE_RULES = "TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION\n\nYou are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.\n\nLANGUAGE \u2014 write each fact in the language of its source blocks (the conversation's primary language); do not translate. Identifiers, versions, error strings, and quoted fragments stay exactly as written.\n\nPRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:\n1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.\n2. Open work \u2014 PRs/issues still pending AND still-open user-requested objectives (re-carry any source block's `Open objectives:` entries verbatim); these may need follow-up.\n3. Key decisions with architectural impact (\"chose X over Y because Z\").\n4. Critical constraints (\"must support Node 22\").\nDrop everything else. Tier 3 is a lookup index, not a knowledge base.\n\nFORMAT:\n- Start with a source header line:\n `Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]`\n- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.\n- No explanations, no rationale, no process \u2014 just the fact.\n- Format: \"[PR/Issue/Version] \u2014 [outcome in \u22648 words]\"\n- Merge related facts from different source blocks if they concern the same topic.\n\nEXAMPLES:\n- \"v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)\"\n- \"PR #196 merged \u2014 preserve-first-user (supersedes #169)\"\n- \"Bug 1214 fixed \u2014 compress consumed all user messages\"\n- \"Objective (m00746) \u2014 eight-arm runner refactor, still open\"\n- \"Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection\"\n- \"Constraint: AGENTS.md forbids as any \u2014 never suppress types\"\n\nDROP:\n- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.\n- Lessons learned (\"tried X, failed because Y\") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.\n- Design rationale details \u2014 keep the decision, drop the \"because\" unless it's a critical constraint.\n- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note \"[N blocks obsolete]\" in the summary.\n\nSIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \u00D7 40 tokens. If a source block has only one trivial fact, output just the header + one line.";
21
29
  //# sourceMappingURL=compression-rules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compression-rules.d.ts","sourceRoot":"","sources":["../src/compression-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,mBAAmB,giBAIqR,CAAC;AAEtT,eAAO,MAAM,qBAAqB,o7LAoC8R,CAAC;AAEjU,eAAO,MAAM,mBAAmB,+/GA+ByN,CAAC;AAE1P,eAAO,MAAM,oBAAoB,2yEAiC4K,CAAC"}
1
+ {"version":3,"file":"compression-rules.d.ts","sourceRoot":"","sources":["../src/compression-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,mBAAmB,kuBAKiK,CAAC;AAElM,eAAO,MAAM,qBAAqB,+9MAsC8R,CAAC;AAEjU,eAAO,MAAM,mBAAmB,qvHAiCyN,CAAC;AAE1P,eAAO,MAAM,oBAAoB,ggFAmC4K,CAAC"}
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  resolvePrompts,
17
17
  thinkingTokenValue,
18
18
  viableRanges
19
- } from "./chunk-HNWTM2K4.js";
19
+ } from "./chunk-HYRYYGPD.js";
20
20
  import {
21
21
  activeBlocks,
22
22
  advanceSurvival,
@@ -219,12 +219,11 @@ function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
219
219
  const anchoredSummaryIds = new Set(
220
220
  anchors.map((anchor) => summaryMessageId(anchor.blockId))
221
221
  );
222
- const pinFirstUser = firstUserIndex < 0 || !isCovered(messages[firstUserIndex].id, coveredBases) || !firstUserDropSafe(messages, firstUserIndex, coveredBases);
223
222
  for (let index = 0; index < messages.length; index++) {
224
223
  while (pending.length > 0 && pending[0].insertAt === index) {
225
224
  result.push(renderSummary(pending.shift()));
226
225
  }
227
- if (pinFirstUser && index === firstUserIndex) {
226
+ if (index === firstUserIndex && firstUserIndex >= 0) {
228
227
  result.push(messages[index]);
229
228
  continue;
230
229
  }
@@ -238,15 +237,6 @@ function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
238
237
  }
239
238
  return result;
240
239
  }
241
- function firstUserDropSafe(messages, firstUserIndex, coveredBases) {
242
- for (let i = firstUserIndex + 1; i < messages.length; i++) {
243
- const message = messages[i];
244
- if (isCovered(message.id, coveredBases)) continue;
245
- if (isRenderedSummaryMessage(message)) continue;
246
- return message.role === "user";
247
- }
248
- return false;
249
- }
250
240
  function renderSummary(anchor) {
251
241
  const body = anchor.summary.trim();
252
242
  const topicLine = anchor.topic ? `${SUMMARY_HEADER} \u2014 ${anchor.topic}` : SUMMARY_HEADER;
@@ -6068,6 +6058,8 @@ DROP \u2014 keep the signal, discard the vessel: verbose logs once the error/res
6068
6058
 
6069
6059
  PRIORITY when compacting: 1. user goal/evolution/intent/hard constraints \xB7 2. decisions + rationale \xB7 3. exact artifacts (paths, signatures, errors, values) \xB7 4. conclusions \xB7 5. lessons learned (what failed and why).
6070
6060
 
6061
+ Language: write the summary in the conversation's PRIMARY LANGUAGE; never translate a monolingual conversation without an explicit user request; mixed-language passages, code, commands, identifiers, and quotes stay as written.
6062
+
6071
6063
  Format: dense scannable bullets under short thematic headers, not narrative prose; every line earns its place. Do not mimic the style of existing summaries in context; follow these rules.`;
6072
6064
  var leanPack = {
6073
6065
  name: "lean",