Preview & apply transactions
Stage full-file replacements, review apply-time path/hash checks, pin an optional baseline, and understand journal, rollback and verification limits.
Signalint 1.2.1 hardens the explicit source-writing tools introduced in 1.2.0. Diagnostic checks do not edit source, but apply_diagnostic_fix can. The caller supplies complete original and replacement content; Signalint does not generate a fix automatically.
Prepare a preview
Section titled “Prepare a preview”Call preview_diagnostic_fix:
{ "patches": [ { "file": "src/count.ts", "originalContent": "export const count: number = '1';\n", "patchedContent": "export const count: number = 1;\n", "description": "Use a numeric initializer" } ]}Preview validates paths, including the deepest existing ancestor of a new path, and stages replacements in memory. The manager computes a SHA-256 staging hash from originalContent; an originalHash field is not an accepted MCP patch argument. No source file is written during preview.
The result contains transactionId, filesCount, summary, status, and patchesPreview. Each preview item reports file, optional description, originalLength, and patchedLength—character lengths, not a unified diff or added/removed line counts. Review the actual contents separately.
Default retention remains 20 previews / 15 minutes, in memory only. A new file uses empty originalContent; these are full-file replacements, not automatically rebased hunks.
Review and apply with an explicit baseline
Section titled “Review and apply with an explicit baseline”After the host obtains the appropriate permission, call:
{ "transactionId": "transaction-id", "confirm": true, "baselineCheckId": "baseline-check-id"}confirm: false refuses application. The boolean itself is not proof of human consent: a client or model can send it. Use your host’s destructive-tool permission controls.
The new optional baselineCheckId has three distinct outcomes:
| Baseline | Behavior before/after source writes |
|---|---|
| Omitted | Apply and post-check; no delta is requested. A successful result includes message: "No baselineCheckId provided; baseline delta was not requested." |
| Active retained snapshot | Resolve that baseline before writing, then compare it with the post-apply snapshot |
| Unknown or expired | Refuse before any writes, with isError: true, filesModified: [], and baseline_check_unknown or baseline_snapshot_expired |
A baseline refusal does not consume an otherwise active preview. Establish a fresh baseline and retry before the preview’s own TTL expires. No implicit latest-snapshot baseline is selected anymore. Inspect source, scope and engine coverage; a content-derived check ID is still not a unique invocation ID.
What the hardened apply path does
Section titled “What the hardened apply path does”- Revalidates every staged path against the project boundary, including symlink/ancestor checks, before writes.
- Compares the current UTF-8 content and its SHA-256 against the preview’s original content/hash. Drift or a missing expected file returns
file_drift_detected, with no writes in that preflight. - Builds an in-memory journal, recording whether targets existed and their current original text.
- Applies files sequentially through adjacent
.signalint-tmp-*files and rename replacement on the same parent filesystem. - On a write failure, attempts reverse-order restoration of already-applied entries and removal of newly created files. It reports
rolled_back/transaction_aborted_and_rolled_backand discards that transaction. - On successful writes, consumes the preview and runs the handler’s file-check provider. This verification is separate from the write transaction.
Statuses remain success, rolled_back, and error. The result includes transactionId, filesModified, and optional code, message, error, postCheckId, or delta.
Limits that still matter
Section titled “Limits that still matter”- Not filesystem-atomic across files. Replacements are sequential; other processes can observe an intermediate multi-file state. Rename fallback for busy/existing destinations can unlink before retry, so do not assume every platform/failure path preserves an indivisible replacement.
- Rollback is best-effort. Restoration errors are swallowed.
rolled_backwithfilesModified: []is not an independent proof that disk was fully restored; directories may remain. - No persistent crash recovery or durability guarantee. The journal lives in memory. The implementation does not add a persistent journal or an explicit fsync protocol for power loss/process termination.
- Apply-time revalidation is real, not an OS sandbox. It is a pre-write check, not a lock against an adversarial external process replacing paths/content after preflight. Use a trusted project and filesystem.
- Replacement metadata is not preserved by contract. New temporary files replace destination inodes; do not assume original executable bits, ownership, hard-link relationships or every filesystem attribute survive.
- Verification does not roll back successful writes. Newly introduced diagnostics are not an automatic rollback condition. A thrown post-check can still leave
status: successwithdelta.error; that fallback does not match the ordinary advertised diagnostic-delta shape. - Check cancellation support does not imply transactional cancellation or undo.
Keep a version-control diff/backup, review all replacements, and run a final check with working engines and appropriate coverage. Read security and known limits.
Discard a preview
Section titled “Discard a preview”{ "transactionId": "transaction-id" }discard_diagnostic_fix returns { transactionId, discarded: boolean } and removes in-memory preparation only. It does not undo an applied change. Unknown/already removed previews return discarded: false.
Source: manager.ts, paths.ts, toolArguments.ts, and applyDiagnosticFix.ts.