Skip to content

feat(history): bulk & policy-driven history compaction (pm-f3pa/pm-yj9w/pm-0pnz)#288

Merged
unbraind merged 3 commits into
mainfrom
feat/history-compaction-maturity
Jun 18, 2026
Merged

feat(history): bulk & policy-driven history compaction (pm-f3pa/pm-yj9w/pm-0pnz)#288
unbraind merged 3 commits into
mainfrom
feat/history-compaction-maturity

Conversation

@unbraind

Copy link
Copy Markdown
Owner

Summary

Matures pm history-compact from a single-stream command into a full compaction-maintenance surface, and adds a config-driven policy that surfaces oversized streams through pm health. Combines three related storage-epic items (parent pm-o2kc) into one cohesive PR.

Item Title
pm-f3pa History stream bulk-compaction: --all-over N / --ids
pm-yj9w --scope closed/all-streams corpus GC sweep
pm-0pnz Auto-compact policy: config-driven threshold + pm health advisory

What shipped

Bulk mode (pm-f3pa, pm-yj9w)

  • pm history-compact accepts bulk selectors (mutually exclusive with a positional <id>):
    • --ids <a,b,c> — explicit list
    • --all-over <N> — every stream with more than N entries
    • --scope closed|all-streams — lifecycle filter
    • --min-entries <N> (default 3) — skip already-compact streams
  • Pure selection in core/history/history-compact-bulk.ts (selectHistoryCompactBulkTargets); runHistoryCompactBulk orchestrates by reusing the single-item runHistoryCompact.
  • One failing stream never aborts the pass; the command exits non-zero only if any stream errored. --dry-run supported.
  • Result reports totals (streams_considered/selected/items_compacted/items_skipped/items_errored) + one row per stream (compacted / skipped+skip_reason / errored).
  • Orphan streams (history file, no item) are unbucketed and excluded from scope=closed.

Compaction policy (pm-0pnz)

  • New history.compact_policy block { enabled, max_entries(500), trigger(health_warn|auto) } — validator, deterministic key ordering, and nested config keys (history_compact_policy_{enabled,max_entries,trigger}).
  • pm health storage check raises an advisory (non-blocking) warning for streams over max_entries, with a remediation_map (pm history-compact <id>, rewritten to --scope all-streams when >1 stream is over). The threshold scan only reads stream contents when the policy is enabled, so the default path stays a cheap directory listing.
  • When the policy is enabled and --all-over is omitted, bulk mode uses max_entries as the default scan threshold.

Wiring & docs

CLI flags + contracts (HISTORY_COMPACT_FLAG_CONTRACTS, parameter contract, golden full.json), bash/zsh/fish completions, help content, remediation registry, COMMANDS.md + CONFIGURATION.md.

Validation

  • ✅ 100% coverage (statements/branches/functions/lines: 29386/23061/4875/28249)
  • ✅ Static quality gate + docs/skills gate green
  • ✅ 4587 tests pass
  • ✅ Manual end-to-end smoke test in a fresh sandbox (single-id mode preserved, bulk compaction, no history drift after compaction, health backward-compatible when policy disabled, config round-trip)
  • ✅ CHANGELOG regenerated from closed items

🤖 Generated with Claude Code

…9w/pm-0pnz)

Mature `pm history-compact` from a single-stream command into a full
compaction-maintenance surface, and add a config-driven policy that surfaces
oversized streams through `pm health`.

Bulk mode (pm-f3pa, pm-yj9w):
- `pm history-compact` now accepts bulk selectors (mutually exclusive with a
  positional <id>): `--ids <a,b,c>` (explicit list), `--all-over <N>` (entry
  threshold), `--scope closed|all-streams` (lifecycle filter), plus
  `--min-entries <N>` (default 3) to skip already-compact streams.
- Pure selection lives in core/history/history-compact-bulk.ts
  (selectHistoryCompactBulkTargets); runHistoryCompactBulk orchestrates by
  reusing the single-item runHistoryCompact. One failing stream never aborts
  the pass; the command exits non-zero only if any stream errored. --dry-run
  supported. Result reports totals + per-stream rows
  (compacted / skipped+skip_reason / errored).
- Orphan streams (history file, no item) are unbucketed and excluded from
  scope=closed.

Compaction policy (pm-0pnz):
- New `history.compact_policy` settings block { enabled, max_entries(500),
  trigger(health_warn|auto) } with validator, deterministic ordering, and
  nested config keys (history_compact_policy_{enabled,max_entries,trigger}).
- `pm health` storage check raises an advisory (non-blocking) warning for
  streams over max_entries, with a remediation_map (pm history-compact <id>,
  rewritten to `--scope all-streams` when more than one stream is over). The
  threshold scan only reads stream contents when the policy is enabled, so the
  default path stays a cheap directory listing.
- When the policy is enabled and `--all-over` is omitted, bulk mode uses
  max_entries as the default scan threshold.

Wiring: CLI flags + contracts (HISTORY_COMPACT_FLAG_CONTRACTS, parameter
contract, golden full.json), bash/zsh/fish completions, help content, remediation
registry, COMMANDS.md + CONFIGURATION.md. 100% test coverage; static + docs gates
green; CHANGELOG regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgxDnvgTbXNgUPV6ihKgTX

@sourcery-ai sourcery-ai 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.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@unbraind

Copy link
Copy Markdown
Owner Author

@gemini-code-assist please review

@unbraind

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@unbraind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 12 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f6e299ba-ff8f-4c7f-a44c-1b1c2b99c1de

📥 Commits

Reviewing files that changed from the base of the PR and between 2a13074 and 04e9244.

📒 Files selected for processing (33)
  • .agents/pm/features/pm-0pnz.toon
  • .agents/pm/features/pm-f3pa.toon
  • .agents/pm/features/pm-yj9w.toon
  • .agents/pm/history/pm-0pnz.jsonl
  • .agents/pm/history/pm-f3pa.jsonl
  • .agents/pm/history/pm-yj9w.jsonl
  • CHANGELOG.md
  • docs/COMMANDS.md
  • docs/CONFIGURATION.md
  • src/cli/commands/completion.ts
  • src/cli/commands/health.ts
  • src/cli/commands/history-compact.ts
  • src/cli/help-content.ts
  • src/cli/register-mutation.ts
  • src/core/config/nested-settings.ts
  • src/core/diagnostics/remediation.ts
  • src/core/history/history-compact-bulk.ts
  • src/core/shared/constants.ts
  • src/core/store/settings-validator.ts
  • src/core/store/settings.ts
  • src/sdk/cli-contracts.ts
  • src/sdk/cli-contracts/tool-parameter-tables.ts
  • src/types.ts
  • tests/fixtures/contracts/full.json
  • tests/integration/history-compact-command.spec.ts
  • tests/integration/release-readiness-runtime.spec.ts
  • tests/unit/commands/completion-command.spec.ts
  • tests/unit/commands/config-key-aliases-search.spec.ts
  • tests/unit/commands/health-command.spec.ts
  • tests/unit/commands/register-commands.spec.ts
  • tests/unit/core/history/history-compact-bulk.spec.ts
  • tests/unit/core/store/settings-store.spec.ts
  • tests/unit/core/store/settings-validator.spec.ts
📝 Walkthrough

Walkthrough

Adds a config-driven HistoryCompactPolicy type and three history.compact_policy settings (enabled, max_entries, trigger). Extends pm history-compact with bulk-mode flags (--ids, --all-over, --scope, --min-entries) backed by a new pure selection module and runHistoryCompactBulk orchestrator. Updates pm health to emit advisory storage warnings for over-threshold history streams with per-stream and bulk remediation commands.

Changes

Bulk history-compact and compact_policy implementation

Layer / File(s) Summary
HistoryCompactPolicy type and settings stack
src/types.ts, src/core/shared/constants.ts, src/core/store/settings-validator.ts, src/core/store/settings.ts, src/core/config/nested-settings.ts
Defines HistoryCompactPolicy interface and extends PmSettings.history. Adds SETTINGS_DEFAULTS defaults (enabled: false, max_entries: 500, trigger: "health_warn"). Expands ParsedSettings and settingsCheck validator to accept optional compact_policy sub-object. Updates mergeSettings to deep-merge compact_policy and serializeSettings key ordering. Registers three NESTED_SETTING_DESCRIPTORS entries for pm config.
Pure bulk target-selection module
src/core/history/history-compact-bulk.ts
New dependency-light module exporting HistoryCompactScope, HistoryCompactBulkSkipReason, candidate/criteria/row interfaces, and selectHistoryCompactBulkTargets. Implements ids-mode (deduplication, no_stream, already_compact) and scan-mode (descending-entry sort, scope_mismatch, below_threshold) selection paths.
Bulk command types, validator, and runHistoryCompactBulk
src/cli/commands/history-compact.ts
Adds HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES, HistoryCompactBulkCommandOptions/ItemResult/Result interfaces, assertHistoryCompactTarget mutual-exclusion validator, countHistoryStreamEntries helper, and runHistoryCompactBulk which scans history/*.jsonl, classifies lifecycle buckets, applies policy-driven allOver fallback, iterates selected streams via runHistoryCompact with per-stream error isolation, and returns aggregated HistoryCompactBulkResult.
pm health compact_policy advisory integration
src/cli/commands/health.ts, src/core/diagnostics/remediation.ts
Classifies history_stream_over_compact_threshold:* warnings as advisory-only. Expands countHistoryStreams to read per-stream entry counts when policy is enabled and compute over_threshold IDs. Updates runHealth storage check to emit warn status, include compact_policy metadata in details, map per-stream remediation commands, and override to pm history-compact --scope all-streams when multiple streams are over threshold. Adds remediation registry entry.
CLI registration, SDK contracts, completion, and help
src/cli/register-mutation.ts, src/sdk/cli-contracts.ts, src/cli/commands/completion.ts, src/cli/help-content.ts
Reworks pm history-compact command registration to accept optional [id] plus --ids, --all-over, --scope, --min-entries with validation and conditional dispatch to single vs bulk path. Imports splitCommaList and renames the local schema helper to avoid conflict. Extends HISTORY_COMPACT_FLAG_CONTRACTS and updates MCP action oneOfRequired groups. Updates bash/zsh/fish completion entries and help examples.
Unit and integration tests
tests/unit/core/history/history-compact-bulk.spec.ts, tests/integration/history-compact-command.spec.ts, tests/unit/commands/health-command.spec.ts, tests/unit/commands/register-commands.spec.ts, tests/unit/core/store/settings-*.spec.ts, tests/fixtures/contracts/full.json, tests/integration/release-readiness-runtime.spec.ts, tests/unit/commands/...
Adds unit tests for selectHistoryCompactBulkTargets ids/scan modes, settings-validator compact_policy acceptance and rejection, settings serialization key ordering, config-key alias count, completion flag assertions, health advisory single/multi-stream scenarios, and register-commands bulk routing/validation. Adds integration tests for assertHistoryCompactTarget and runHistoryCompactBulk across all selector modes. Updates contracts fixture with oneOf mutual-exclusion schema.
Docs, changelog, and agent metadata
CHANGELOG.md, docs/COMMANDS.md, docs/CONFIGURATION.md, .agents/pm/features/*, .agents/pm/history/*
Adds three CHANGELOG entries. Documents bulk-mode usage and history.compact_policy settings. Closes three agent feature specs (pm-0pnz, pm-f3pa, pm-yj9w) and appends corresponding JSONL history log closure entries.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as register-mutation
  participant Validator as assertHistoryCompactTarget
  participant Bulk as runHistoryCompactBulk
  participant FS as history JSONL files
  participant Selector as selectHistoryCompactBulkTargets
  participant Compact as runHistoryCompact

  User->>CLI: pm history-compact --all-over 50 --scope closed --min-entries 3
  CLI->>Validator: assertHistoryCompactTarget(id=undefined, {allOver, scope})
  Validator-->>CLI: ok
  CLI->>Bulk: HistoryCompactBulkCommandOptions + GlobalOptions
  Bulk->>FS: list streams, count entries, apply read hooks
  Bulk->>Selector: candidates + criteria (allOver, scope, minEntries)
  Selector-->>Bulk: HistoryCompactBulkSelectionRow array
  loop each selected stream
    Bulk->>Compact: dryRun, author, message, force
    Compact-->>Bulk: result or caught per-stream error
  end
  Bulk-->>CLI: HistoryCompactBulkResult with totals and items
  CLI->>User: print result table, non-zero exit when items_errored is positive
Loading
sequenceDiagram
  participant User
  participant runHealth
  participant countHistoryStreams
  participant FS as history JSONL files
  participant RemediationRegistry

  User->>runHealth: pm health
  runHealth->>countHistoryStreams: pmRoot + settings.history.compact_policy
  countHistoryStreams->>FS: read each stream entry count when policy enabled
  countHistoryStreams-->>runHealth: count, over_threshold list, warnings, max_entries
  runHealth->>runHealth: set storage status to warn when over_threshold non-empty
  runHealth->>RemediationRegistry: look up history_stream_over_compact_threshold per id
  RemediationRegistry-->>runHealth: pm history-compact id
  runHealth->>runHealth: override to --scope all-streams when multiple streams over threshold
  runHealth-->>User: health report with advisory warn, overall status stays ok
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • unbraind/pm-cli#122: Modifies the same pm history-compact command implementation, CLI registration, SDK contracts, and help surfaces that this PR extends with bulk-mode support.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding bulk and policy-driven history compaction to the pm history-compact command.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context on the three items being combined and the functionality shipped.
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.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a config-driven auto-compact policy and bulk history compaction capabilities for the pm CLI tool, allowing operators to sweep and compact multiple history streams. Key additions include the history.compact_policy settings block, advisory warnings in pm health for over-threshold streams, and new bulk selectors (--ids, --all-over, and --scope) for pm history-compact. Feedback on the changes highlights opportunities to optimize memory usage in src/cli/commands/health.ts and src/cli/commands/history-compact.ts by replacing line-splitting string allocations with character-by-character scanning when counting history stream entries.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +252 to +258
const raw = await fs.readFile(streamPath, "utf8");
let entries = 0;
for (const line of raw.split(/\r?\n/)) {
if (line.trim().length > 0) {
entries += 1;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Reading the entire history stream file and splitting it by line (raw.split(/\r?\n/)) creates a large array of strings in memory. For large history files (e.g., >19MB), this causes significant memory allocation overhead and garbage collection thrashing during health checks. Since we only need to count non-empty lines, we can scan the string character-by-character in $O(N)$ time and $O(1)$ extra space, completely avoiding array allocations.

Suggested change
const raw = await fs.readFile(streamPath, "utf8");
let entries = 0;
for (const line of raw.split(/\r?\n/)) {
if (line.trim().length > 0) {
entries += 1;
}
}
const raw = await fs.readFile(streamPath, "utf8");
let entries = 0;
let hasChars = false;
for (let i = 0; i < raw.length; i++) {
const code = raw.charCodeAt(i);
if (code === 10) {
if (hasChars) {
entries += 1;
hasChars = false;
}
} else if (code !== 13 && code !== 32 && code !== 9) {
hasChars = true;
}
}
if (hasChars) {
entries += 1;
}

Comment on lines +494 to +502
function countHistoryStreamEntries(raw: string): number {
let count = 0;
for (const line of raw.split(/\r?\n/)) {
if (line.trim().length > 0) {
count += 1;
}
}
return count;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Splitting the entire history stream file by line (raw.split(/\r?\n/)) to count entries allocates a large array of strings. For oversized history streams, this introduces unnecessary memory overhead and CPU cycles. We can optimize this to scan the string character-by-character in $O(N)$ time and $O(1)$ extra space, avoiding any string array allocations.

function countHistoryStreamEntries(raw: string): number {
  let count = 0;
  let hasChars = false;
  for (let i = 0; i < raw.length; i++) {
    const code = raw.charCodeAt(i);
    if (code === 10) {
      if (hasChars) {
        count += 1;
        hasChars = false;
      }
    } else if (code !== 13 && code !== 32 && code !== 9) {
      hasChars = true;
    }
  }
  if (hasChars) {
    count += 1;
  }
  return count;
}

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a config-driven history compaction policy and a bulk compaction mode for pm history-compact, allowing operators to automatically flag and sweep oversized history streams based on entry count thresholds or lifecycle status. It also integrates these checks into pm health as advisory storage warnings. The review feedback highlights a potential issue in src/cli/commands/health.ts where looping over and overwriting all keys in remediationMap could accidentally overwrite other unrelated storage remediations, and suggests targeting only the specific history_stream_over_compact_threshold key instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1801 to +1805
if (check.name === "storage" && historySummary.over_threshold.length > 1) {
for (const code of Object.keys(remediationMap)) {
remediationMap[code] = "pm history-compact --scope all-streams";
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Looping over all keys of remediationMap and overwriting them with the bulk compaction command is risky. If other warnings are present in the storage check (or added in the future), their remediations will be incorrectly overwritten.

It is safer and more precise to target only the specific warning code history_stream_over_compact_threshold.

Suggested change
if (check.name === "storage" && historySummary.over_threshold.length > 1) {
for (const code of Object.keys(remediationMap)) {
remediationMap[code] = "pm history-compact --scope all-streams";
}
}
if (check.name === "storage" && historySummary.over_threshold.length > 1) {
if (remediationMap["history_stream_over_compact_threshold"] !== undefined) {
remediationMap["history_stream_over_compact_threshold"] = "pm history-compact --scope all-streams";
}
}

@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.

Actionable comments posted: 8

🤖 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.

Inline comments:
In `@docs/COMMANDS.md`:
- Line 594: The documentation for the history-compact command currently presents
the `--ids`, `--all-over`, and `--scope` options in a way that suggests they can
be combined (using "and/or"), but the CLI actually rejects mixing `--ids` with
the scan selectors. Clarify in the COMMANDS.md file that `--ids` is mutually
exclusive with `--all-over` and `--scope`, and that users can only select
streams using one of these three modes at a time. Restructure the wording to
make it explicit that these are three separate, mutually exclusive selection
approaches rather than options that can be mixed together.

In `@docs/CONFIGURATION.md`:
- Line 66: The documentation for `history.compact_policy.max_entries` currently
describes its behavior without clarifying that it only applies as the default
`--all-over` threshold when `history.compact_policy.enabled` is true. Revise the
configuration documentation entry for `history.compact_policy.max_entries` to
explicitly state this conditional dependency, making it clear that the default
threshold behavior is only active when the compact policy is enabled.

In `@src/cli/commands/history-compact.ts`:
- Around line 538-544: The operations on lines 540 and 541 (fs.readFile and
runActiveOnReadHooks) can throw exceptions that will terminate the entire
history-compact command instead of isolating the error to just that stream. Wrap
these operations in a try-catch block within the for loop that iterates over
historyFiles, and when an exception is caught, handle it gracefully by either
skipping that entry or adding it to candidates with error metadata, then
continue the loop to process remaining files instead of letting the exception
propagate.

In `@src/cli/register-mutation.ts`:
- Around line 1336-1340: The --before option is documented as single-id mode
only, but when id is omitted (bulk mode), the option is silently ignored rather
than throwing an error. This can lead to unintended data compaction. Add
validation logic near where the id parameter is checked (around line 1374) to
detect when --before is provided without an id and reject the operation with an
appropriate error message. Reference the option names like --before, --ids,
--all-over, and --scope in your validation to ensure the --before option
conflicts with bulk mode operations.
- Around line 1355-1358: The current implementation uses Number.parseInt for
parsing the allOver and minEntries options, which silently truncates invalid
input at the first non-numeric character (e.g., "10abc" becomes 10, "3.5"
becomes 3). Replace the Number.parseInt calls for both the allOver variable
assignment and the minEntries variable assignment with a strict integer parsing
approach that validates the entire input string is numeric. This should reject
partial numeric strings like "10abc" and decimal strings like "3.5" as invalid
rather than silently truncating them to pass validation.

In `@src/sdk/cli-contracts.ts`:
- Around line 1950-1954: The oneOfRequired constraint in the history-compact CLI
contract at line 1954 enforces that exactly one of id, ids, allOver, or scope
must be present, but the runtime assertHistoryCompactTarget allows allOver and
scope to be used together in scan mode, causing a schema-runtime mismatch.
Update the oneOfRequired array to include a valid combination option that allows
both allOver and scope together, in addition to the existing single-option
combinations, so the MCP schema validation matches the actual runtime behavior
for history-compact operations.

In `@tests/fixtures/contracts/full.json`:
- Around line 18012-18023: The scope enum values in the fixture are incorrect
for the history-compact command. Change the enum array from ["project",
"global"] to ["closed", "all-streams"] to match the PR objectives for
lifecycle-based filtering. Update the description to reflect that the scope
parameter filters by lifecycle state (closed or all-streams) rather than project
or global state. Update the examples array to use the correct valid values
("closed" and "all-streams") instead of the current project and global examples.

In `@tests/integration/history-compact-command.spec.ts`:
- Around line 730-733: The test "requires an initialized tracker" uses a
hardcoded path `/tmp/pm-compact-bulk-missing-root` that may already exist on
shared runners, causing nondeterministic test failures. Replace the hardcoded
path in the runHistoryCompactBulk call with a dynamically generated unique
temporary path using a method like generating a UUID or timestamp suffix,
ensuring a fresh path is used for each test execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: e0d695bb-06ca-4387-bdec-60a0fb68399f

📥 Commits

Reviewing files that changed from the base of the PR and between 2a13074 and bd0b38d.

📒 Files selected for processing (32)
  • .agents/pm/features/pm-0pnz.toon
  • .agents/pm/features/pm-f3pa.toon
  • .agents/pm/features/pm-yj9w.toon
  • .agents/pm/history/pm-0pnz.jsonl
  • .agents/pm/history/pm-f3pa.jsonl
  • .agents/pm/history/pm-yj9w.jsonl
  • CHANGELOG.md
  • docs/COMMANDS.md
  • docs/CONFIGURATION.md
  • src/cli/commands/completion.ts
  • src/cli/commands/health.ts
  • src/cli/commands/history-compact.ts
  • src/cli/help-content.ts
  • src/cli/register-mutation.ts
  • src/core/config/nested-settings.ts
  • src/core/diagnostics/remediation.ts
  • src/core/history/history-compact-bulk.ts
  • src/core/shared/constants.ts
  • src/core/store/settings-validator.ts
  • src/core/store/settings.ts
  • src/sdk/cli-contracts.ts
  • src/types.ts
  • tests/fixtures/contracts/full.json
  • tests/integration/history-compact-command.spec.ts
  • tests/integration/release-readiness-runtime.spec.ts
  • tests/unit/commands/completion-command.spec.ts
  • tests/unit/commands/config-key-aliases-search.spec.ts
  • tests/unit/commands/health-command.spec.ts
  • tests/unit/commands/register-commands.spec.ts
  • tests/unit/core/history/history-compact-bulk.spec.ts
  • tests/unit/core/store/settings-store.spec.ts
  • tests/unit/core/store/settings-validator.spec.ts

Comment thread docs/COMMANDS.md Outdated
Comment thread docs/CONFIGURATION.md Outdated
Comment thread src/cli/commands/history-compact.ts
Comment thread src/cli/register-mutation.ts
Comment thread src/cli/register-mutation.ts Outdated
Comment thread src/sdk/cli-contracts.ts Outdated
Comment thread tests/fixtures/contracts/full.json Outdated
Comment thread tests/integration/history-compact-command.spec.ts Outdated
- Replace `--scope closed|all-streams` with `--closed` / `--all-streams`
  booleans. The MCP `pm` tool exposes a single shared `scope` param
  (enum project|global), so a history-compact `--scope` would advertise the
  wrong enum and reject closed|all-streams. Booleans are collision-free and
  mirror `history-repair --all`. New `closed`/`allStreams` (boolean) +
  `allOver`/`minEntries` (number) params are declared in the MCP property table
  so the generated schema is correctly typed (previously `allOver`/`minEntries`
  fell back to string).
- Reject `--before` in bulk mode instead of silently ignoring it (it only
  applies to single-id compaction).
- Strict non-negative-integer parsing for `--all-over`/`--min-entries`:
  reject "10abc"/"3.5"/"-3" that Number.parseInt would silently truncate.
- Drop `oneOfRequired` from the history-compact parameter contract: scan
  selectors (`allOver` + `closed`/`allStreams`) legitimately combine, which a
  one-of rule rejects. Mode/exclusivity is enforced at runtime by
  assertHistoryCompactTarget (CLI + MCP).
- Use a deterministic guaranteed-absent path in the not-initialized test
  instead of a hardcoded /tmp path.

100% coverage maintained; static + docs gates green; contract golden regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgxDnvgTbXNgUPV6ihKgTX
@unbraind

Copy link
Copy Markdown
Owner Author

Thanks @coderabbitai — all five findings addressed in 3a1dde6:

  1. --before silently dropped in bulk mode → now rejected with a usage error when any bulk selector is present.
  2. Lax Number.parseInt truncation (10abc→10, 3.5→3) → --all-over/--min-entries now strictly require ^\d+$ and reject otherwise.
  3. oneOfRequired rejected valid scan combos → dropped from the parameter contract; mode/exclusivity is enforced at runtime by assertHistoryCompactTarget (shared by CLI + MCP), which correctly allows --all-over + scope together.
  4. Wrong scope enum in the MCP schema → replaced --scope closed|all-streams with --closed / --all-streams booleans (the shared scope param is firmly project|global). Added closed/allStreams (boolean) and allOver/minEntries (number) to the MCP property table so the generated schema is correctly typed.
  5. Hardcoded /tmp missing-root path → now a guaranteed-absent subpath of a fresh temp root.

100% coverage maintained, static + docs gates green, contract golden regenerated.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

@unbraind Thanks for the detailed fix summary! Great to see all five findings addressed cleanly in 3a1dde6 — the strict ^\d+$ regex for integer parsing and the runtime-only exclusivity enforcement via assertHistoryCompactTarget are particularly solid approaches. Kicking off the full review now.

✅ Action performed

Full review finished.

#288)

Address remaining CodeRabbit findings:
- Bulk enumeration now wraps each stream read in try/catch: an unreadable
  stream (e.g. EISDIR) is isolated as an `errored` result row instead of
  aborting the whole pass, mirroring the per-stream compaction error handling.
  streams_considered + items_errored account for pre-selection failures.
- COMMANDS.md: clarify that --ids is used on its own (not combined with the
  scan selectors) and that --closed/--all-streams are mutually exclusive.
- CONFIGURATION.md: note that max_entries/trigger are inert unless the policy
  is enabled.

100% coverage maintained; static + docs gates green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgxDnvgTbXNgUPV6ihKgTX
@unbraind

Copy link
Copy Markdown
Owner Author

Addressed the remaining findings in 04e9244:

  • Per-stream scan failures (history-compact.ts) → bulk enumeration now wraps each stream read in try/catch; an unreadable stream is isolated as an errored row (counted in items_errored/streams_considered) instead of aborting the pass. Added an EISDIR regression test.
  • COMMANDS.md → clarified --ids is standalone (not combined with scan selectors) and --closed/--all-streams are mutually exclusive.
  • CONFIGURATION.md → noted max_entries/trigger are inert unless the policy is enabled.

All 8 findings now resolved. 100% coverage, static + docs gates green.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

@unbraind Thanks for the thorough follow-up! The per-stream error isolation approach — wrapping individual stream reads in try/catch and surfacing unreadable streams as errored rows rather than aborting the pass — is a robust pattern, and the EISDIR regression test is a great addition. Kicking off the full review now.

✅ Action performed

Full review finished.

@unbraind unbraind merged commit eab136c into main Jun 18, 2026
15 checks passed
@unbraind unbraind deleted the feat/history-compaction-maturity branch June 18, 2026 18:48
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