Conversation
There was a problem hiding this comment.
Review Summary
This PR correctly implements section-marker-based merging for shared instruction files (CLAUDE.md, AGENTS.md), fixing the user content overwrite issue described in #628.
No Blocking Issues
The implementation is solid and follows project guidelines.
Code Quality ✓
- TypeScript strict mode: No
anytypes, proper type annotations throughout - Named exports: Follows project conventions
- Error handling: Proper use of
instanceof Deno.errors.NotFoundchecks - Atomic writes: Uses
atomicWriteTextFilefor safe file updates
DDD Compliance ✓
RepoService is appropriately designed as a Domain Service:
- Stateless (version passed to constructor, paths passed to methods)
- Coordinates file operations without owning state
- Clear separation between shared-file tools (claude/opencode/codex) and tool-specific files (cursor/kiro)
Test Coverage ✓
Excellent coverage with tests for all four cases:
- No file exists → creates with managed section
- File has markers → replaces content between markers
- Legacy swamp content (no markers) → migrates to marked section
- No swamp content → prepends managed section
Plus explicit tests verifying cursor/kiro files are still fully overwritten (no markers).
Suggestions (Non-blocking)
Minor redundancy in migrateLegacyInstructions at line 514:
const prefix = before.length > 0 ? before : "";This could simply be const prefix = before; since an empty string already equals "". Not worth changing for this PR.
🤖 Generated with Claude Code
There was a problem hiding this comment.
Adversarial Review
Critical / High
-
HIGH: Missing END marker causes cryptic crash (
repo_service.ts:417-423)The
ensureInstructionsSectionmethod checks only for the BEGIN marker before callingreplaceManagedSection:if (existingContent.includes(INSTRUCTIONS_SECTION_BEGIN)) { const updatedContent = this.replaceManagedSection(...);
But
replaceManagedSectionthrows if either marker is missing:if (beginIndex === -1 || endIndex === -1) { throw new Error("Internal error: managed section markers not found"); }
Breaking example: User accidentally deletes the END marker from their CLAUDE.md:
<!-- BEGIN swamp managed section - DO NOT EDIT --> # Project ...template content... (END marker accidentally deleted) ## My Custom Rules
Running
swamp repo upgradethrows "Internal error: managed section markers not found" — a confusing message that doesn't help the user fix their file.Suggested fix: Check for both markers in
ensureInstructionsSection. If only BEGIN exists, either:- Fall through to case 4 (prepend) and warn the user, OR
- Add the missing END marker at a reasonable location
-
HIGH: Legacy migration duplicates content on partial template match (
repo_service.ts:461-492)The
migrateLegacyInstructionsmethod searches for exact strings:const startPattern = /# Project\n\nThis repository is managed with \[swamp\]/; const endMarker = "Use `swamp --help` to see available commands.\n";
If the start pattern matches but the end marker doesn't (user modified it slightly), the fallback prepends the new section while keeping all old content:
if (endIndex === -1) { return newSection + "\n" + separator + content; }
Breaking example: User has legacy CLAUDE.md where they changed one word:
# Project This repository is managed with [swamp]... ... Use `swamp --help` to see commands. ← changed "available commands" to "commands"
After upgrade, they get:
<!-- BEGIN swamp managed section --> ...new template... <!-- END swamp managed section --> # Project This repository is managed with [swamp]... ... Use `swamp --help` to see commands.
The user now has duplicate swamp instructions — the old template plus the new managed section.
Suggested fix: When start matches but end doesn't, scan for likely end boundaries (e.g., look for
##headers that aren't part of the known template) rather than blindly prepending. Or at minimum, warn the user that migration was partial.
Medium
-
MEDIUM: Reversed markers corrupt file (
repo_service.ts:716-738)replaceManagedSectiondoesn't validate thatbeginIndex < endIndex. If a user (or merge conflict) produces:<!-- END swamp managed section --> content <!-- BEGIN swamp managed section - DO NOT EDIT -->
The method produces malformed output with duplicated/corrupted content because
beforecaptures up to BEGIN (including END) andaftercaptures from END position (including BEGIN and beyond).Suggested fix: Add
if (endIndex < beginIndex) throw new Error(...)or treat as invalid markers. -
MEDIUM: Multiple marker pairs cause silent data loss (
repo_service.ts:722-723)indexOffinds only the first occurrence. If a file has duplicate BEGIN/END pairs (e.g., from a bad merge):<!-- BEGIN swamp managed section --> ...section 1... <!-- END swamp managed section --> <!-- BEGIN swamp managed section --> ...section 2... <!-- END swamp managed section -->
Only the first section is replaced; the second is treated as user content. On subsequent upgrades, only the first is updated. This could lead to confusion and stale instructions.
Suggested fix: Detect multiple occurrences and either error with a clear message or warn the user.
Low
-
LOW: TOCTOU race condition (
repo_service.ts:401-410)There's a time gap between reading the file and writing it. If another process creates the file in between, the write silently overwrites it. Unlikely in practice since this is user-initiated CLI.
-
LOW: Empty file gets extra trailing newline (
repo_service.ts:446-450)When existing content is empty string, the separator logic adds
\nproducing<section>\n\ninstead of<section>\n. Cosmetic only.
Verdict
FAIL — Issues #1 and #2 are high severity bugs that can cause confusing errors or content duplication during normal upgrade operations. The marker validation issues (#3, #4) are lower priority but should be addressed for robustness.
`swamp repo upgrade` was fully overwriting CLAUDE.md/AGENTS.md with the
swamp template, destroying any project-specific content users had added.
Adopt the same section-marker pattern already used for .gitignore
management. Shared instruction files (CLAUDE.md, AGENTS.md) now use
HTML comment markers to delimit the swamp-managed section:
<!-- BEGIN swamp managed section - DO NOT EDIT -->
...template content...
<!-- END swamp managed section -->
Tool-specific files (.cursor/rules/swamp.mdc, .kiro/steering/swamp-rules.md)
continue to be fully overwritten since they are not shared with user content.
On init: if a CLAUDE.md already exists, the managed section is merged in
(prepended) rather than skipping the file entirely.
On upgrade, four cases are handled:
1. No file — create with managed section
2. File has markers — replace only the managed section
3. File has legacy content (no markers) — migrate to marked format
4. File has no swamp content — prepend managed section
Also generalizes replaceManagedSection() to accept begin/end marker
strings so it works for both .gitignore and instruction files.
Fixes #628
Co-authored-by: Blake Irvin <blakeirvin@me.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Review: Approved ✅
This is a well-implemented fix for the CLAUDE.md/AGENTS.md overwrite issue (#628).
Strengths
Domain-Driven Design Compliance:
RepoServicecorrectly follows the Domain Service pattern with stateless, well-factored methods- Good separation of concerns:
usesSharedInstructionsFile()for classification,ensureInstructionsSection()for orchestration,migrateLegacyInstructions()for conversion - Exhaustive type handling with
assertNever()in the tool switch statement - Clear ubiquitous language: "managed section", "legacy content", "shared instructions file"
Code Quality:
- No
anytypes - Named exports
- Uses
atomicWriteTextFilefor safe file operations - Proper error handling with
UserErrorfor user-facing issues
Test Coverage:
- Comprehensive tests covering all 4 cases (no file, with markers, legacy content, no swamp content)
- Edge case tests: missing END marker recovery, duplicate markers detection, partial legacy match
- Good coverage of tool-specific vs shared file behavior (cursor/kiro still fully overwritten)
Security:
- No injection vulnerabilities in marker detection
- Validates marker order and duplicate markers
- Safe atomic writes
Minor Suggestions (non-blocking)
The replaceManagedSection() refactoring to accept begin/end markers as parameters is clean and avoids code duplication. The 4-case handling in ensureInstructionsSection() is clear and well-documented with comments.
The manual testing items in the test plan should be completed before merging, but the automated tests provide good confidence.
🤖 Reviewed with Claude Code
There was a problem hiding this comment.
Adversarial Review
Critical / High
-
Duplicate BEGIN markers when END marker is missing (
repo_service.ts:489-526)- What's wrong: When a file has the BEGIN marker but is missing the END marker (e.g., user accidentally deleted it), AND the file body contains the legacy signature "This repository is managed with [swamp]", case 3 (legacy migration) matches instead of case 4 (prepend).
- Breaking example:
- Run
swamp repo init- creates CLAUDE.md with both markers - Accidentally delete the
<!-- END swamp managed section -->line - Run
swamp repo upgrade - File now has duplicate BEGIN markers
- Next
swamp repo upgradefails with "Found multiple swamp managed sections"
- Run
- Code path:
ensureInstructionsSection→ case 2 fails (END missing) → case 3 matches (body contains signature) →migrateLegacyInstructionstreats orphaned BEGIN as "prefix" and prepends newSection (which includes its own BEGIN marker) - Why test passes: The test at lines 1862-1892 uses
assertStringIncludeswhich succeeds even with duplicates. It doesn't verify exactly ONE BEGIN marker exists. - Suggested fix: In
ensureInstructionsSection, if BEGIN marker exists but END doesn't, check for this corrupted state explicitly and either throw a clear UserError or strip the orphaned BEGIN before prepending.
-
Legacy migration silently deletes user content (
repo_service.ts:508-514)- What's wrong: When migrating legacy content, if the end marker text
"Use \swamp --help` to see available commands.\n"is not found (because user edited it), the fallback at lines 508-514 returnsprefix + newSection + "\n"`, discarding EVERYTHING after the legacy start pattern. - Breaking example:
If user changed "available commands." to just "commands.", the entire "## My Team Standards" section is lost.
# Project This repository is managed with [swamp]... ## Commands Use `swamp --help` to see commands. ## My Team Standards Always write tests. Everything here will be SILENTLY DELETED
- Why test passes: The test at lines 1894-1937 sets up this exact scenario with
"## My Custom Section\n\nUser content here.\n"but DOES NOT assert this content is preserved. It only checks for markers and no duplicate headings. - Suggested fix: When end marker isn't found, preserve everything after the matched start pattern that doesn't look like part of the swamp template. Alternatively, abort migration with a warning and fall back to prepend-only mode to avoid data loss.
- What's wrong: When migrating legacy content, if the end marker text
Medium
- Duplicate END markers not detected (
repo_service.ts:743-750)- Only duplicate BEGIN markers are detected and rejected. If a bad merge creates
BEGIN...content...END...END, the second END marker is left orphaned in the file. - Not a functional break but leaves garbage in user's file that could confuse them.
- Suggested fix: Add a similar check:
const secondEnd = content.indexOf(endMarker, endIndex + 1);
- Only duplicate BEGIN markers are detected and rejected. If a bad merge creates
Low
- Test assertions are too weak (
repo_service_test.ts)- Multiple tests use
assertStringIncludesfor marker presence without verifying uniqueness. Tests pass with corrupted outputs. - Consider adding explicit count assertions like the one at line 1931-1936.
- Multiple tests use
Verdict
FAIL - Two HIGH severity issues found:
- Missing END marker recovery creates duplicate markers, breaking subsequent upgrades
- Legacy migration can silently delete user content when template text was modified
Both issues have incomplete test coverage that allows them to pass CI while producing incorrect results.
Summary
Fixes #628 —
swamp repo upgradewas fully overwritingCLAUDE.md/AGENTS.mdwith the swamp template, destroying any project-specific content users had added..gitignoremanagementCLAUDE.md,AGENTS.md) now use HTML comment markers to delimit the swamp-managed section.cursor/rules/swamp.mdc,.kiro/steering/swamp-rules.md) continue to be fully overwritten since they are not shared with user contentinit: if aCLAUDE.mdalready exists, the managed section is merged in rather than skipping the fileupgrade, four cases are handled: no file, file with markers, file with legacy content, file with no swamp contentTest plan
deno check— passesdeno lint— passesdeno fmt— passesdeno run test— 2700/2700 passeddeno run compile— binary compilesswamp repo initin empty dir creates CLAUDE.md with markersswamp repo initin dir with existing CLAUDE.md merges managed sectionswamp repo upgradepreserves it🤖 Generated with Claude Code