Skip to content

feat(cli): add reversible full Trellis ablation - #538

Open
lifan-builds wants to merge 6 commits into
mindfold-ai:mainfrom
lifan-builds:codex/full-trellis-ablation
Open

feat(cli): add reversible full Trellis ablation#538
lifan-builds wants to merge 6 commits into
mindfold-ai:mainfrom
lifan-builds:codex/full-trellis-ablation

Conversation

@lifan-builds

@lifan-builds lifan-builds commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add full-only trellis ablate and trellis restore commands
  • snapshot exact Trellis-owned project state into a strict, crash-safe external transaction before mutation
  • restore exact files, directories, symlinks, and modes only after an all-path conflict preflight
  • share managed-removal planning and structured scrubbers with uninstall while preserving uninstall behavior
  • document the command boundary in English/Chinese README content and executable backend specs

Related to #530. This PR intentionally does not implement selective capability targets or release baselines.

Safety model

  • v2 template manifest remains the ownership boundary
  • state roots are verified outside the project on both stage and restore, including symlink ancestors
  • persisted state is strict-schema validated before publication
  • backup paths must equal backup/<project-relative-path>
  • mixed JSON/TOML/Markdown surfaces preserve user-owned neighbors
  • restore conflicts perform zero project writes
  • the transaction is deleted only after exact restoration verifies

Verification

  • CLI lint and strict TypeScript checks pass
  • build passes
  • full CLI suite: 76 files / 1,712 tests pass
  • focused ablation/uninstall/store/planner/scrubber matrix passes
  • staged GitNexus comparison against origin/main: 29 files, 110 symbols, 13 intended ablate/restore/scrub flows; high breadth as expected for a destructive/recovery command

Disposable mechanical validation from one committed revision proved:

  • dry-run immutability
  • 107 managed entries removed, one mixed settings file scrubbed, and 44 empty managed directories pruned
  • no application-file changes while ablated
  • exact 152-path restore to a clean worktree
  • a recreated managed file causes conflict with an identical before/after Git-status hash, proving zero partial writes

A separate context-retrieval-focused comparison used a medium-large production TypeScript/Next.js application (about 775 tracked files and 130k tracked text lines) and a presentation-only provider benefit-title formatter. Two fresh agents received the same history-free source, symptom-level prompt, model/reasoning setting, sandbox, and time budget; native multi-agent was disabled and neither condition was rerun or steered.

  • Trellis on retrieved the project policy and passed all 5 hidden policy groups (9/9 visible + hidden tests).
  • Fully ablated passed the visible example but only 1/5 hidden groups (4/8 total tests) after overfitting with a broad superscript-tag remover.
  • Observed cost was 344.6 seconds / 37 command executions with Trellis versus 317.7 seconds / 17 commands fully ablated.

This is narrow evidence that Trellis delivered project-specific edge-case policy at measurable workflow cost in one paired run, not a general performance claim.

Scope limits

  • full project ablation only
  • no worktree creation, agent launch, prompt dispatch, scoring, or release downgrade
  • Git index/history remains visible, so agents can inspect deleted Trellis files
  • global CLI, channel data, and host transcripts are not removed

Environment note

The Codex runtime's pnpm dependency bootstrap blocked esbuild's install script before Husky could run. Commits used HUSKY=0 only after the equivalent package-local lint, typecheck, build, focused tests, and full 1,712-test gate passed.

Summary by CodeRabbit

  • New Features
    • Added trellis ablate and trellis restore with dry-run and confirmation options.
    • Supports exact restoration of managed files, directories, and symlinks using external recovery data.
    • Detects conflicts, unsafe paths, and invalid recovery data without overwriting changes.
  • Bug Fixes
    • Improved cleanup of Trellis-owned Claude and Codex settings while preserving unrelated configuration.
    • Added safer handling for symlinks, malformed files, and failed operations.
  • Documentation
    • Added CLI guidance, FAQ entries, specifications, and usage details in English and Chinese.
  • Tests
    • Added coverage for ablation, restoration, conflicts, rollback, cleanup, and filesystem safety.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: de3f3c1f-43dd-42ce-8773-b1cabff89757

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds trellis ablate and trellis restore with dry-run, confirmation, rollback, conflict detection, and external recovery state. Extracts shared managed-removal logic, expands scrubbers, adds filesystem-safety tests, and documents the workflow.

Changes

Reversible Trellis ablation

Layer / File(s) Summary
Ablation contract and delivery design
.trellis/spec/cli/backend/*, .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/*, .trellis/workspace/fantasyc/*, README.md, README_CN.md
Defines command behavior, ownership boundaries, transaction validation, safety rules, rollback, conflict handling, testing, implementation evidence, and user-facing documentation.
Shared managed-removal execution
packages/cli/src/utils/managed-removal.ts, packages/cli/src/utils/uninstall-scrubbers.ts, packages/cli/src/commands/uninstall.ts, packages/cli/src/utils/managed-paths.ts, packages/cli/src/commands/update.ts, packages/cli/src/utils/manifest-prune.ts, packages/cli/test/utils/*
Centralizes removal planning and execution, enforces path safety, and updates Claude and Codex scrubbers while preserving user-owned settings.
External recovery transaction store
packages/cli/src/utils/ablation-store.ts, packages/cli/test/utils/ablation-store.test.ts
Adds versioned external state, fingerprints, verified backups, atomic publication, locking, conflict detection, exact restoration, rollback, and cleanup.
CLI ablation and restoration commands
packages/cli/src/cli/index.ts, packages/cli/src/commands/ablate.ts, packages/cli/src/utils/cwd-guard.ts, packages/cli/test/commands/ablate.integration.test.ts
Registers both commands and implements plan rendering, dry runs, confirmation, mutation, verification, rollback, restoration, conflict handling, and integration coverage.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

Suggested reviewers: taosu0216

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding reversible full Trellis ablation to the CLI.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@lifan-builds

Copy link
Copy Markdown
Contributor Author

Benchmark update — exploratory single paired run

I ran a context-retrieval-focused ablation in a medium-large production TypeScript/Next.js application: about 775 tracked files and 130k tracked text lines. The task fixed a presentation-only provider benefit-title formatter in a browser userscript. The visible fixture exposed one realistic footnote-markup regression; a hidden evaluator covered exact one-pass decoding, narrow terminal-marker removal, preservation of unrelated markup-like text and double encoding, empty-result fallback, inert rendering, and unchanged storage identity.

  • Trellis on: passed all 5 hidden policy groups and 9/9 visible + hidden tests.
  • Fully ablated: passed the visible example but only 1/5 hidden groups (4/8 total tests); it overfit with a broad sup-tag remover that violated preservation and fallback rules.
  • Observed overhead: 344.6 s and 37 command executions with Trellis versus 317.7 s and 17 commands fully ablated — about 27 seconds and 20 commands in this pair.

Both conditions used the same history-free source tree, prompt, model/reasoning setting, sandbox, and 600-second budget; native multi-agent was disabled, runs were sequential, and there were no reruns or human interventions. The treatment restored exactly afterward, and the contamination audit found no Git-history, outside-repository, or agent-initiated network reads.

Narrow interpretation: in this test, Trellis delivered project-specific edge-case policy that materially changed correctness, at measurable workflow cost. This is evidence for context delivery in one task, not a general performance claim.

@lifan-builds

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@lifan-builds
lifan-builds marked this pull request as ready for review August 11, 2026 04:35

@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: 16

🧹 Nitpick comments (10)
packages/cli/test/utils/uninstall-scrubbers.test.ts (1)

97-111: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a case where the manifest does not own the statusline script.

The current test passes only because the command's last token differs from .claude/hooks/statusline.py. It does not cover the case where a user wrote that exact command and Trellis never installed the script. Add a case that calls scrubHooksJson with a deletedPaths list that excludes .claude/hooks/statusline.py and asserts the statusLine entry survives. This depends on the ownership fix in packages/cli/src/utils/uninstall-scrubbers.ts.

🤖 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 `@packages/cli/test/utils/uninstall-scrubbers.test.ts` around lines 97 - 111,
Add a test in the statusLine scrubber cases using the exact
`.claude/hooks/statusline.py` command while passing a deletedPaths list that
excludes that script, then assert scrubHooksJson preserves the statusLine entry
and reports fullyEmpty as false. Use the existing scrubHooksJson and
CLAUDE_DELETE_PATHS test setup, ensuring the scenario verifies non-ownership
behavior.
packages/cli/test/utils/managed-removal.test.ts (1)

44-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use it.skipIf for both Windows guards.

On Windows, the current tests pass without assertions. Conditional skips report the tests as skipped.

🤖 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 `@packages/cli/test/utils/managed-removal.test.ts` around lines 44 - 58,
Replace the early-return Windows guards in both tests, “refuses traversal
through an external parent symlink” and “treats a leaf symlink as an opaque
deletion,” with the test framework’s it.skipIf mechanism so Windows reports them
as skipped rather than passing without assertions.
packages/cli/src/utils/managed-removal.ts (1)

27-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move shared constants and cleanupEmptyDirs to a utility module.

utils/managed-removal.ts imports commands/update.ts, while commands/uninstall.ts and commands/ablate.ts import utils/managed-removal.ts. Move the shared symbols to utils/ and re-export them from commands/update.ts to prevent a future load-time cycle.

🤖 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 `@packages/cli/src/utils/managed-removal.ts` around lines 27 - 31, Move
cleanupEmptyDirs, TRELLIS_BLOCK_END, and TRELLIS_BLOCK_START into a shared
utility module under utils, update managed-removal.ts and other consumers to
import them there, and re-export the symbols from commands/update.ts to preserve
its existing public imports while removing the utility-to-command dependency.
packages/cli/src/commands/ablate.ts (1)

466-468: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Remove the duplicated conflict and verification passes.

restoreAblationTransaction already calls collectRestoreConflicts and verifyRestoredState. This function calls collectRestoreConflicts at Line 442 and verifyRestoredState again at Line 467. Each call re-hashes every managed path, and loadAblationTransaction has already hashed the whole backup tree. For a large .trellis tree the command hashes the same content several times.

Keep the preflight at Line 442 for the plan output, then rely on the store for the write path.

♻️ Proposed change
   restoreAblationTransaction(transaction);
-  verifyRestoredState(transaction);
   deleteAblationTransaction(transaction);

Remove the now-unused verifyRestoredState import only if rollbackFailedAblation no longer needs it; it still does, so keep the import.

🤖 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 `@packages/cli/src/commands/ablate.ts` around lines 466 - 468, Remove the
redundant verifyRestoredState call after restoreAblationTransaction in the
affected command flow, relying on restoreAblationTransaction for post-write
conflict and verification checks while preserving the earlier
collectRestoreConflicts preflight used for plan output. Keep the
verifyRestoredState import because rollbackFailedAblation still uses it.
packages/cli/src/utils/ablation-store.ts (2)

238-243: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Compare fingerprints structurally instead of by JSON.stringify.

JSON.stringify equality depends on key insertion order. The order currently matches between the object literals in this module and the objects returned by ablationStateSchema.parse. If a future edit reorders a schema field or a literal, every comparison silently reports a mismatch. The failure mode is severe: restore reports conflicts for unchanged paths and refuses to recover the project.

♻️ Proposed order-independent comparison
 export function fingerprintsEqual(
   left: PathFingerprint,
   right: PathFingerprint,
 ): boolean {
-  return JSON.stringify(left) === JSON.stringify(right);
+  if (left.kind !== right.kind) return false;
+  if (left.kind === "absent") return true;
+  if (left.kind === "symlink" && right.kind === "symlink") {
+    return left.target === right.target && left.mode === right.mode;
+  }
+  if (left.kind === "file" && right.kind === "file") {
+    return (
+      left.sha256 === right.sha256 &&
+      left.size === right.size &&
+      left.mode === right.mode
+    );
+  }
+  return (
+    left.kind === "directory" &&
+    right.kind === "directory" &&
+    left.sha256 === right.sha256 &&
+    left.mode === right.mode
+  );
 }

Note: hashDirectory also embeds JSON.stringify(fingerprint) in the directory hash at Line 184. That usage is self-consistent within one run, so it does not need the same change.

🤖 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 `@packages/cli/src/utils/ablation-store.ts` around lines 238 - 243, Update
fingerprintsEqual to compare PathFingerprint values structurally and
independently of object key insertion order, rather than using JSON.stringify.
Preserve equality for identical field values regardless of schema or
object-literal property ordering; leave hashDirectory’s JSON.stringify usage
unchanged.

158-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use z.iso.datetime() instead of the deprecated method form.

Zod ^4.4.2 supports both APIs, but z.iso.datetime() is the recommended replacement.

🤖 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 `@packages/cli/src/utils/ablation-store.ts` at line 158, Update the createdAt
schema field to use Zod’s z.iso.datetime() API instead of the deprecated
z.string().datetime() method form, while preserving its existing validation
behavior.
packages/cli/test/utils/ablation-store.test.ts (1)

249-263: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a regression test for conflict recovery on a preparing transaction.

This test covers a conflict after applied. It does not cover a conflict while the status is preparing. That path writes conflict and then permanently loses the pre-matching relaxation in collectRestoreConflicts. See the comment on packages/cli/src/utils/ablation-store.ts Lines 647-660.

Add a test that stages a transaction, edits one managed path, expects AblationConflictError, reverts the edit, and then expects a successful restore.

🤖 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 `@packages/cli/test/utils/ablation-store.test.ts` around lines 249 - 263, Add a
regression test alongside the existing conflict test for a transaction still in
preparing status: stage it, edit a managed path, assert
restoreAblationTransaction throws AblationConflictError, revert the edit to its
original content, then assert a subsequent restore succeeds and completes the
transaction cleanup.
packages/cli/test/commands/ablate.integration.test.ts (2)

49-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Restore the original isTTY descriptor in afterEach.

vi.restoreAllMocks() does not revert Object.defineProperty. The process object is shared between test files that run in the same worker, so the overwritten isTTY value leaks after this file completes.

Capture the original descriptor in beforeEach and reinstate it in afterEach.

♻️ Proposed change
+  let originalIsTTY: PropertyDescriptor | undefined;
+
   beforeEach(() => {
     ...
+    originalIsTTY = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
     Object.defineProperty(process.stdin, "isTTY", {
       configurable: true,
       value: true,
     });
   });
   afterEach(() => {
     vi.restoreAllMocks();
+    if (originalIsTTY) {
+      Object.defineProperty(process.stdin, "isTTY", originalIsTTY);
+    } else {
+      Reflect.deleteProperty(process.stdin, "isTTY");
+    }
🤖 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 `@packages/cli/test/commands/ablate.integration.test.ts` around lines 49 - 53,
Update the test setup around the process.stdin isTTY override to capture its
original property descriptor in beforeEach and restore that descriptor in
afterEach. Keep vi.restoreAllMocks() as-is, but explicitly reinstate the
descriptor so the shared process.stdin state does not leak between tests.

167-168: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use it.skipIf(process.platform === "win32") for tests 7 and 10.

Early returns record these Windows tests as passed without execution. Vitest 4 supports it.skipIf, which reports them as skipped.

🤖 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 `@packages/cli/test/commands/ablate.integration.test.ts` around lines 167 -
168, Update tests `#7` and `#10` to use Vitest’s it.skipIf(process.platform ===
"win32") instead of returning early inside the test bodies, so Windows runs
report them as skipped rather than passed without execution.
.trellis/spec/cli/backend/index.md (1)

35-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the ablation command to the pre-development checklist.

The new index entry is present, but the checklist does not direct contributors editing commands/ablate.ts or utils/ablation-store.ts to read commands-ablate.md. Add a bullet beside the uninstall entry that also points to filesystem-safety.md.

As per path instructions, .trellis/** work must consult spec/ for package- and layer-scoped coding guidelines before writing code in a given layer.

🤖 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 @.trellis/spec/cli/backend/index.md at line 35, Update the pre-development
checklist in the backend index to add an ablation entry beside the uninstall
entry, directing contributors working on commands/ablate.ts or
utils/ablation-store.ts to read commands-ablate.md and filesystem-safety.md,
while preserving the existing spec/ guidance for .trellis work.

Source: Path instructions

🤖 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 @.trellis/spec/cli/backend/commands-ablate.md:
- Around line 201-206: Clarify the “Privacy and session boundary” policy by
explicitly acknowledging that exact .trellis/ task, spec, and workspace bytes
may contain prompts, responses, or credentials and are copied to the external
state root. Narrow the absolute exclusion guarantee accordingly, or define an
enforceable exclusion, rejection, or encryption mechanism without claiming
redaction while preserving exact restoration.
- Around line 102-110: Update ablate() to call assertExternalStateRoot() before
invoking getTransactionPaths() or checking transactionPaths.transactionDir,
ensuring invalid in-project roots and symlink ancestors are rejected before any
recovery object is read or created. Add a regression test covering this ordering
and verifying that no recovery object is accessed or created.

In @.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md:
- Around line 72-73: Update the rollback flow described in the ablation
procedure to delete the transaction after restoring and verifying the
pre-ablation state; when recovery or verification fails, retain the transaction
and mark it as conflict for later recovery instead of leaving it preparing.
- Around line 61-68: Update restoreAblationTransaction to acquire the project
lock before restore preflight and retain it through state changes, restoration
writes, and final verification. Revalidate all path fingerprints after acquiring
the lock, then abort on conflicts before mutating project files; ensure the lock
is released on every success and failure path.

In @.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.jsonl:
- Line 7: Update both task index entries to use the archived research path for
implementation-evidence.md: change the file value in
.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.jsonl lines
7-7 and .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/check.jsonl
lines 6-6 to
.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/implementation-evidence.md.

In @.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.md:
- Line 37: Replace the <new ablation tests> placeholder in the focused pnpm test
command with the concrete paths of the ablation-store and ablate integration
test files used by the final test run, so the archived plan contains an
executable validation command.

In @.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/prd.md:
- Around line 47-48: Resolve the conflict between full `.trellis/` backups and
the prohibition on storing user data by explicitly defining the allowed recovery
payload and retention policy. Align the ablation design with the user-data paths
identified by the `uninstall` command: either document permission to back up
them or exclude/sanitize them and revise exact-restore requirements. Add a test
covering the selected policy before release.
- Line 62: Update the `trellis ablate` and `restore` behavior specification to
explicitly define exit statuses and user-facing output when `.trellis/` or
recovery state is missing, then add integration assertions covering both
missing-state cases.

In
@.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/castforge-comparison.md:
- Around line 26-30: Revise the comparison paragraph to remove the “48.0%
sooner” completion-time speedup framing. Describe 184.31 seconds only as the
elapsed-time difference between the ablated run and the fixed control cutoff,
explicitly noting that the control did not naturally complete.

In
@.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/local-castforge-validation.md:
- Around line 7-13: Replace the machine-specific /Users/lfan/... paths in the
evidence with redacted or repository-relative placeholders, preserving the
distinctions between mechanical, control, treatment, and recovery-state
locations. Keep the statement that the canonical checkout was unmodified and the
recovery state was empty after final restore.
- Around line 28-31: Replace the Git-status SHA-256 claim in the ablation record
with per-path before-and-after hashes covering file bytes, entry type, symlink
target, and mode; if those measurements are unavailable, describe the unchanged
Git-status hash only as consistent with zero writes rather than proof.

In @.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/task.json:
- Around line 15-18: Update the completed-task metadata to preserve commit
3ea84685: in
.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/task.json lines
15-18, set commit to 3ea84685 or document why null is valid; in
.trellis/workspace/fantasyc/journal-1.md lines 20-24, replace “(see git log)”
with 3ea84685.

In `@packages/cli/src/utils/ablation-store.ts`:
- Around line 647-660: Preserve crash recovery when conflicts occur after a
transaction already matches the restored-state relaxation: in
packages/cli/src/utils/ablation-store.ts lines 647-660, guard the conflict
transition in restoreAblationTransaction with
mayAlreadyBeRestored(transaction.state), and apply the same guard to the
conflict transition in packages/cli/src/commands/ablate.ts lines 442-447;
otherwise leave the transaction’s prior status intact while still throwing
AblationConflictError.

In `@packages/cli/src/utils/managed-removal.ts`:
- Around line 276-284: Update the CLI error handling around
buildManagedRemovalPlan so strict ablation failures include recovery guidance
alongside the existing error. Tell users to remove the stale manifest entry or
restore the managed file, while preserving the current failure and exit
behavior.

In `@packages/cli/src/utils/uninstall-scrubbers.ts`:
- Around line 71-82: Update isTrellisClaudeStatusLine to accept deletedPaths and
return false unless it includes CLAUDE_TRELLIS_STATUSLINE; pass deletedPaths
from its call site in scrubHooksJson. Add coverage in
packages/cli/test/utils/uninstall-scrubbers.test.ts lines 97-111 verifying a
statusLine command for the path is preserved when that path is absent from
deletedPaths.
- Around line 390-422: Update the table-boundary regex in the scan within the
uninstall scrubber to recognize both standard TOML table headers and
array-of-tables headers such as [[agents]]. Ensure the max_depth removal stops
before either header so user-owned settings in subsequent array tables are
preserved.

---

Nitpick comments:
In @.trellis/spec/cli/backend/index.md:
- Line 35: Update the pre-development checklist in the backend index to add an
ablation entry beside the uninstall entry, directing contributors working on
commands/ablate.ts or utils/ablation-store.ts to read commands-ablate.md and
filesystem-safety.md, while preserving the existing spec/ guidance for .trellis
work.

In `@packages/cli/src/commands/ablate.ts`:
- Around line 466-468: Remove the redundant verifyRestoredState call after
restoreAblationTransaction in the affected command flow, relying on
restoreAblationTransaction for post-write conflict and verification checks while
preserving the earlier collectRestoreConflicts preflight used for plan output.
Keep the verifyRestoredState import because rollbackFailedAblation still uses
it.

In `@packages/cli/src/utils/ablation-store.ts`:
- Around line 238-243: Update fingerprintsEqual to compare PathFingerprint
values structurally and independently of object key insertion order, rather than
using JSON.stringify. Preserve equality for identical field values regardless of
schema or object-literal property ordering; leave hashDirectory’s JSON.stringify
usage unchanged.
- Line 158: Update the createdAt schema field to use Zod’s z.iso.datetime() API
instead of the deprecated z.string().datetime() method form, while preserving
its existing validation behavior.

In `@packages/cli/src/utils/managed-removal.ts`:
- Around line 27-31: Move cleanupEmptyDirs, TRELLIS_BLOCK_END, and
TRELLIS_BLOCK_START into a shared utility module under utils, update
managed-removal.ts and other consumers to import them there, and re-export the
symbols from commands/update.ts to preserve its existing public imports while
removing the utility-to-command dependency.

In `@packages/cli/test/commands/ablate.integration.test.ts`:
- Around line 49-53: Update the test setup around the process.stdin isTTY
override to capture its original property descriptor in beforeEach and restore
that descriptor in afterEach. Keep vi.restoreAllMocks() as-is, but explicitly
reinstate the descriptor so the shared process.stdin state does not leak between
tests.
- Around line 167-168: Update tests `#7` and `#10` to use Vitest’s
it.skipIf(process.platform === "win32") instead of returning early inside the
test bodies, so Windows runs report them as skipped rather than passed without
execution.

In `@packages/cli/test/utils/ablation-store.test.ts`:
- Around line 249-263: Add a regression test alongside the existing conflict
test for a transaction still in preparing status: stage it, edit a managed path,
assert restoreAblationTransaction throws AblationConflictError, revert the edit
to its original content, then assert a subsequent restore succeeds and completes
the transaction cleanup.

In `@packages/cli/test/utils/managed-removal.test.ts`:
- Around line 44-58: Replace the early-return Windows guards in both tests,
“refuses traversal through an external parent symlink” and “treats a leaf
symlink as an opaque deletion,” with the test framework’s it.skipIf mechanism so
Windows reports them as skipped rather than passing without assertions.

In `@packages/cli/test/utils/uninstall-scrubbers.test.ts`:
- Around line 97-111: Add a test in the statusLine scrubber cases using the
exact `.claude/hooks/statusline.py` command while passing a deletedPaths list
that excludes that script, then assert scrubHooksJson preserves the statusLine
entry and reports fullyEmpty as false. Use the existing scrubHooksJson and
CLAUDE_DELETE_PATHS test setup, ensuring the scenario verifies non-ownership
behavior.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 669466d4-6f89-4726-a0bc-e5d8103e1881

📥 Commits

Reviewing files that changed from the base of the PR and between c8e327a and 3c91f45.

📒 Files selected for processing (29)
  • .trellis/spec/cli/backend/commands-ablate.md
  • .trellis/spec/cli/backend/commands-uninstall.md
  • .trellis/spec/cli/backend/index.md
  • .trellis/spec/cli/backend/uninstall-scrubbers.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/check.jsonl
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.jsonl
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/prd.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/castforge-comparison.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/final-review.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/implementation-evidence.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/local-castforge-validation.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/task.json
  • .trellis/workspace/fantasyc/index.md
  • .trellis/workspace/fantasyc/journal-1.md
  • README.md
  • README_CN.md
  • packages/cli/src/cli/index.ts
  • packages/cli/src/commands/ablate.ts
  • packages/cli/src/commands/uninstall.ts
  • packages/cli/src/utils/ablation-store.ts
  • packages/cli/src/utils/cwd-guard.ts
  • packages/cli/src/utils/managed-removal.ts
  • packages/cli/src/utils/uninstall-scrubbers.ts
  • packages/cli/test/commands/ablate.integration.test.ts
  • packages/cli/test/utils/ablation-store.test.ts
  • packages/cli/test/utils/managed-removal.test.ts
  • packages/cli/test/utils/uninstall-scrubbers.test.ts

Comment thread .trellis/spec/cli/backend/commands-ablate.md
Comment thread .trellis/spec/cli/backend/commands-ablate.md
Comment thread .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md Outdated
Comment thread .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md Outdated
Comment thread .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.jsonl Outdated
Comment thread .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/task.json Outdated
Comment thread packages/cli/src/utils/ablation-store.ts
Comment thread packages/cli/src/utils/managed-removal.ts
Comment thread packages/cli/src/utils/uninstall-scrubbers.ts
Comment thread packages/cli/src/utils/uninstall-scrubbers.ts

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/utils/ablation-store.ts (1)

469-542: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Hold the project lock across ablation apply and rollback. ablate stages the transaction and calls applyAblationPlan without acquiring the project lock. A concurrent trellis restore can interleave writes or delete the transaction before rollback completes. Acquire the lock before staging and hold it through apply, verification, and state transition. Make rollbackAblationTransaction reuse the held lock because the current lock is not reentrant.

🤖 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 `@packages/cli/src/utils/ablation-store.ts` around lines 469 - 542, Update the
ablation command flow around stageAblationTransaction and applyAblationPlan to
acquire the project lock before staging and retain it through apply,
verification, and state transition. Modify rollbackAblationTransaction to reuse
the already-held lock rather than attempting a nested acquisition, preserving
safe cleanup and release when the full operation completes.
🧹 Nitpick comments (2)
packages/cli/src/utils/ablation-store.ts (1)

639-650: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

releaseProjectLock can delete a lock owned by another process.

The function reads the holder pid, compares it, then unlinks. Another process can replace the lock file between the read and the unlink, and this process then removes the new owner's lock. The window is small, but the failure mode is two concurrent restores on the same project.

An openSync/fstatSync inode check, or a per-acquisition unique token compared before unlink, closes the window.

🤖 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 `@packages/cli/src/utils/ablation-store.ts` around lines 639 - 650, Make
releaseProjectLock atomically verify lock ownership before removal so a
replacement lock cannot be deleted after the initial read; use an
openSync/fstatSync inode check or the existing acquisition’s unique token, and
unlink only when that identity still matches the lock being released.
packages/cli/test/utils/managed-removal.test.ts (1)

59-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend this test to execute the plan.

Import and call executeManagedRemovalPlan(tmpDir, plan). Assert that .codex/hooks.json is removed and target.json retains its original contents.

🤖 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 `@packages/cli/test/utils/managed-removal.test.ts` around lines 59 - 77, Extend
the “treats a leaf symlink as an opaque deletion” test by calling
executeManagedRemovalPlan(tmpDir, plan) after building the plan. Assert that
.codex/hooks.json no longer exists and target.json still contains its original
contents.

Source: Linters/SAST tools

🤖 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 @.trellis/spec/cli/backend/commands-ablate.md:
- Around line 25-28: Update the ablate command specification to require an
atomic per-project reservation before snapshot creation, held through staging
and ablation and released on both success and failure; clarify that only one
concurrent invocation may proceed before transaction publication, and add a
separate-process concurrent-ablate test covering this behavior.
- Around line 192-196: Update the restore implementation centered on
restoreEntry() to protect each target path from concurrent edits after
fingerprint validation, using a per-path lock or compare-and-replace check
before replacement. Restore regular files via an atomic temporary-file write and
rename, preserving the existing conflict behavior when validation fails. Ensure
injected write or rename failures clean up temporary files and transition the
transaction out of restoring safely, then add tests covering concurrent races
and injected failures.

In `@packages/cli/src/utils/managed-paths.ts`:
- Around line 10-24: Update cleanupEmptyDirs to validate dirPath before
isManagedPath or filesystem access: reject absolute paths and any "." or ".."
segments, resolve the canonical project-relative path, and require the resolved
location to remain below cwd before constructing fullPath. Preserve managed-root
and recursive parent cleanup behavior, and add regression tests covering
traversal inputs such as .trellis/../user-empty-dir.

---

Outside diff comments:
In `@packages/cli/src/utils/ablation-store.ts`:
- Around line 469-542: Update the ablation command flow around
stageAblationTransaction and applyAblationPlan to acquire the project lock
before staging and retain it through apply, verification, and state transition.
Modify rollbackAblationTransaction to reuse the already-held lock rather than
attempting a nested acquisition, preserving safe cleanup and release when the
full operation completes.

---

Nitpick comments:
In `@packages/cli/src/utils/ablation-store.ts`:
- Around line 639-650: Make releaseProjectLock atomically verify lock ownership
before removal so a replacement lock cannot be deleted after the initial read;
use an openSync/fstatSync inode check or the existing acquisition’s unique
token, and unlink only when that identity still matches the lock being released.

In `@packages/cli/test/utils/managed-removal.test.ts`:
- Around line 59-77: Extend the “treats a leaf symlink as an opaque deletion”
test by calling executeManagedRemovalPlan(tmpDir, plan) after building the plan.
Assert that .codex/hooks.json no longer exists and target.json still contains
its original contents.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c160637-6f6e-4676-aea7-3498a22dd31a

📥 Commits

Reviewing files that changed from the base of the PR and between 3c91f45 and a84f9fb.

📒 Files selected for processing (24)
  • .trellis/spec/cli/backend/commands-ablate.md
  • .trellis/spec/cli/backend/index.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/check.jsonl
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.jsonl
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/prd.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/castforge-comparison.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/local-castforge-validation.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/task.json
  • .trellis/workspace/fantasyc/journal-1.md
  • README.md
  • README_CN.md
  • packages/cli/src/commands/ablate.ts
  • packages/cli/src/commands/update.ts
  • packages/cli/src/utils/ablation-store.ts
  • packages/cli/src/utils/managed-paths.ts
  • packages/cli/src/utils/managed-removal.ts
  • packages/cli/src/utils/manifest-prune.ts
  • packages/cli/src/utils/uninstall-scrubbers.ts
  • packages/cli/test/commands/ablate.integration.test.ts
  • packages/cli/test/utils/ablation-store.test.ts
  • packages/cli/test/utils/managed-removal.test.ts
  • packages/cli/test/utils/uninstall-scrubbers.test.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • README.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.jsonl
  • .trellis/workspace/fantasyc/journal-1.md
  • README_CN.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/local-castforge-validation.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/task.json
  • packages/cli/test/utils/uninstall-scrubbers.test.ts
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/research/castforge-comparison.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/implement.md
  • packages/cli/src/utils/uninstall-scrubbers.ts
  • .trellis/spec/cli/backend/index.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md
  • packages/cli/src/utils/managed-removal.ts
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/check.jsonl
  • packages/cli/src/commands/ablate.ts

Comment thread .trellis/spec/cli/backend/commands-ablate.md
Comment thread .trellis/spec/cli/backend/commands-ablate.md
Comment thread packages/cli/src/utils/managed-paths.ts

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/utils/ablation-store.ts (1)

626-646: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The stale-lock path can delete a lock that another process just created.

acquireProjectLock writes the pid after openSync. Between those two calls the lock file exists and is empty. A second process that hits EEXIST reads an empty file, computes holderPid = 0 (Number("") is 0), skips the pidAlive check, and unlinks the live lock. Both processes then believe they own the project lock, and ablation state plus project mutations can interleave.

Treat an unparsable or empty lock as stale only after an age threshold, and confirm the file identity before unlinking.

🔒 Proposed fix
     let holderPid = 0;
+    let lockAgeMs = 0;
     try {
       holderPid = Number(fs.readFileSync(paths.lockFile, "utf-8").trim());
+      lockAgeMs = Date.now() - fs.statSync(paths.lockFile).mtimeMs;
     } catch {
       continue;
     }
     if (holderPid && pidAlive(holderPid)) {
       throw new Error(
         `Another Trellis ablation operation is already in progress for this project (pid ${holderPid}).`,
       );
     }
+    if (!Number.isInteger(holderPid) || holderPid <= 0) {
+      // A concurrent creator has not written its pid yet. Only reclaim the
+      // lock once it is old enough to be a genuine leftover.
+      if (lockAgeMs < LOCK_STALE_MS) {
+        throw new Error(
+          "Another Trellis ablation operation is already in progress for this project.",
+        );
+      }
+    }
     try {
       fs.unlinkSync(paths.lockFile);
🤖 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 `@packages/cli/src/utils/ablation-store.ts` around lines 626 - 646, Update
acquireProjectLock’s stale-lock handling so an empty or unparsable lock file is
not immediately deleted: only treat it as stale after the defined age threshold.
Before unlinking any stale lock, re-stat or otherwise confirm the file identity
has not changed since it was read, then retry acquisition when another process
replaced it.
🧹 Nitpick comments (2)
packages/cli/test/utils/ablation-store.test.ts (1)

306-309: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The ready promise can hang if the helper process exits early.

holder.once("error", reject) fires only for spawn failures. If the child throws after start, for example when writeFileSync with flag: "wx" hits EEXIST, the child exits without writing to stdout and the promise never settles. The test then fails by timeout instead of by cause.

♻️ Proposed hardening
       await new Promise<void>((resolve, reject) => {
         holder.once("error", reject);
+        holder.once("exit", (code) =>
+          reject(new Error(`lock holder exited early with code ${code}`)),
+        );
         holder.stdout.once("data", () => resolve());
       });
🤖 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 `@packages/cli/test/utils/ablation-store.test.ts` around lines 306 - 309,
Update the readiness promise around holder in the ablation-store test to reject
when the child process exits before emitting stdout, while preserving rejection
for spawn errors and resolution on the first stdout data event. Ensure all
listeners are cleaned up or the promise settles only once.
packages/cli/src/utils/ablation-store.ts (1)

709-733: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the repeated expected-ablated assertion.

The same conflict check appears four times in restoreEntry. Each copy builds an identical AblationConflictError. Extract a helper to keep the checks in sync.

♻️ Proposed refactor
+function assertExpectedAblated(entry: AblationEntry, destination: string): void {
+  const actual = fingerprintPath(destination);
+  if (fingerprintsEqual(actual, entry.expectedAblated)) return;
+  throw new AblationConflictError([
+    {
+      relativePath: entry.relativePath,
+      expected: entry.expectedAblated,
+      actual,
+    },
+  ]);
+}

Then replace each inline block with if (verifyExpectedState) assertExpectedAblated(entry, destination);.

Also applies to: 756-767, 785-796

🤖 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 `@packages/cli/src/utils/ablation-store.ts` around lines 709 - 733, In
restoreEntry, extract the repeated verifyExpectedState fingerprint comparison
and AblationConflictError construction into an assertExpectedAblated helper that
accepts entry and destination. Replace all four inline expected-ablated conflict
checks with calls to this helper guarded by verifyExpectedState, preserving the
existing expected and actual fingerprint values.
🤖 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 @.trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/prd.md:
- Around line 66-70: Update the restore replacement flow described by
beforeReplace to prevent fs.renameSync from overwriting a target changed by an
external writer: use a no-replace compare-and-swap mechanism, or explicitly
narrow the documented conflict guarantee. Add coverage for a writer acting
between beforeReplace and replacement, verifying the external content remains
intact, temporary files are removed, and the operation can be retried.

---

Outside diff comments:
In `@packages/cli/src/utils/ablation-store.ts`:
- Around line 626-646: Update acquireProjectLock’s stale-lock handling so an
empty or unparsable lock file is not immediately deleted: only treat it as stale
after the defined age threshold. Before unlinking any stale lock, re-stat or
otherwise confirm the file identity has not changed since it was read, then
retry acquisition when another process replaced it.

---

Nitpick comments:
In `@packages/cli/src/utils/ablation-store.ts`:
- Around line 709-733: In restoreEntry, extract the repeated verifyExpectedState
fingerprint comparison and AblationConflictError construction into an
assertExpectedAblated helper that accepts entry and destination. Replace all
four inline expected-ablated conflict checks with calls to this helper guarded
by verifyExpectedState, preserving the existing expected and actual fingerprint
values.

In `@packages/cli/test/utils/ablation-store.test.ts`:
- Around line 306-309: Update the readiness promise around holder in the
ablation-store test to reject when the child process exits before emitting
stdout, while preserving rejection for spawn errors and resolution on the first
stdout data event. Ensure all listeners are cleaned up or the promise settles
only once.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dc9c955-4ffa-4656-bddc-737ffabae344

📥 Commits

Reviewing files that changed from the base of the PR and between a84f9fb and d6417c2.

📒 Files selected for processing (8)
  • .trellis/spec/cli/backend/commands-ablate.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/prd.md
  • packages/cli/src/commands/ablate.ts
  • packages/cli/src/utils/ablation-store.ts
  • packages/cli/src/utils/managed-paths.ts
  • packages/cli/test/commands/update-internals.test.ts
  • packages/cli/test/utils/ablation-store.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/cli/src/utils/managed-paths.ts
  • .trellis/spec/cli/backend/commands-ablate.md
  • packages/cli/src/commands/ablate.ts
  • .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/design.md

Comment thread .trellis/tasks/archive/2026-08/08-10-full-trellis-ablation/prd.md
@lifan-builds

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@taosu0216

Copy link
Copy Markdown
Contributor

First, the part that is on us: you asked for direction in #530, followed up on 2026-08-10 explicitly saying you wanted maintainer input before opening a PR, and got no reply either time. You then scoped the work down yourself, deferred selective capabilities and release baselines, and built it. Waiting on an answer that never came, and being left to guess the boundary, is not the experience we want contributors to have. Sorry.

Where this stands

The direction question — whether reversible project mutation belongs in Trellis at all — is still genuinely open on our side and needs a maintainer decision rather than a reviewer's opinion. I am flagging it for that decision now rather than letting it sit again. What I can give you today is the code-level read.

Code-level observations

The scoping you chose looks right: full-only, no selective capabilities, no release baselines, no worktree or agent orchestration. Reusing the v2 managed-file manifest and the uninstall planner rather than inventing a parallel ownership model is the correct instinct, and pulling the shared removal logic out into managed-removal.ts / uninstall-scrubbers.ts makes uninstall and ablate one mechanism with two policies instead of two codebases that drift.

The all-path conflict preflight before restore, with zero writes on conflict, is the property that makes this safe enough to be worth having. Same for verifying the backup before mutating anything.

Two concrete things to fix regardless of which way the direction call goes:

  1. Personal files in the diff. .trellis/workspace/fantasyc/index.md and journal-1.md are your local session-journal output and should not be in the PR. (Archived task artefacts under .trellis/tasks/archive/ are fine — that is repo convention.)

  2. The open CodeRabbit threads. You replied "Fixed in commit a84f9fb" to roughly fourteen of them but the threads are still open, so a reader cannot tell settled from outstanding. Please resolve the ones that are done. One appears to have had no reply at all — managed-removal.ts:284, about the guidance shown when the planner errors. Minor, but worth closing out.

What happens next

The direction decision comes back to you here, not another round of silence. If it is yes, the code is in reviewable shape and the remaining work is small. If it is no, you will get that answer with the reasoning rather than a stale PR — and in that case the managed-removal.ts / uninstall-scrubbers.ts refactor may still be worth landing on its own, since it improves uninstall independently of whether ablate ever ships.

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.

2 participants