Skip to content

fix(cli): keep squad nap from trimming history into an uncommittable archive - #2350

Merged
tamirdresher merged 2 commits into
github:devfrom
joslat:joslat/2349-nap-history-archive-guard
Oct 28, 2026
Merged

tamirdresher merged 2 commits into
github:devfrom
joslat:joslat/2349-nap-history-archive-guard

Conversation

@joslat

@joslat joslat commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

What

squad nap history compression now follows the same safety rules as decision archival. It won't trim a history.md that git will commit into a history-archive.md that git can't commit, and it rewrites history.md only after checking that the archive gained every archived section.

Why

Closes #2349

With .squad/ excluded from git (the #1783 setup), nap appended the older sections to a new, ignored history-archive.md and trimmed the tracked history.md. The trim got committed and the archive didn't: in the repro, 5 of 12 sections were left after a normal git add -u commit, while nap reported success. Once #2322 lands, squad nap is the only thing that compacts history, so this matters more (Tamir raised it on #2322).

How

All in packages/squad-cli/src/cli/core/nap.ts, in compressHistory. The comments refer to archiveDecisions so the two stay together.

  • Rule 1. Refuse when history.md would be committed but its archive couldn't be: isCommittableDestination(history) && !isCommittableDestination(archive). The action says History compression refused: … and nothing is written.
    • One difference from decisions: decisions check only the destination. If .squad/ is ignored as a whole and was never committed, history.md is ignored too and there's nothing to lose. A destination-only check would refuse every compression in that setup (there's a test for it). I left decision archival as it is.
  • Rule 2. After the append, count the archive's ## headings and compare them with the count before plus the archived sections. On a mismatch, abort and leave history.md untouched.
    • The count uses the same /^##\s/ match that splits the sections, so a ## line inside a fence counts the same way on both sides.
    • Nothing to archive means nothing to trim.
  • runNap and runNapSync pass the repo root the same way archiveDecisions derives it (path.dirname(squadDir)).

Dry runs are unchanged, matching decisions: the checks run only when files are written.

Testing

New tests in test/cli/nap-archival-safety.test.ts, with real git repos, counting sections in the committed tree:

  • with .squad/ excluded and no archive yet, no section is lost across a commit;
  • the refusal is reported, history.md is unchanged, no archive is created, and git status is clean;
  • with the archive already tracked, it still compresses (5 kept, 7 archived, all committed);
  • with .squad/ ignored as a whole and never committed, it still compresses;
  • with .squad/ not excluded, it compresses normally.

Results:

  • With the nap.ts change reverted, the first two fail. The others pass both ways, because they guard normal compression.
  • With a destination-only Rule 1, the "ignored as a whole" test fails.
  • nap-archival-safety, nap, cli/doctor and state/archival: 145 passed.
  • npm run lint: clean. ESLint on the changed files: 0 errors.

⚠️ Quick Check

  • If SDK/CLI source files changed: changeset added (.changeset/nap-history-archive-guard.md, cli patch)

PR Readiness Checklist

Branch & Commit

  • Branch created from dev (not main)
  • Branch is up to date with dev (c3f54c2)
  • Verified diff contains only intended changes (3 files)
  • PR is not in draft mode
  • Commit history is clean

Build & Test

  • npm run lint passes
  • Relevant tests pass
  • No file overlap with any open PR

Docs


Breaking Changes

None. Nap now refuses a compression that would have lost sections at the next commit, and reports it.

Waivers

None.

…archive

History compression appended the older sections of history.md to
history-archive.md and rewrote history.md without the checks decision
archival has. With .squad/ excluded from git, the new archive is ignored,
so the trim committed and the archived sections never did.

compressHistory now follows the same rules as archiveDecisions:

- Rule 1: refuse when history.md would be committed but its archive could
  not be (isCommittableDestination). A .squad/ ignored as a whole still
  compresses, since nothing there reaches a commit;
- Rule 2: rewrite history.md only after the archive gained every archived
  section, counted the same way the sections were split; nothing to
  archive means nothing to trim.

Closes github#2349
@github-actions

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #2350

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 1
Files modified 2
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/nap-history-archive-guard.md
squad-cli (1 file)
  • packages/squad-cli/src/cli/core/nap.ts
tests (1 file)
  • test/cli/nap-archival-safety.test.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Architectural Review

Authoritative evidence: base-controlled pull_request_target reporter.

⚠️ Architectural review: 1 warning(s).

Severity Category Finding Files
🟡 warning bootstrap-area 1 file(s) in the bootstrap area (packages/squad-cli/src/cli/core/) were modified. These files must maintain zero external dependencies. Review carefully. packages/squad-cli/src/cli/core/nap.ts

Automated architectural review — informational only.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 358dbc2

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 3 item(s) to address before review

Status Check Details
❌ Single commit 2 commits — consider squashing before review
✅ Not in draft Ready for review
✅ Branch up to date Up to date with dev
❌ Copilot review No Copilot review yet — it may still be processing
✅ Changeset present Changeset file found
✅ Scope clean No .squad/ or docs/proposals/ files
✅ No merge conflicts No merge conflicts
✅ Copilot threads resolved No Copilot review threads
❌ CI passing 15 check(s) still running

Files Changed (3 files, +171 −6)

File +/−
.changeset/nap-history-archive-guard.md +5 −0
packages/squad-cli/src/cli/core/nap.ts +52 −6
test/cli/nap-archival-safety.test.ts +114 −0

Total: +171 −6


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@joslat

joslat commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

About the bootstrap-area warning: this PR doesn't change any imports in nap.ts. It already imported isCommittableDestination from the SDK for decision archival, and history compression now uses the same function. nap.ts also isn't one of the protected bootstrap files in scripts/check-bootstrap-deps.mjs. That check passes here, and I ran it locally on the PR commit too ("all protected files use only node:* imports").

@tamirdresher
tamirdresher merged commit c5e2c83 into github:dev Oct 28, 2026
22 of 23 checks passed
@joslat joslat mentioned this pull request Nov 10, 2026
12 of 13 tasks
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.

squad nap can trim a tracked history.md into a history-archive.md that git never commits

2 participants