Skip to content

feat(mcp): add manage_adr mode='append' - #1243

Open
andis777 wants to merge 1 commit into
DeusData:mainfrom
andis777:feat/adr-append-mode
Open

feat(mcp): add manage_adr mode='append'#1243
andis777 wants to merge 1 commit into
DeusData:mainfrom
andis777:feat/adr-append-mode

Conversation

@andis777

Copy link
Copy Markdown

Problem

manage_adr can only replace: mode='update' overwrites the stored document in full. Adding a single entry to a long-lived ADR therefore costs a full re-send of the whole document.

That is more than an efficiency problem. The caller has to reproduce every byte it did not intend to change, so the prefix is only as safe as the round-trip that carried it — an agent re-emitting ~60 KB of prose to append one paragraph has ~60 KB of opportunity to silently drop or mangle text that nobody asked it to touch. Verifying the result means fetching the document back and diffing it, which doubles the cost again.

Change

Adds mode='append', which concatenates server-side so the stored copy is the only source of the prefix:

  • Trailing newlines on the stored content are trimmed and exactly one blank line is inserted, so repeated appends cannot accumulate whitespace.
  • An empty or missing ADR degrades to a plain create (no leading blank line), so append is safe as a first write.
  • append without content returns status='missing_content' and isError instead of falling through to the get branch — a caller that meant to write must not receive a success-shaped read. (update has the same fall-through today; left alone to keep this change scoped.)
  • The response carries content_length and appended_length so callers can confirm the write landed without re-fetching the document.

get, update, store and sections are untouched.

The mode enum and content now carry descriptions, mostly so update = REPLACE the whole document is visible at the call site rather than something you learn by overwriting an ADR.

Tests

Four cases in tests/test_mcp.c:

  • tool_manage_adr_append_extends_without_replacing — prefix survives verbatim, new chunk lands after it, exactly one blank line joins them, both sections still parse via mode='sections'.
  • tool_manage_adr_append_creates_when_absent — exact-match assert that no leading blank line is introduced.
  • tool_manage_adr_append_without_content_errors — errors and leaves the stored ADR byte-identical.
  • tool_manage_adr_append_is_advertised — the mode appears in tools/list, so callers can discover it instead of continuing to pay for rewrites.

Verification I could not do

I was unable to build or run the test suite — no C toolchain was available on the machine this was written on. Please treat compilation as unverified; I would rather flag this than have it discovered in review.

What I did instead:

  • Checked every API used against existing call sites in the tree (heap_strdup, SKIP_ONE from src/foundation/constants.h, yyjson_mut_obj_add_uint as used in src/pipeline/artifact.c, cbm_mcp_handle_tool, ASSERT_STR_EQ), and mirrored the existing size_t/SKIP_ONE indexing idiom from adr_list_sections_from_content to stay within the project's -Werror settings.
  • Ported adr_append_content to a scratch script and ran the boundary cases: CRLF endings, multiple trailing newlines, empty existing content, content consisting only of newlines, empty addition, and four appends in a row (no triple newlines accumulate). All behaved as asserted in the C tests.
  • Ran scripts/security-audit.sh: passes. It reports src/mcp/mcp.c has 17 file read operations (expected max 15), but that is pre-existing — git show HEAD~1:src/mcp/mcp.c | grep -c 'fopen\|fread\|read_file' is also 17. This change adds no file reads.

Happy to adjust naming (append vs add), the separator policy, or the missing_content status if you'd prefer different conventions.

🤖 Generated with Claude Code

'update' replaces the whole ADR, so adding a single entry costs a full
re-send of the document. For a large ADR that is both expensive and a
chance to silently drop existing text on the round-trip: the caller has
to reproduce every byte it did not intend to change.

'append' concatenates server-side, so the stored copy is the only source
of the prefix. Trailing newlines are trimmed and exactly one blank line
is inserted, so repeated appends do not accumulate whitespace; an empty
or missing ADR degrades to a plain create.

append without 'content' fails with status='missing_content' instead of
falling through to the 'get' branch — a caller that meant to write must
not receive a success-shaped read.

The response carries content_length and appended_length so callers can
confirm the write landed without re-fetching the document, which for
large ADRs is the expensive part.

Tests: append extends without replacing (order + single-blank-line
separator + sections still parse), append creates when absent, append
without content errors and leaves the ADR untouched, and the mode is
advertised in tools/list.

Signed-off-by: andis777 <24672074+andis777@users.noreply.github.com>
@andis777
andis777 requested a review from DeusData as a code owner July 24, 2026 06:07
@andis777

Copy link
Copy Markdown
Author

CI is green — this closes the verification gap flagged in the description

The PR description says compilation was unverified because no C toolchain was available where this was written. CI has now covered exactly that, so please disregard that caveat: all 25 checks pass.

Builds and full suites on every target: pr-smoke (ubuntu-latest, macos-14, windows-latest), test-unix (ubuntu-latest + ubuntu-24.04-arm with gcc/g++, macos-14 + macos-15-intel with cc/c++), test-windows (CLANG64/x86_64), test-tsan (ubuntu-latest, ubuntu-24.04-arm, macos-14). Plus lint, security / security-static, security / codeql-gate, security / license-gate, dco, test / test-windows-guards, test / shard-completeness.

The four new tests actually executed

"Suite passed" is not the same claim as "my tests ran", so I checked. Per-test names are not in the CI logs — a control grep for the pre-existing tool_manage_adr_unified_backend_issue256 also returns nothing, so their absence proves nothing either way. The test counts do prove it. Comparing this branch against the main run at b6b55192:

shard main this PR delta
test-unix (ubuntu-latest, gcc, 1/3) 1854 passed 1858 passed +4
test-unix (ubuntu-latest, gcc, 2/3) 1959 passed 1959 passed 0
test-unix (ubuntu-latest, gcc, 3/3) 2804 passed 2804 passed 0

tests/test_mcp.c runs in shard 1/3, which is up by exactly the four added cases, with 0 failed throughout. No other shard moved, so nothing else was disturbed.

One note on the audit, in case it comes up in review: scripts/security-audit.sh passes but prints REVIEW: src/mcp/mcp.c has 17 file read operations (expected max 15). That is pre-existing drift against EXPECTED_MAX, not something this PR introduces — git show HEAD~1:src/mcp/mcp.c | grep -c 'fopen\|fread\|read_file' is also 17. This change adds no file reads. Happy to bump the constant in a separate commit if you'd like it brought back in line, but I left it alone to keep this PR scoped.

@DeusData DeusData added enhancement New feature or request ux/behavior Display bugs, docs, adoption UX priority/normal Standard review queue; useful PR with ordinary maintainer urgency. labels Jul 24, 2026
@DeusData DeusData added this to the 0.9.2-rc milestone Jul 24, 2026
@DeusData

Copy link
Copy Markdown
Owner

Thanks for the careful tests and docs. This needs an ADR-model decision before implementation approval. The current append mode is a non-idempotent read-concatenate-replace operation in the MCP handler, so a retry after a lost response duplicates content; it also bypasses the existing store-level section-update path and its 8,000-character bound. Leading newlines in the addition violate the advertised one-blank-line rule, and empty content can report success while appending zero bytes. If append semantics are accepted, the implementation will need store-layer atomicity, explicit retry/idempotency and size rules, plus tests for repeated requests, concurrent append, leading newlines, empty content, and response length fields. No need to revise until the maintainer direction is decided.

@DeusData

Copy link
Copy Markdown
Owner

Thanks for this, and sorry for the wait. Queued for review.

MERGEABLE with all 28 checks green, +205/-3 across three files.

An append mode for manage_adr is a sensible-sounding addition — ADR content is naturally accumulative and a read-modify-write from the client side is easy to get wrong. It does extend the MCP tool contract, so it will get a look on interface design as well as implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority/normal Standard review queue; useful PR with ordinary maintainer urgency. ux/behavior Display bugs, docs, adoption UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants