Skip to content

feat(context): add compaction preservation hooks and queue#91

Merged
omarluq merged 5 commits into
mainfrom
feat/compaction-preservation-hooks-queue
Jun 7, 2026
Merged

feat(context): add compaction preservation hooks and queue#91
omarluq merged 5 commits into
mainfrom
feat/compaction-preservation-hooks-queue

Conversation

@omarluq

@omarluq omarluq commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Adds file operation preservation for compaction summaries, extension lifecycle hooks around compaction, and prompt queueing while manual compaction is running.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37ac646d-2e53-44ca-81fe-5b70c78c17bc

📥 Commits

Reviewing files that changed from the base of the PR and between 902baf3 and 360a688.

📒 Files selected for processing (6)
  • internal/assistant/context_compaction_lifecycle.go
  • internal/assistant/context_compaction_lifecycle_internal_test.go
  • internal/assistant/context_file_operations.go
  • internal/assistant/context_file_operations_internal_test.go
  • internal/extension/lifecycle_test.go
  • internal/terminal/prompt_send_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/assistant/context_file_operations.go
  • internal/assistant/context_compaction_lifecycle.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Extensions can hook into context compaction (before/after) to provide/override summaries, details, or cancel compaction.
    • Compaction now collects and preserves file-operation info and appends it to compaction summaries.
    • Terminal tracks queued compaction start position and resumes processing queued prompts after compaction completes.
  • Bug Fixes

    • Compaction failures now restore queued prompts and restore composer state correctly.
  • Tests

    • Added comprehensive tests for compaction lifecycle, file-operation collection, and terminal queue behavior.

Walkthrough

This PR wires extension lifecycle hooks into session compaction, collects and preserves file-operation records into compaction metadata and summaries, and updates terminal logic to snapshot, resume, and restore queued prompts during compaction.

Changes

Compaction Lifecycle & File Operations System

Layer / File(s) Summary
Extension Lifecycle Compaction Contract
internal/extension/lifecycle.go
Introduces CompactionMutation and session_before_compact/session_compact event names; parses and merges compaction mutations from Lua handlers into dispatch results.
Core compaction flow & decision orchestration
internal/assistant/context_compaction.go, internal/assistant/lifecycle.go, internal/assistant/context_compaction_lifecycle.go
CompactSessionFrom now attaches collected file operations to the compaction plan, routes through a decision flow (compactionSummaryDecision) that runs before-hook handlers and resolves summary/decision/cancel, updates plan first-kept index from decisions, and appends compaction entries with merged metadata.
File operations detection & formatting
internal/assistant/context_file_operations.go
Collects and deduplicates file operations from prior compaction details and tool-result entries (path args, patterns, bash command tokenization), classifies read vs modified, truncates command previews, and rewrites/appends a bounded "File operations preserved during compaction:" summary block.
Lifecycle dispatch payloads & diagnostics
internal/assistant/context_compaction_lifecycle.go
Builds before/after payloads including token counts, IDs, split-turn summary, and serialized file operations; dispatches lifecycle hooks, emits phase diagnostics, and validates/normalizes extension compaction mutations into internal decisions.
Compaction tests and runtime integration
internal/assistant/context_compaction_test.go, internal/assistant/context_compaction_lifecycle_test.go, internal/assistant/context_compaction_lifecycle_internal_test.go
Adds/updates tests asserting hook-provided summaries and cancellation, verifies post-compact events are published, and validates file-operations preservation in compaction summary and entry metadata. Includes runtime test helper setup.

Terminal Queue Management & Compaction Integration

Layer / File(s) Summary
Compaction state & async handling
internal/terminal/app.go, internal/terminal/async_events.go, internal/terminal/compact_commands.go
activeCompactionState gains QueuedStart; compact async-event handlers accept context; applyCompactDone resumes queued prompts (via processQueuedPrompt(ctx)); applyCompactError snapshots and restores queued prompts around failure handling.
Prompt queue extraction & restoration
internal/terminal/prompt_queue.go, internal/terminal/prompt_send.go
Add queuedCompactionPrompts to extract queued messages from QueuedStart onward, and restoreCompactionQueuedPrompts to re-append and resume processing. sendPrompt now uses app.busy() for gating.
Prompt submission & deferment during compaction
internal/terminal/prompt_submit.go
Submissions while compacting: slash-prefixed prompts are deferred (composer restored, status set to waiting) while other prompts are queued with history recorded and status updated to indicate a queued prompt.
Terminal tests for queued prompt recovery
internal/terminal/compact_commands_test.go, internal/terminal/async_events_test.go, internal/terminal/prompt_send_test.go
Introduce compactTestFailed constant; update tests to initialize activeCompaction.QueuedStart and call async handlers with context; add tests verifying queued prompts are sent on success and restored on failure.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • omarluq/librecode#82: Related terminal compaction flow changes and manual /compact handling this PR builds upon.
  • omarluq/librecode#45: Prior lifecycle mutation handling changes that intersect with the new compaction mutation parsing and dispatch logic.

"🐰 I hopped through code and spun a thread,
Hooks tuned the compaction song in my head,
Files listed, queued prompts kept neat,
Extensions chimed in with decisions sweet,
Now contexts sleep tidy, and prompts greet!"

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the primary changes: adding compaction preservation hooks, lifecycle integration, and prompt queueing functionality.
Description check ✅ Passed The description is directly related to the changeset, covering the three main features: file operation preservation, extension lifecycle hooks, and prompt queueing during compaction.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/compaction-preservation-hooks-queue

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.59459% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.89%. Comparing base (d66f82f) to head (360a688).

Files with missing lines Patch % Lines
internal/assistant/context_file_operations.go 92.94% 6 Missing and 6 partials ⚠️
internal/assistant/context_compaction.go 91.83% 2 Missing and 2 partials ⚠️
internal/assistant/context_compaction_lifecycle.go 95.95% 2 Missing and 2 partials ⚠️
internal/extension/lifecycle.go 94.73% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #91      +/-   ##
==========================================
+ Coverage   69.40%   69.89%   +0.49%     
==========================================
  Files         217      219       +2     
  Lines       19236    19607     +371     
==========================================
+ Hits        13350    13705     +355     
- Misses       4729     4737       +8     
- Partials     1157     1165       +8     
Flag Coverage Δ
unittests 69.89% <94.59%> (+0.49%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
internal/assistant/context_compaction_lifecycle.go (1)

181-198: 💤 Low value

Consider using slices.Contains from the standard library.

The slicesContainsString helper reimplements slices.Contains which is available in Go 1.21+. The slices package is already imported in context_file_operations.go in this PR.

♻️ Optional: Use stdlib slices.Contains
+import "slices"
+
-func slicesContainsString(values []string, target string) bool {
-	for _, value := range values {
-		if value == target {
-			return true
-		}
-	}
-
-	return false
-}

Then replace slicesContainsString(plan.KeptEntryIDs, decision.FirstKeptEntryID) with slices.Contains(plan.KeptEntryIDs, decision.FirstKeptEntryID).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/assistant/context_compaction_lifecycle.go` around lines 181 - 198,
Replace the custom helper slicesContainsString with the standard library
function: remove the slicesContainsString function and update its call sites
(e.g., replace slicesContainsString(plan.KeptEntryIDs,
decision.FirstKeptEntryID) with slices.Contains(plan.KeptEntryIDs,
decision.FirstKeptEntryID)); ensure the file imports golang.org's stdlib slices
package if not already imported in this file.
internal/assistant/context_file_operations.go (1)

180-193: 💤 Low value

Substring matching may cause false positives for mutating command detection.

Commands like echo "remove this" would match "rm" substring, and echo "this sed -i example" would match "sed -i". Consider using word boundary matching or checking only the first token (the actual command).

♻️ Optional: Use word-boundary or first-token matching
 func shellCommandLooksMutating(command string) bool {
 	lower := strings.ToLower(command)
 	if strings.Contains(lower, ">") {
 		return true
 	}
-	mutatingCommands := []string{"cp", "mv", "rm", "mkdir", "touch", "tee", "sed -i", "perl -i"}
-	for _, candidate := range mutatingCommands {
-		if strings.Contains(lower, candidate) {
+	fields := strings.Fields(lower)
+	if len(fields) == 0 {
+		return false
+	}
+	firstCmd := fields[0]
+	singleTokenMutating := []string{"cp", "mv", "rm", "mkdir", "touch", "tee"}
+	for _, candidate := range singleTokenMutating {
+		if firstCmd == candidate {
 			return true
 		}
 	}
+	// Check for sed/perl with -i flag anywhere in command
+	if (firstCmd == "sed" || firstCmd == "perl") && strings.Contains(lower, " -i") {
+		return true
+	}
 
 	return false
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/assistant/context_file_operations.go` around lines 180 - 193, The
current shellCommandLooksMutating function falsely flags commands by substring
matching (e.g., "echo remove" matches "rm"); update it to only consider the
actual command token or use word-boundary matching: split the input into tokens
(e.g., by whitespace), inspect the first token (command) and any explicit
redirection characters, and compare that token against the mutatingCommands list
(or use regex with \b boundaries) instead of using strings.Contains on the whole
lowercased string; ensure checks for redirection (">", ">>") still behave
correctly and preserve the existing function name shellCommandLooksMutating and
the mutatingCommands set.
internal/terminal/prompt_send_test.go (1)

145-154: ⚡ Quick win

Consider adding test coverage for command deferral during compaction.

The test verifies that regular prompts are queued during compaction, but there's no test case for the scenario where a command (prompt starting with /) is submitted during compaction. Based on the implementation in prompt_submit.go lines 22-26, commands should restore the composer text and defer until compaction finishes, rather than being queued.

🧪 Suggested test case

Add a test case to submitCases() like:

{
	setupApp:          func(app *App) { app.compacting = true },
	composerText:      "/model",
	wantComposerText:  "/model",
	wantQueued:        nil,
	name:              "defers command while compacting",
	wantMode:          modeChat,
	wantPromptHistory: 0,
	wantConsumed:      false,
	wantRequest:       false,
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/prompt_send_test.go` around lines 145 - 154, Add a test
case to submitCases() that sets app.compacting = true via setupApp, supplies
composerText starting with "/" (e.g. "/model"), and asserts the composer text is
preserved (wantComposerText "/model"), no queued prompts (wantQueued nil), no
prompt history increment (wantPromptHistory 0), and both wantConsumed and
wantRequest are false; this verifies the command-deferral behavior implemented
in prompt_submit.go (lines ~22-26) where commands are restored and deferred
during compaction.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/assistant/context_compaction_lifecycle.go`:
- Around line 181-198: Replace the custom helper slicesContainsString with the
standard library function: remove the slicesContainsString function and update
its call sites (e.g., replace slicesContainsString(plan.KeptEntryIDs,
decision.FirstKeptEntryID) with slices.Contains(plan.KeptEntryIDs,
decision.FirstKeptEntryID)); ensure the file imports golang.org's stdlib slices
package if not already imported in this file.

In `@internal/assistant/context_file_operations.go`:
- Around line 180-193: The current shellCommandLooksMutating function falsely
flags commands by substring matching (e.g., "echo remove" matches "rm"); update
it to only consider the actual command token or use word-boundary matching:
split the input into tokens (e.g., by whitespace), inspect the first token
(command) and any explicit redirection characters, and compare that token
against the mutatingCommands list (or use regex with \b boundaries) instead of
using strings.Contains on the whole lowercased string; ensure checks for
redirection (">", ">>") still behave correctly and preserve the existing
function name shellCommandLooksMutating and the mutatingCommands set.

In `@internal/terminal/prompt_send_test.go`:
- Around line 145-154: Add a test case to submitCases() that sets app.compacting
= true via setupApp, supplies composerText starting with "/" (e.g. "/model"),
and asserts the composer text is preserved (wantComposerText "/model"), no
queued prompts (wantQueued nil), no prompt history increment (wantPromptHistory
0), and both wantConsumed and wantRequest are false; this verifies the
command-deferral behavior implemented in prompt_submit.go (lines ~22-26) where
commands are restored and deferred during compaction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4ddfed5-7e1d-4944-bf43-b3d8123b32e3

📥 Commits

Reviewing files that changed from the base of the PR and between d66f82f and 902baf3.

📒 Files selected for processing (16)
  • internal/assistant/context_compaction.go
  • internal/assistant/context_compaction_lifecycle.go
  • internal/assistant/context_compaction_lifecycle_test.go
  • internal/assistant/context_compaction_test.go
  • internal/assistant/context_file_operations.go
  • internal/assistant/lifecycle.go
  • internal/extension/lifecycle.go
  • internal/terminal/app.go
  • internal/terminal/async_events.go
  • internal/terminal/async_events_test.go
  • internal/terminal/compact_commands.go
  • internal/terminal/compact_commands_test.go
  • internal/terminal/prompt_queue.go
  • internal/terminal/prompt_send.go
  • internal/terminal/prompt_send_test.go
  • internal/terminal/prompt_submit.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 7, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jun 7, 2026

Copy link
Copy Markdown

@omarluq omarluq merged commit 380ccde into main Jun 7, 2026
15 checks passed
@omarluq omarluq deleted the feat/compaction-preservation-hooks-queue branch June 7, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant