pi-goosedump 0.12.6 → 0.12.8

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.
Files changed (3) hide show
  1. package/README.md +13 -5
  2. package/dist/index.ts +79 -26
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -71,7 +71,8 @@ Optional settings live under `goosedump` in:
71
71
  ```json
72
72
  {
73
73
  "goosedump": {
74
- "turnsBeforeCompact": 0
74
+ "turnsBeforeCompact": 0,
75
+ "summaryMaxTokens": 4096
75
76
  }
76
77
  }
77
78
  ```
@@ -80,16 +81,23 @@ Optional settings live under `goosedump` in:
80
81
  compaction. The countdown is restored when a session resumes and appears in the
81
82
  footer with a status dot (green when >25% turns remain, yellow when ≤25%, red when ≤12.5%)
82
83
  followed by hexadecimal turn countdown: `● GD:0A` means ten turns remain, `● GD:00` is ready
83
- to compact, and `● GD:--` is disabled. Clearing the project value restores the
84
+ to compact, and `● GD:--` is disabled.
85
+
86
+ `summaryMaxTokens` is an integer from 512 to 65536 (default 4096). The effective
87
+ summary budget is the lower of this value and 80% of Pi's `compaction.reserveTokens`,
88
+ leaving room for the next model response. Clearing a project value restores the
84
89
  global setting.
85
90
 
86
91
  ## Compaction behavior
87
92
 
88
93
  The extension replaces Pi's normal summary during manual, scheduled, and
89
94
  automatic compaction. It invokes `goosedump session compact --remember`,
90
- preserves the previous compaction summary, and summarizes only the newly
91
- compacted range. A `keep:N` request keeps the last N active user turns;
92
- it is cancelled when no earlier history is available to summarize.
95
+ preserves the previous compaction summary, summarizes only the newly compacted
96
+ range, and passes the effective summary budget to goosedump. When necessary,
97
+ goosedump removes older read-only paths, completed items, chronology lines, and
98
+ modified/created paths in that order while preserving goals, blockers, decisions,
99
+ preferences, and the newest actionable context. A `keep:N` request keeps the last
100
+ N active user turns; it is cancelled when no earlier history is available to summarize.
93
101
 
94
102
  Compactions with custom instructions continue to use Pi's normal compactor. If
95
103
  goosedump fails, the extension reports the error and falls back to Pi. Successful
package/dist/index.ts CHANGED
@@ -52,13 +52,22 @@ var MEMORY_HIT_SEPARATOR = `
52
52
  var MEMORY_TEXT_TRUNCATED = `
53
53
  [Entry text truncated]`;
54
54
  var DEFAULT_GOOSEDUMP_SETTINGS = {
55
- turnsBeforeCompact: 0
55
+ turnsBeforeCompact: 0,
56
+ summaryMaxTokens: 4096
56
57
  };
57
58
  var TURNS_BEFORE_COMPACT_MIN = 0;
58
59
  var TURNS_BEFORE_COMPACT_MAX = 255;
60
+ var SUMMARY_MAX_TOKENS_MIN = 512;
61
+ var SUMMARY_MAX_TOKENS_MAX = 65536;
59
62
  function isTurnsBeforeCompact(value) {
60
63
  return typeof value === "number" && Number.isSafeInteger(value) && value >= TURNS_BEFORE_COMPACT_MIN && value <= TURNS_BEFORE_COMPACT_MAX;
61
64
  }
65
+ function isSummaryMaxTokens(value) {
66
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= SUMMARY_MAX_TOKENS_MIN && value <= SUMMARY_MAX_TOKENS_MAX;
67
+ }
68
+ function isGoosedumpSettingValue(key, value) {
69
+ return key === "turnsBeforeCompact" ? isTurnsBeforeCompact(value) : isSummaryMaxTokens(value);
70
+ }
62
71
  function piAgentDir() {
63
72
  return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
64
73
  }
@@ -114,9 +123,12 @@ function readGoosedumpSettingsFrom(path) {
114
123
  if (!isRecord(raw))
115
124
  return {};
116
125
  const settings = {};
117
- const value = raw.turnsBeforeCompact;
118
- if (isTurnsBeforeCompact(value))
119
- settings.turnsBeforeCompact = value;
126
+ if (isTurnsBeforeCompact(raw.turnsBeforeCompact)) {
127
+ settings.turnsBeforeCompact = raw.turnsBeforeCompact;
128
+ }
129
+ if (isSummaryMaxTokens(raw.summaryMaxTokens)) {
130
+ settings.summaryMaxTokens = raw.summaryMaxTokens;
131
+ }
120
132
  return settings;
121
133
  }
122
134
  function validateGoosedumpSettings(path) {
@@ -126,10 +138,14 @@ function validateGoosedumpSettings(path) {
126
138
  if (!isRecord(root.goosedump)) {
127
139
  throw new Error(`${path}: goosedump must be a JSON object`);
128
140
  }
129
- const value = root.goosedump.turnsBeforeCompact;
130
- if (value !== undefined && !isTurnsBeforeCompact(value)) {
141
+ const turns = root.goosedump.turnsBeforeCompact;
142
+ if (turns !== undefined && !isTurnsBeforeCompact(turns)) {
131
143
  throw new Error(`${path}: goosedump.turnsBeforeCompact must be an integer from ` + `${TURNS_BEFORE_COMPACT_MIN} to ${TURNS_BEFORE_COMPACT_MAX}`);
132
144
  }
145
+ const summaryTokens = root.goosedump.summaryMaxTokens;
146
+ if (summaryTokens !== undefined && !isSummaryMaxTokens(summaryTokens)) {
147
+ throw new Error(`${path}: goosedump.summaryMaxTokens must be an integer from ` + `${SUMMARY_MAX_TOKENS_MIN} to ${SUMMARY_MAX_TOKENS_MAX}`);
148
+ }
133
149
  }
134
150
  function resolveGoosedumpSettings(cwd) {
135
151
  return {
@@ -155,9 +171,12 @@ function readScopedGoosedumpSettings(cwd, scope) {
155
171
  if (!isRecord(raw))
156
172
  return { ...DEFAULT_GOOSEDUMP_SETTINGS };
157
173
  const settings = {};
158
- const value = raw.turnsBeforeCompact;
159
- if (isTurnsBeforeCompact(value))
160
- settings.turnsBeforeCompact = value;
174
+ if (isTurnsBeforeCompact(raw.turnsBeforeCompact)) {
175
+ settings.turnsBeforeCompact = raw.turnsBeforeCompact;
176
+ }
177
+ if (isSummaryMaxTokens(raw.summaryMaxTokens)) {
178
+ settings.summaryMaxTokens = raw.summaryMaxTokens;
179
+ }
161
180
  return { ...DEFAULT_GOOSEDUMP_SETTINGS, ...settings };
162
181
  }
163
182
  function writeScopedGoosedumpSetting(cwd, scope, key, value) {
@@ -495,6 +514,7 @@ async function goosedumpCompact(target, options, signal, executablePath) {
495
514
  args.push("--from", options.from);
496
515
  if (options.previousSummary)
497
516
  args.push("--previous-summary", options.previousSummary);
517
+ args.push("--summary-max-tokens", String(options.summaryMaxTokens));
498
518
  args.push("--before", options.until);
499
519
  const output = await runGoosedumpAsync(args, signal, executablePath);
500
520
  for (const line of output.stdout.split(`
@@ -551,6 +571,22 @@ function piKeepRecentTokens(cwd) {
551
571
  }
552
572
  return DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;
553
573
  }
574
+ function piReserveTokens(cwd) {
575
+ for (const path of [projectSettingsPath(cwd), globalSettingsPath()]) {
576
+ const compaction = readJsonObject(path).compaction;
577
+ if (isRecord(compaction) && typeof compaction.reserveTokens === "number") {
578
+ return compaction.reserveTokens;
579
+ }
580
+ }
581
+ return DEFAULT_COMPACTION_SETTINGS.reserveTokens;
582
+ }
583
+ function effectiveSummaryMaxTokens(cwd) {
584
+ const configured = resolveGoosedumpSettings(cwd).summaryMaxTokens;
585
+ const reserveTokens = piReserveTokens(cwd);
586
+ if (reserveTokens <= 0)
587
+ return configured;
588
+ return Math.max(1, Math.min(configured, Math.floor(reserveTokens * 0.8)));
589
+ }
554
590
  function hasCompactableHistory(ctx) {
555
591
  const branch = ctx.sessionManager.getBranch();
556
592
  if (branch.length === 0)
@@ -656,7 +692,7 @@ function utf8Prefix(text, maxBytes) {
656
692
  }
657
693
  return prefix.join("");
658
694
  }
659
- async function buildGoosedumpCompaction(target, preparation, branchEntries, keep, signal, executablePath) {
695
+ async function buildGoosedumpCompaction(target, preparation, branchEntries, summaryMaxTokens, keep, signal, executablePath) {
660
696
  let firstKeptEntryId = preparation.firstKeptEntryId;
661
697
  if (keep !== undefined && keep > 0) {
662
698
  const id = keepBoundaryId(branchEntries, keep);
@@ -668,7 +704,7 @@ async function buildGoosedumpCompaction(target, preparation, branchEntries, keep
668
704
  const range = buildCompactRange(branchEntries, firstKeptEntryId);
669
705
  if (!range)
670
706
  return;
671
- const generated = await goosedumpCompact(target, { ...range, previousSummary: preparation.previousSummary }, signal, executablePath);
707
+ const generated = await goosedumpCompact(target, { ...range, previousSummary: preparation.previousSummary, summaryMaxTokens }, signal, executablePath);
672
708
  if (!generated.summary)
673
709
  return;
674
710
  const summary = generated.summary;
@@ -779,18 +815,35 @@ async function runSettingsEditor(ctx) {
779
815
  const global = readScopedGoosedumpSettings(location, "global");
780
816
  const projectPath = projectSettingsPath(location);
781
817
  const projectRaw = readJsonObjectStrict(projectPath).goosedump;
782
- const projectValue = isRecord(projectRaw) && isTurnsBeforeCompact(projectRaw.turnsBeforeCompact) ? projectRaw.turnsBeforeCompact : undefined;
783
- return ["global", "project"].map((scope) => ({
784
- id: `${scope}:turnsBeforeCompact`,
785
- label: settingLabel(scope, "turnsBeforeCompact"),
786
- description: scope === "project" ? "LLM turns before compaction; empty inherits Global." : "LLM turns before compaction; 0 disables.",
787
- currentValue: String(scope === "project" ? projectValue ?? "" : global.turnsBeforeCompact),
788
- submenu: (currentValue, subDone) => new NumberInputSubmenu(settingLabel(scope, "turnsBeforeCompact"), currentValue === "" ? null : Number(currentValue), (value) => subDone(String(value)), () => subDone(), {
818
+ const project = isRecord(projectRaw) ? projectRaw : {};
819
+ const specs = [
820
+ {
821
+ key: "turnsBeforeCompact",
822
+ description: "LLM turns before compaction; 0 disables.",
789
823
  min: TURNS_BEFORE_COMPACT_MIN,
790
- max: TURNS_BEFORE_COMPACT_MAX,
791
- onClear: scope === "project" ? () => subDone("") : undefined,
792
- onInvalid: (message) => ctx.ui.notify(message, "error")
793
- })
824
+ max: TURNS_BEFORE_COMPACT_MAX
825
+ },
826
+ {
827
+ key: "summaryMaxTokens",
828
+ description: "Maximum estimated tokens in each summary.",
829
+ min: SUMMARY_MAX_TOKENS_MIN,
830
+ max: SUMMARY_MAX_TOKENS_MAX
831
+ }
832
+ ];
833
+ return ["global", "project"].flatMap((scope) => specs.map(({ key, description, min, max }) => {
834
+ const projectValue = isGoosedumpSettingValue(key, project[key]) ? project[key] : undefined;
835
+ return {
836
+ id: `${scope}:${key}`,
837
+ label: settingLabel(scope, key),
838
+ description: scope === "project" ? `${description} Empty inherits Global.` : description,
839
+ currentValue: String(scope === "project" ? projectValue ?? "" : global[key]),
840
+ submenu: (currentValue, subDone) => new NumberInputSubmenu(settingLabel(scope, key), currentValue === "" ? null : Number(currentValue), (value) => subDone(String(value)), () => subDone(), {
841
+ min,
842
+ max,
843
+ onClear: scope === "project" ? () => subDone("") : undefined,
844
+ onInvalid: (message) => ctx.ui.notify(message, "error")
845
+ })
846
+ };
794
847
  }));
795
848
  }
796
849
  function rebuildSettingsList() {
@@ -809,12 +862,12 @@ async function runSettingsEditor(ctx) {
809
862
  return;
810
863
  }
811
864
  const parsed = Number(newValue);
812
- if (!isTurnsBeforeCompact(parsed))
865
+ if (!isGoosedumpSettingValue(key, parsed))
813
866
  return;
814
867
  try {
815
868
  writeScopedGoosedumpSetting(location, scope, key, parsed);
816
869
  settingsList.updateValue(id, newValue);
817
- ctx.ui.notify(`${settingLabel(scope, key)} = ${formatCompactSetting(parsed)}`, "info");
870
+ ctx.ui.notify(`${settingLabel(scope, key)} = ${key === "turnsBeforeCompact" ? formatCompactSetting(parsed) : parsed}`, "info");
818
871
  tui.requestRender();
819
872
  } catch (error) {
820
873
  const message = error instanceof Error ? error.message : String(error);
@@ -841,7 +894,7 @@ async function runSettingsEditor(ctx) {
841
894
  lines.push(makeRow(line, innerW, border));
842
895
  lines.push(makeRow("", innerW, border));
843
896
  const effective = resolveGoosedumpSettings(location);
844
- lines.push(makeRow(dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)}`), innerW, border));
897
+ lines.push(makeRow(dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location)} tokens`), innerW, border));
845
898
  lines.push(makeRow(dim("↑↓ select enter/space edit esc close"), innerW, border));
846
899
  lines.push(`${borderFg("╰")}${borderFg("─".repeat(width - 2))}${borderFg("╯")}`);
847
900
  return lines;
@@ -1692,7 +1745,7 @@ ${lines.join(`
1692
1745
  compactionHookActive = true;
1693
1746
  try {
1694
1747
  const keep = pendingKeep ?? undefined;
1695
- const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, keep, event.signal, nativeExecutablePath);
1748
+ const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd), keep, event.signal, nativeExecutablePath);
1696
1749
  if (compaction && "cancel" in compaction) {
1697
1750
  if (ctx.hasUI)
1698
1751
  ctx.ui.notify(compaction.reason, "info");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "description": "Pi extension for goosedump-backed session-history search, entry expansion, and compaction",
5
5
  "keywords": [
6
6
  "compaction",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@earendil-works/pi-tui": "^0.82.1",
41
- "@jarkkojs/goosedump": "^0.12.6",
41
+ "@jarkkojs/goosedump": "^0.12.8",
42
42
  "@sinclair/typebox": "^0.34.49"
43
43
  },
44
44
  "devDependencies": {