opencode-acp 1.13.2 → 1.13.6
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/README.md +39 -3
- package/README.zh-CN.md +39 -3
- package/dist/index.js +237 -52
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/quality-gate/evaluate.d.ts +13 -0
- package/dist/lib/compress/quality-gate/evaluate.d.ts.map +1 -1
- package/dist/lib/compress/quality-gate/index.d.ts +3 -1
- package/dist/lib/compress/quality-gate/index.d.ts.map +1 -1
- package/dist/lib/compress/quality-gate/rejection.d.ts +11 -0
- package/dist/lib/compress/quality-gate/rejection.d.ts.map +1 -0
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/types.d.ts +6 -0
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +12 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -309,8 +309,12 @@ Each level overrides the previous, so project settings take priority over global
|
|
|
309
309
|
// Controls how likely compression is after user messages
|
|
310
310
|
// ("strong" = more likely, "soft" = less likely)
|
|
311
311
|
"nudgeForce": "soft",
|
|
312
|
-
//
|
|
313
|
-
|
|
312
|
+
// Hard-excluded tool names. The root default is ["skill", "compress"]; an explicit
|
|
313
|
+
// array replaces the inherited policy. Use [] to compress all tool outputs.
|
|
314
|
+
// "compress" is always force-protected regardless of this setting — its summary
|
|
315
|
+
// parameter is the sole record of compressed conversation and cannot be recovered
|
|
316
|
+
// if lost. Use [] to compress all tool outputs except compress itself.
|
|
317
|
+
"protectedTools": ["skill", "compress"],
|
|
314
318
|
// Preserve text wrapped in <protect>...</protect> when compressed
|
|
315
319
|
"protectTags": false,
|
|
316
320
|
// Preserve your messages during compression.
|
|
@@ -399,7 +403,7 @@ By default, these tools are always protected from pruning:
|
|
|
399
403
|
|
|
400
404
|
The `protectedTools` arrays in `commands` and `strategies` add to this default list.
|
|
401
405
|
|
|
402
|
-
For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are **hard-excluded** from compression ranges (v1.10.0+). When the model compresses a range that includes a protected tool message, that message survives intact in visible context — only the surrounding non-protected messages are compressed.
|
|
406
|
+
For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are **hard-excluded** from compression ranges (v1.10.0+). When the model compresses a range that includes a protected tool message, that message survives intact in visible context — only the surrounding non-protected messages are compressed. The root default is `["skill", "compress"]` (the `compress` entry protects compress tool calls — which carry summaries — from being eaten by subsequent sequential compressions); an explicit array replaces the inherited policy. **`"compress"` is always force-protected regardless of user config** — its `summary` parameter is the sole record of compressed conversation and cannot be recovered if lost. Setting `[]` protects only `compress`; setting `["task"]` protects `task` and `compress`.
|
|
403
407
|
|
|
404
408
|
---
|
|
405
409
|
|
|
@@ -471,6 +475,38 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
|
|
|
471
475
|
|
|
472
476
|
## Changelog
|
|
473
477
|
|
|
478
|
+
### v1.13.6 — Force-Protect Compress Tool Regardless of User Config (PR #188)
|
|
479
|
+
|
|
480
|
+
**Problem**: `compress.protectedTools` uses a replace merge policy (PR #177): a user setting `protectedTools: ["skill"]` or `protectedTools: []` silently removed `"compress"` from the protected list. This made compress summaries — the sole record of compressed conversation — vulnerable to being pruned by subsequent sequential compressions, causing irreversible data loss.
|
|
481
|
+
|
|
482
|
+
**Fix**: Added `FORCE_COMPRESS_PROTECTED = ["compress"]` constant in `lib/config.ts`. In `mergeCompress()`, when a user provides an explicit `protectedTools` array, the constant is spread into the Set to guarantee `"compress"` survives any override. Even `protectedTools: []` now resolves to `["compress"]`. Dual-agent reviewed (Oracle + General, both APPROVE).
|
|
483
|
+
|
|
484
|
+
Files: `lib/config.ts`, `tests/config-protected-tools.test.ts`, `README.md`, `README.zh-CN.md`. 846 tests pass.
|
|
485
|
+
|
|
486
|
+
### v1.13.5 — Fix Release CI for Squash Merges (PR #187)
|
|
487
|
+
|
|
488
|
+
**Problem**: The release detection regex in `.github/workflows/release.yml` only matched standard merge commits (`Merge pull request #N from .../YYYY-MM-DD_release-v...`), not squash merges. PRs #182 (v1.13.3) and #186 (v1.13.4) were squash-merged, so the release workflow silently skipped — no tag, no npm publish, no GitHub Release. npm was stuck at 1.13.2 while master had already moved to 1.13.4.
|
|
489
|
+
|
|
490
|
+
**Fix**: Added a second pattern to the detection logic: `^release: v[0-9]+\.[0-9]+\.[0-9]+` matches squash merge commit titles that start with the release PR title convention (`release: vVERSION ...`). Both standard and squash merges are now detected. Also bumps version to 1.13.5 to publish all accumulated changes (v1.13.3 quality gate + v1.13.4 compress protection + this CI fix).
|
|
491
|
+
|
|
492
|
+
Files: `.github/workflows/release.yml`, `package.json`, `README.md`, `README.zh-CN.md`. 843 tests pass (no source code changes).
|
|
493
|
+
|
|
494
|
+
### v1.13.4 — Protect Compress Tool Calls from Being Compressed (PR #185)
|
|
495
|
+
|
|
496
|
+
**Problem**: Sequential compressions ate previous summaries. Each compress tool call (which carries the summary in its `summary` parameter) lives a few messages after the range it compressed. When the model issued a new compress whose range started right after the previous one's end, the previous compress call fell inside the new range and was pruned — destroying the accumulated summary chain. Evidence from `ses_07562b88`: 113 messages → 6 messages in one compress call because all previous compress call anchors (b5–b10) were inside the new range.
|
|
497
|
+
|
|
498
|
+
**Fix**: Added `"compress"` to `COMPRESS_DEFAULT_PROTECTED_TOOLS` in `lib/config.ts`. This makes `filterProtectedToolMessages` hard-exclude compress tool call messages from compression ranges (Bug 39 mechanism). The compress call survives intact in visible context; only surrounding non-protected messages are compressed. Also synced stale `["skill"]` defaults in `dcp.schema.json`, `README.md`, and `README.zh-CN.md` to `["skill", "compress"]`. Users can opt out with `compress.protectedTools: ["skill"]`.
|
|
499
|
+
|
|
500
|
+
Files: `lib/config.ts`, `dcp.schema.json`, `README.md`, `README.zh-CN.md`. Tests: `tests/protect-compress-calls.test.ts` (6 new tests). 843 pass.
|
|
501
|
+
|
|
502
|
+
### v1.13.3 — Quality Gate Enforcement + E2E Test Framework + protectedTools Fix (PRs #173, #174, #175, #177, #179)
|
|
503
|
+
|
|
504
|
+
**Problem**: (1) Compressions with extremely low retention (<1%) or near-zero keyword recall passed silently, causing severe context loss. (2) No end-to-end test infrastructure existed to verify ACP compression through the real opencode→LLM pipeline. (3) `compress.protectedTools` merged with inherited defaults instead of replacing them — an explicit `[]` still protected the inherited set.
|
|
505
|
+
|
|
506
|
+
**Fix**: (1) PR #173 — New opt-in `qualityGate` config (`enabled: false` by default). Pre-commit evaluation via ROUGE-1 recall + L1 length floor. Rejected compressions return a structured error with recovery guidance (split range or write denser summary). `qualityGateRetryPending` flag tracks rejection state. (2) PR #174 — `scripts/e2e/` framework: fake LLM server (OpenAI-compatible SSE), scripted JSON scenarios, state verifier. 4 baseline scenarios. (3) PR #175 — 18 new proportional baseline adjustment tests. (4) PR #177 — `compress.protectedTools` now replaces inherited defaults; explicit `[]` protects nothing. (5) PR #179 — AGENTS.md §5.1.1.2: absolute prohibition on Agent merging PRs.
|
|
507
|
+
|
|
508
|
+
Files: `lib/compress/quality-gate/`, `lib/compress/{range,message}.ts`, `lib/config.ts`, `scripts/e2e/`, `tests/proportional-baseline.test.ts`, `tests/quality-gate-enforcement.test.ts`, `AGENTS.md`. Tests: 837 pass.
|
|
509
|
+
|
|
474
510
|
### v1.13.2 — Preserve Last User Msg + Config Defaults Tuning (PR #169)
|
|
475
511
|
|
|
476
512
|
**Problem**: Two issues remained after v1.13.1's notification freeze fix. (1) When the model compressed a range that covered all visible user messages, the next API call had zero user-role messages — zhipuai-lb rejected this with the same HTTP 400 code 1214 (`isRetryable: false`), freezing the session. This was the second path to the same freeze that v1.13.1's empty-notification fix addressed. (2) The default `pruneNotification: "detailed"` fired a toast on every compress call (10–30 per session is typical), which was over-intrusive for a routine background operation. Additionally, `compress.maxSummaryLengthHard: 10000` rejected ~25% of information-dense useful summaries in real sessions.
|
package/README.zh-CN.md
CHANGED
|
@@ -278,8 +278,12 @@ ACP 使用自己的配置文件,按以下顺序搜索:
|
|
|
278
278
|
// Controls how likely compression is after user messages
|
|
279
279
|
// ("strong" = more likely, "soft" = less likely)
|
|
280
280
|
"nudgeForce": "soft",
|
|
281
|
-
//
|
|
282
|
-
|
|
281
|
+
// Hard-excluded tool names. The root default is ["skill", "compress"]; an explicit
|
|
282
|
+
// array replaces the inherited policy. Use [] to compress all tool outputs.
|
|
283
|
+
// "compress" is always force-protected regardless of this setting — its summary
|
|
284
|
+
// parameter is the sole record of compressed conversation and cannot be recovered
|
|
285
|
+
// if lost. Use [] to compress all tool outputs except compress itself.
|
|
286
|
+
"protectedTools": ["skill", "compress"],
|
|
283
287
|
// Preserve text wrapped in <protect>...</protect> when compressed
|
|
284
288
|
"protectTags": false,
|
|
285
289
|
// Preserve your messages during compression.
|
|
@@ -367,7 +371,7 @@ ACP 暴露六个可编辑的 prompt:
|
|
|
367
371
|
|
|
368
372
|
`commands` 和 `strategies` 中的 `protectedTools` 数组会添加到此默认列表。
|
|
369
373
|
|
|
370
|
-
对于 `compress` 工具,`compress.protectedTools` 确保特定工具的输出被**硬排除**在压缩范围之外(v1.10.0+)。当模型压缩包含受保护工具消息的范围时,该消息完整保留在可见上下文中 —
|
|
374
|
+
对于 `compress` 工具,`compress.protectedTools` 确保特定工具的输出被**硬排除**在压缩范围之外(v1.10.0+)。当模型压缩包含受保护工具消息的范围时,该消息完整保留在可见上下文中 — 只有周围的非受保护消息被压缩。根默认值为 `["skill", "compress"]`(`compress` 条目保护携带 summary 的 compress 工具调用,防止被后续顺序压缩吞噬);显式数组会替换继承的策略。**`"compress"` 无论用户如何配置都会被强制保护** — 其 `summary` 参数是已压缩对话的唯一记录,一旦丢失无法恢复。设置 `[]` 仅保护 `compress`;设置 `["task"]` 保护 `task` 和 `compress`。
|
|
371
375
|
|
|
372
376
|
---
|
|
373
377
|
|
|
@@ -439,6 +443,38 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
439
443
|
|
|
440
444
|
## 更新日志
|
|
441
445
|
|
|
446
|
+
### v1.13.6 — 强制保护 compress 工具,无视用户配置(PR #188)
|
|
447
|
+
|
|
448
|
+
**问题**:`compress.protectedTools` 使用替换式合并策略(PR #177):用户设置 `protectedTools: ["skill"]` 或 `protectedTools: []` 会静默地从保护列表中移除 `"compress"`。这使得 compress 摘要 — 压缩对话的唯一记录 — 容易被后续的顺序压缩裁剪,导致不可恢复的数据丢失。
|
|
449
|
+
|
|
450
|
+
**修复**:在 `lib/config.ts` 中添加 `FORCE_COMPRESS_PROTECTED = ["compress"]` 常量。在 `mergeCompress()` 中,当用户提供显式 `protectedTools` 数组时,该常量被展开到 Set 中,保证 `"compress"` 在任何覆盖下都保留。即使 `protectedTools: []` 现在也会解析为 `["compress"]`。双 agent 审查通过(Oracle + General,均 APPROVE)。
|
|
451
|
+
|
|
452
|
+
文件:`lib/config.ts`、`tests/config-protected-tools.test.ts`、`README.md`、`README.zh-CN.md`。846 项测试通过。
|
|
453
|
+
|
|
454
|
+
### v1.13.5 — 修复 Release CI 对 Squash Merge 的检测(PR #187)
|
|
455
|
+
|
|
456
|
+
**问题**:`.github/workflows/release.yml` 的发布检测正则只认标准 merge commit(`Merge pull request #N from .../YYYY-MM-DD_release-v...`),不认 squash merge。PR #182(v1.13.3)和 #186(v1.13.4)都是 squash 合并,导致 release workflow 静默跳过 — 没有 tag、没有 npm 发布、没有 GitHub Release。npm 卡在 1.13.2,而 master 已经到了 1.13.4。
|
|
457
|
+
|
|
458
|
+
**修复**:在检测逻辑中添加第二个模式:`^release: v[0-9]+\.[0-9]+\.[0-9]+` 匹配以 release PR 标题开头的 squash merge commit(`release: vVERSION ...`)。现在标准 merge 和 squash merge 都能被检测到。同时将版本号升到 1.13.5,以发布所有累积的变更(v1.13.3 质量门禁 + v1.13.4 compress 保护 + 本次 CI 修复)。
|
|
459
|
+
|
|
460
|
+
文件:`.github/workflows/release.yml`、`package.json`、`README.md`、`README.zh-CN.md`。843 测试通过(无源码变更)。
|
|
461
|
+
|
|
462
|
+
### v1.13.4 — 保护 compress 工具调用不被压缩(PR #185)
|
|
463
|
+
|
|
464
|
+
**问题**:顺序压缩会吞噬之前的 summary。每个 compress 工具调用(在其 `summary` 参数中携带摘要)位于其压缩范围之后几条消息处。当模型发出新的 compress,其范围紧接前一个范围的结尾开始时,前一个 compress 调用落入新范围内并被裁剪——摧毁累积的摘要链。`ses_07562b88` 的证据:113 条消息在一次 compress 调用中变为 6 条,因为所有之前的 compress 调用锚点(b5–b10)都在新范围内。
|
|
465
|
+
|
|
466
|
+
**修复**:在 `lib/config.ts` 的 `COMPRESS_DEFAULT_PROTECTED_TOOLS` 中添加 `"compress"`。这使得 `filterProtectedToolMessages` 硬排除 compress 工具调用消息不进入压缩范围(Bug 39 机制)。compress 调用完整保留在可见上下文中;只有周围的非受保护消息被压缩。同时将 `dcp.schema.json`、`README.md` 和 `README.zh-CN.md` 中过时的 `["skill"]` 默认值同步为 `["skill", "compress"]`。用户可通过 `compress.protectedTools: ["skill"]` 退出。
|
|
467
|
+
|
|
468
|
+
文件:`lib/config.ts`、`dcp.schema.json`、`README.md`、`README.zh-CN.md`。测试:`tests/protect-compress-calls.test.ts`(6 个新测试)。843 pass。
|
|
469
|
+
|
|
470
|
+
### v1.13.3 — 质量门禁 + E2E 测试框架 + protectedTools 修复(PR #173, #174, #175, #177, #179)
|
|
471
|
+
|
|
472
|
+
**问题**:(1)极低保留率(<1%)或接近零关键词召回的压缩会静默通过,导致严重的上下文丢失。(2)缺少端到端测试基础设施来验证 ACP 通过真实 opencode→LLM 管道的压缩行为。(3)`compress.protectedTools` 与继承的默认值合并而非替换——显式 `[]` 仍会保护继承的集合。
|
|
473
|
+
|
|
474
|
+
**修复**:(1)PR #173——新增可选 `qualityGate` 配置(默认 `enabled: false`)。提交前通过 ROUGE-1 召回率 + L1 长度下限评估。被拒绝的压缩返回结构化错误并附带恢复指引(拆分范围或写更密的摘要)。`qualityGateRetryPending` 标志跟踪拒绝状态。(2)PR #174——`scripts/e2e/` 框架:fake LLM 服务器(OpenAI 兼容 SSE)、脚本化 JSON 场景、状态验证器。4 个基线场景。(3)PR #175——18 个新的比例基线调整测试。(4)PR #177——`compress.protectedTools` 现在替换继承的默认值;显式 `[]` 不保护任何工具。(5)PR #179——AGENTS.md §5.1.1.2:绝对禁止 Agent 合并 PR。
|
|
475
|
+
|
|
476
|
+
文件:`lib/compress/quality-gate/`、`lib/compress/{range,message}.ts`、`lib/config.ts`、`scripts/e2e/`、`tests/proportional-baseline.test.ts`、`tests/quality-gate-enforcement.test.ts`、`AGENTS.md`。测试:837 pass。
|
|
477
|
+
|
|
442
478
|
### v1.13.2 — 保留最近用户消息 + 配置默认值调优(PR #169)
|
|
443
479
|
|
|
444
480
|
**问题**:v1.13.1 的通知冻结修复之后还剩两个问题。(1)当模型压缩的范围覆盖了所有可见的 user 消息时,下一次 API 调用中 user 角色消息数量为零——zhipuai-lb 以同样的 HTTP 400 code 1214(`isRetryable: false`)拒绝,会话冻结。这是 v1.13.1 修复的空通知路径之外,通往同一冻结 bug 的第二条路径。(2)默认 `pruneNotification: "detailed"` 每次压缩都弹 toast(典型会话 10–30 次),对例行后台操作来说过于打扰。另外 `compress.maxSummaryLengthHard: 10000` 在真实会话中拒绝了约 25% 信息密度高的有用摘要。
|
package/dist/index.js
CHANGED
|
@@ -1516,7 +1516,8 @@ var DEFAULT_PROTECTED_TOOLS = [
|
|
|
1516
1516
|
"write",
|
|
1517
1517
|
"edit"
|
|
1518
1518
|
];
|
|
1519
|
-
var COMPRESS_DEFAULT_PROTECTED_TOOLS = ["skill"];
|
|
1519
|
+
var COMPRESS_DEFAULT_PROTECTED_TOOLS = ["skill", "compress"];
|
|
1520
|
+
var FORCE_COMPRESS_PROTECTED = ["compress"];
|
|
1520
1521
|
function showConfigWarnings(ctx, configPath, configData, isProject) {
|
|
1521
1522
|
const invalidKeys = getInvalidConfigKeys(configData);
|
|
1522
1523
|
const typeErrors = validateConfigTypes(configData);
|
|
@@ -1632,7 +1633,7 @@ var defaultConfig = {
|
|
|
1632
1633
|
algorithms: {
|
|
1633
1634
|
"rouge-recall-v1": {
|
|
1634
1635
|
layer1MinChars: 200,
|
|
1635
|
-
layer1MinRetentionPct:
|
|
1636
|
+
layer1MinRetentionPct: 5,
|
|
1636
1637
|
layer2MaxRougeF1: 0.05,
|
|
1637
1638
|
layer2MaxTop20Recall: 0.2
|
|
1638
1639
|
}
|
|
@@ -1765,7 +1766,7 @@ function mergeCompress(base, override) {
|
|
|
1765
1766
|
toolOutputNudgeThreshold: override.toolOutputNudgeThreshold,
|
|
1766
1767
|
iterationNudgeThreshold: override.iterationNudgeThreshold ?? base.iterationNudgeThreshold,
|
|
1767
1768
|
nudgeForce: override.nudgeForce ?? base.nudgeForce,
|
|
1768
|
-
protectedTools: [.../* @__PURE__ */ new Set([...
|
|
1769
|
+
protectedTools: Array.isArray(override.protectedTools) ? [.../* @__PURE__ */ new Set([...override.protectedTools, ...FORCE_COMPRESS_PROTECTED])] : base.protectedTools,
|
|
1769
1770
|
protectTags: override.protectTags ?? base.protectTags,
|
|
1770
1771
|
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages,
|
|
1771
1772
|
maxSummaryLengthHard: override.maxSummaryLengthHard ?? base.maxSummaryLengthHard,
|
|
@@ -4445,7 +4446,8 @@ function createSessionState() {
|
|
|
4445
4446
|
lastCompaction: 0,
|
|
4446
4447
|
currentTurn: 0,
|
|
4447
4448
|
modelContextLimit: void 0,
|
|
4448
|
-
systemPromptTokens: void 0
|
|
4449
|
+
systemPromptTokens: void 0,
|
|
4450
|
+
qualityGateRetryPending: false
|
|
4449
4451
|
};
|
|
4450
4452
|
}
|
|
4451
4453
|
function resetSessionState(state) {
|
|
@@ -4485,6 +4487,7 @@ function resetSessionState(state) {
|
|
|
4485
4487
|
state.currentTurn = 0;
|
|
4486
4488
|
state.modelContextLimit = void 0;
|
|
4487
4489
|
state.systemPromptTokens = void 0;
|
|
4490
|
+
state.qualityGateRetryPending = false;
|
|
4488
4491
|
}
|
|
4489
4492
|
async function ensureSessionInitialized(client, state, sessionId, logger, messages, manualModeEnabled, config) {
|
|
4490
4493
|
if (state.sessionId === sessionId) {
|
|
@@ -5974,6 +5977,164 @@ function evaluateBatchQuality(state, rawMessages, entries, config, logger) {
|
|
|
5974
5977
|
failures
|
|
5975
5978
|
};
|
|
5976
5979
|
}
|
|
5980
|
+
function evaluatePreCommitQuality(rawMessages, messageIds, messageTokenById, summary, config, logger) {
|
|
5981
|
+
const qg = config.qualityGate;
|
|
5982
|
+
if (!qg || qg.enabled !== true) return null;
|
|
5983
|
+
ensureBuiltinGatesRegistered();
|
|
5984
|
+
const algoName = qg.algorithm;
|
|
5985
|
+
if (!algoName) {
|
|
5986
|
+
logger.warn("Quality gate enabled but no algorithm specified", {});
|
|
5987
|
+
return null;
|
|
5988
|
+
}
|
|
5989
|
+
const gate = getQualityGate(algoName);
|
|
5990
|
+
if (!gate) {
|
|
5991
|
+
logger.warn("Quality gate algorithm not found in registry", { algorithm: algoName });
|
|
5992
|
+
return null;
|
|
5993
|
+
}
|
|
5994
|
+
if (messageIds.length === 0) return null;
|
|
5995
|
+
const idToMsg = /* @__PURE__ */ new Map();
|
|
5996
|
+
for (const m of rawMessages) {
|
|
5997
|
+
const id = m?.info?.id;
|
|
5998
|
+
if (typeof id === "string") idToMsg.set(id, m);
|
|
5999
|
+
}
|
|
6000
|
+
const chunks = [];
|
|
6001
|
+
let compressedTokens = 0;
|
|
6002
|
+
for (const id of messageIds) {
|
|
6003
|
+
const m = idToMsg.get(id);
|
|
6004
|
+
if (m) chunks.push(extractMessageText(m.parts));
|
|
6005
|
+
compressedTokens += messageTokenById.get(id) || 0;
|
|
6006
|
+
}
|
|
6007
|
+
if (chunks.length === 0) return null;
|
|
6008
|
+
const originalText = chunks.join("\n");
|
|
6009
|
+
const pseudoBlock = {
|
|
6010
|
+
blockId: -1,
|
|
6011
|
+
summary,
|
|
6012
|
+
compressedTokens,
|
|
6013
|
+
directMessageIds: messageIds,
|
|
6014
|
+
effectiveMessageIds: messageIds
|
|
6015
|
+
};
|
|
6016
|
+
const ctx = {
|
|
6017
|
+
block: pseudoBlock,
|
|
6018
|
+
summary,
|
|
6019
|
+
originalChunks: chunks,
|
|
6020
|
+
originalText,
|
|
6021
|
+
originalTokens: Math.ceil(originalText.length / CHARS_PER_TOKEN_ESTIMATE)
|
|
6022
|
+
};
|
|
6023
|
+
const algoConfig = (qg.algorithms && qg.algorithms[algoName]) ?? {};
|
|
6024
|
+
try {
|
|
6025
|
+
return gate.evaluate(ctx, algoConfig);
|
|
6026
|
+
} catch (err) {
|
|
6027
|
+
logger.warn("Pre-commit quality gate threw \u2014 treating as pass", {
|
|
6028
|
+
gate: gate.name,
|
|
6029
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6030
|
+
});
|
|
6031
|
+
return { passed: true, metrics: [] };
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
// node_modules/context-compress-algorithms/dist/chunk-ZRHPFN6B.js
|
|
6036
|
+
var COMPRESS_PHILOSOPHY = `Compression Philosophy:
|
|
6037
|
+
- All compression serves the primary task, but be frugal.
|
|
6038
|
+
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
6039
|
+
- Compress by need, not by percentage.
|
|
6040
|
+
- 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.
|
|
6041
|
+
- Curate summaries like a well-structured document. User prompts, compressed tool outputs, code, logs, or skill-call intermediate results that are critically important should be preserved \u2014 not by exempting them from compression, but by embedding them in the summary via [[KEEP:mNNNNN]] (auto-expanded verbatim) and [[REF:mNNNNN|description]] (compact link).`;
|
|
6042
|
+
var HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS
|
|
6043
|
+
|
|
6044
|
+
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.
|
|
6045
|
+
|
|
6046
|
+
KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
|
|
6047
|
+
- 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.
|
|
6048
|
+
- 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").
|
|
6049
|
+
- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
|
|
6050
|
+
- 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\xD7 PPL gap because KV store is static", not "KVNet underperforms").
|
|
6051
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
|
|
6052
|
+
- Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
|
|
6053
|
+
- Exact values: versions, config keys, thresholds, magic numbers.
|
|
6054
|
+
- User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
|
|
6055
|
+
- 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.
|
|
6056
|
+
- 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.
|
|
6057
|
+
- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
|
|
6058
|
+
- Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
|
|
6059
|
+
|
|
6060
|
+
DROP \u2014 extract the signal, discard the vessel:
|
|
6061
|
+
- Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
|
|
6062
|
+
- Duplicate file reads once the needed content is recorded.
|
|
6063
|
+
- 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).
|
|
6064
|
+
- Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
|
|
6065
|
+
- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
|
|
6066
|
+
- Repeated status checks (\`git status\`, \`ls\`) once state is known.
|
|
6067
|
+
|
|
6068
|
+
For 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.
|
|
6069
|
+
|
|
6070
|
+
KEEP MARKERS: \`[[KEEP:mNNNNN]]\` expands original message content into the summary (truncated to a max length). Do NOT use KEEP for verbose command output, diagnostic scripts, log dumps, or any content whose value is in the conclusion rather than the raw output \u2014 summarize these or use \`[[REF:mNNNNN|desc]]\` instead.
|
|
6071
|
+
|
|
6072
|
+
PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
6073
|
+
1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
|
|
6074
|
+
2. Decisions and rationale.
|
|
6075
|
+
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
6076
|
+
4. Conclusions and key findings.
|
|
6077
|
+
5. Lessons learned: what failed and why.
|
|
6078
|
+
|
|
6079
|
+
Write 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.`;
|
|
6080
|
+
|
|
6081
|
+
// lib/compress/quality-gate/rejection.ts
|
|
6082
|
+
function formatMetric(result, name) {
|
|
6083
|
+
const m = result.metrics.find((x) => x.name === name);
|
|
6084
|
+
if (!m) return "?";
|
|
6085
|
+
switch (m.format) {
|
|
6086
|
+
case "percent":
|
|
6087
|
+
return `${m.value.toFixed(2)}%`;
|
|
6088
|
+
case "ratio":
|
|
6089
|
+
return m.value.toFixed(4);
|
|
6090
|
+
default:
|
|
6091
|
+
return String(m.value);
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
function computeStats(plan) {
|
|
6095
|
+
let originalTokens = 0;
|
|
6096
|
+
for (const id of plan.messageIds) {
|
|
6097
|
+
originalTokens += plan.messageTokenById.get(id) || 0;
|
|
6098
|
+
}
|
|
6099
|
+
const summaryChars = plan.summary.length;
|
|
6100
|
+
const ratio = originalTokens > 0 ? (originalTokens / Math.max(summaryChars / 4, 1)).toFixed(1) : "?";
|
|
6101
|
+
const retentionPct = originalTokens > 0 ? (summaryChars / (originalTokens * 4) * 100).toFixed(2) : "?";
|
|
6102
|
+
return { originalTokens, summaryChars, ratio, retentionPct };
|
|
6103
|
+
}
|
|
6104
|
+
function buildQualityRejectionError(plan, result) {
|
|
6105
|
+
const stats = computeStats(plan);
|
|
6106
|
+
const metrics = [
|
|
6107
|
+
`Original: ~${stats.originalTokens} tokens`,
|
|
6108
|
+
`Summary: ${stats.summaryChars} chars`,
|
|
6109
|
+
`Ratio: ${stats.ratio}:1`,
|
|
6110
|
+
`Retention: ${stats.retentionPct}%`,
|
|
6111
|
+
`Gate layer: ${result.layer ?? "unknown"}`,
|
|
6112
|
+
`rougeF1: ${formatMetric(result, "rougeF1")}`,
|
|
6113
|
+
`top20Recall: ${formatMetric(result, "top20Recall")}`
|
|
6114
|
+
];
|
|
6115
|
+
const message = `\u26A0\uFE0F COMPRESSION REJECTED \u2014 QUALITY GATE FAILURE
|
|
6116
|
+
|
|
6117
|
+
Range: ${plan.startId}\u2013${plan.endId}
|
|
6118
|
+
${metrics.join("\n")}
|
|
6119
|
+
|
|
6120
|
+
\u26A0\uFE0F CRITICAL: Compression is the ONLY mechanism for preserving historical context in this session.
|
|
6121
|
+
Once a compression is accepted, the original messages are permanently removed from visible context.
|
|
6122
|
+
Your summary becomes the SOLE record. If it fails, subsequent work is built on a broken foundation \u2014
|
|
6123
|
+
memory loss \u2192 wrong assumptions \u2192 entire reasoning chain collapse.
|
|
6124
|
+
Treat every compression with maximum care.
|
|
6125
|
+
|
|
6126
|
+
${HOW_TO_COMPRESS_RULES}
|
|
6127
|
+
|
|
6128
|
+
To retry: rewrite a more complete summary that preserves critical details (file paths, decisions,
|
|
6129
|
+
exact values, errors). Then add "acknowledgeRisk": true to the compress tool call parameters.
|
|
6130
|
+
Without acknowledgeRisk: true, the compression will be rejected again.`;
|
|
6131
|
+
return new Error(message);
|
|
6132
|
+
}
|
|
6133
|
+
function buildPreemptiveAcknowledgeError() {
|
|
6134
|
+
return new Error(
|
|
6135
|
+
'Parameter "acknowledgeRisk": true was provided, but no quality gate rejection is pending. This parameter is only valid immediately after a compression was rejected by the quality gate. Remove it and try again.'
|
|
6136
|
+
);
|
|
6137
|
+
}
|
|
5977
6138
|
|
|
5978
6139
|
// lib/compress/pipeline.ts
|
|
5979
6140
|
function snapshotCompressionState(state) {
|
|
@@ -6242,7 +6403,8 @@ function buildSchema(maxSummaryLengthHard) {
|
|
|
6242
6403
|
),
|
|
6243
6404
|
dangerous: tool2.schema.boolean().optional().describe(
|
|
6244
6405
|
"Set to true ONLY when you are certain the most recent message(s) must be compressed. Required when a range includes the tail of the conversation."
|
|
6245
|
-
)
|
|
6406
|
+
),
|
|
6407
|
+
acknowledgeRisk: tool2.schema.boolean().optional()
|
|
6246
6408
|
};
|
|
6247
6409
|
}
|
|
6248
6410
|
function createCompressMessageTool(ctx) {
|
|
@@ -6341,6 +6503,39 @@ function createCompressMessageTool(ctx) {
|
|
|
6341
6503
|
}))
|
|
6342
6504
|
);
|
|
6343
6505
|
if (phantomError) throw phantomError;
|
|
6506
|
+
const acknowledgeRisk = args.acknowledgeRisk === true;
|
|
6507
|
+
const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
|
|
6508
|
+
if (acknowledgeRisk && !ctx.state.qualityGateRetryPending) {
|
|
6509
|
+
throw buildPreemptiveAcknowledgeError();
|
|
6510
|
+
}
|
|
6511
|
+
if (acknowledgeRisk) {
|
|
6512
|
+
ctx.state.qualityGateRetryPending = false;
|
|
6513
|
+
} else {
|
|
6514
|
+
ctx.state.qualityGateRetryPending = false;
|
|
6515
|
+
for (const { plan, summaryWithTools } of preparedPlans) {
|
|
6516
|
+
const result = evaluatePreCommitQuality(
|
|
6517
|
+
rawMessages,
|
|
6518
|
+
plan.selection.messageIds,
|
|
6519
|
+
plan.selection.messageTokenById,
|
|
6520
|
+
summaryWithTools,
|
|
6521
|
+
ctx.config,
|
|
6522
|
+
ctx.logger
|
|
6523
|
+
);
|
|
6524
|
+
if (result && !result.passed) {
|
|
6525
|
+
ctx.state.qualityGateRetryPending = true;
|
|
6526
|
+
throw buildQualityRejectionError(
|
|
6527
|
+
{
|
|
6528
|
+
startId: plan.entry.messageId,
|
|
6529
|
+
endId: plan.entry.messageId,
|
|
6530
|
+
summary: summaryWithTools,
|
|
6531
|
+
messageIds: plan.selection.messageIds,
|
|
6532
|
+
messageTokenById: plan.selection.messageTokenById
|
|
6533
|
+
},
|
|
6534
|
+
result
|
|
6535
|
+
);
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6344
6539
|
const snapshot = snapshotCompressionState(ctx.state);
|
|
6345
6540
|
const runId = allocateRunId(ctx.state);
|
|
6346
6541
|
try {
|
|
@@ -6385,6 +6580,7 @@ function createCompressMessageTool(ctx) {
|
|
|
6385
6580
|
await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
|
|
6386
6581
|
} catch (error) {
|
|
6387
6582
|
restoreCompressionState(ctx.state, snapshot);
|
|
6583
|
+
ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
|
|
6388
6584
|
throw error;
|
|
6389
6585
|
}
|
|
6390
6586
|
return formatResult(plans.length, skippedIssues, skippedCount);
|
|
@@ -6420,7 +6616,8 @@ function buildSchema2(maxSummaryLengthHard) {
|
|
|
6420
6616
|
),
|
|
6421
6617
|
dangerous: tool3.schema.boolean().optional().describe(
|
|
6422
6618
|
"Set to true ONLY when you are certain the most recent message(s) must be compressed. Required when a range includes the tail of the conversation."
|
|
6423
|
-
)
|
|
6619
|
+
),
|
|
6620
|
+
acknowledgeRisk: tool3.schema.boolean().optional()
|
|
6424
6621
|
};
|
|
6425
6622
|
}
|
|
6426
6623
|
function createCompressRangeTool(ctx) {
|
|
@@ -6572,6 +6769,39 @@ function createCompressRangeTool(ctx) {
|
|
|
6572
6769
|
}))
|
|
6573
6770
|
);
|
|
6574
6771
|
if (phantomError) throw phantomError;
|
|
6772
|
+
const acknowledgeRisk = args.acknowledgeRisk === true;
|
|
6773
|
+
const qualityGateRetryPendingBefore = ctx.state.qualityGateRetryPending;
|
|
6774
|
+
if (acknowledgeRisk && !ctx.state.qualityGateRetryPending) {
|
|
6775
|
+
throw buildPreemptiveAcknowledgeError();
|
|
6776
|
+
}
|
|
6777
|
+
if (acknowledgeRisk) {
|
|
6778
|
+
ctx.state.qualityGateRetryPending = false;
|
|
6779
|
+
} else {
|
|
6780
|
+
ctx.state.qualityGateRetryPending = false;
|
|
6781
|
+
for (const plan of preparedPlans) {
|
|
6782
|
+
const result = evaluatePreCommitQuality(
|
|
6783
|
+
rawMessages,
|
|
6784
|
+
plan.selection.messageIds,
|
|
6785
|
+
plan.selection.messageTokenById,
|
|
6786
|
+
plan.finalSummary,
|
|
6787
|
+
ctx.config,
|
|
6788
|
+
ctx.logger
|
|
6789
|
+
);
|
|
6790
|
+
if (result && !result.passed) {
|
|
6791
|
+
ctx.state.qualityGateRetryPending = true;
|
|
6792
|
+
throw buildQualityRejectionError(
|
|
6793
|
+
{
|
|
6794
|
+
startId: plan.entry.startId,
|
|
6795
|
+
endId: plan.entry.endId,
|
|
6796
|
+
summary: plan.finalSummary,
|
|
6797
|
+
messageIds: plan.selection.messageIds,
|
|
6798
|
+
messageTokenById: plan.selection.messageTokenById
|
|
6799
|
+
},
|
|
6800
|
+
result
|
|
6801
|
+
);
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6575
6805
|
const snapshot = snapshotCompressionState(ctx.state);
|
|
6576
6806
|
const runId = allocateRunId(ctx.state);
|
|
6577
6807
|
try {
|
|
@@ -6623,6 +6853,7 @@ function createCompressRangeTool(ctx) {
|
|
|
6623
6853
|
);
|
|
6624
6854
|
} catch (error) {
|
|
6625
6855
|
restoreCompressionState(ctx.state, snapshot);
|
|
6856
|
+
ctx.state.qualityGateRetryPending = qualityGateRetryPendingBefore;
|
|
6626
6857
|
throw error;
|
|
6627
6858
|
}
|
|
6628
6859
|
return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.
|
|
@@ -7963,52 +8194,6 @@ ${lines2.join("\n")}`;
|
|
|
7963
8194
|
${lines.join("\n")}`;
|
|
7964
8195
|
}
|
|
7965
8196
|
|
|
7966
|
-
// node_modules/context-compress-algorithms/dist/chunk-ZRHPFN6B.js
|
|
7967
|
-
var COMPRESS_PHILOSOPHY = `Compression Philosophy:
|
|
7968
|
-
- All compression serves the primary task, but be frugal.
|
|
7969
|
-
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
7970
|
-
- Compress by need, not by percentage.
|
|
7971
|
-
- 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.
|
|
7972
|
-
- Curate summaries like a well-structured document. User prompts, compressed tool outputs, code, logs, or skill-call intermediate results that are critically important should be preserved \u2014 not by exempting them from compression, but by embedding them in the summary via [[KEEP:mNNNNN]] (auto-expanded verbatim) and [[REF:mNNNNN|description]] (compact link).`;
|
|
7973
|
-
var HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS
|
|
7974
|
-
|
|
7975
|
-
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.
|
|
7976
|
-
|
|
7977
|
-
KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
|
|
7978
|
-
- 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.
|
|
7979
|
-
- 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").
|
|
7980
|
-
- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
|
|
7981
|
-
- 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\xD7 PPL gap because KV store is static", not "KVNet underperforms").
|
|
7982
|
-
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
|
|
7983
|
-
- Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
|
|
7984
|
-
- Exact values: versions, config keys, thresholds, magic numbers.
|
|
7985
|
-
- User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
|
|
7986
|
-
- 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.
|
|
7987
|
-
- 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.
|
|
7988
|
-
- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
|
|
7989
|
-
- Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
|
|
7990
|
-
|
|
7991
|
-
DROP \u2014 extract the signal, discard the vessel:
|
|
7992
|
-
- Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
|
|
7993
|
-
- Duplicate file reads once the needed content is recorded.
|
|
7994
|
-
- 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).
|
|
7995
|
-
- Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
|
|
7996
|
-
- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
|
|
7997
|
-
- Repeated status checks (\`git status\`, \`ls\`) once state is known.
|
|
7998
|
-
|
|
7999
|
-
For 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.
|
|
8000
|
-
|
|
8001
|
-
KEEP MARKERS: \`[[KEEP:mNNNNN]]\` expands original message content into the summary (truncated to a max length). Do NOT use KEEP for verbose command output, diagnostic scripts, log dumps, or any content whose value is in the conclusion rather than the raw output \u2014 summarize these or use \`[[REF:mNNNNN|desc]]\` instead.
|
|
8002
|
-
|
|
8003
|
-
PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
8004
|
-
1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
|
|
8005
|
-
2. Decisions and rationale.
|
|
8006
|
-
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
8007
|
-
4. Conclusions and key findings.
|
|
8008
|
-
5. Lessons learned: what failed and why.
|
|
8009
|
-
|
|
8010
|
-
Write 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.`;
|
|
8011
|
-
|
|
8012
8197
|
// lib/messages/inject/inject.ts
|
|
8013
8198
|
var ACP_SUFFIX_SEED = "acp-dynamic-guidance";
|
|
8014
8199
|
function createSuffixMessage(messages) {
|