feat(notes): add_note MCP tool with a per-day agent cap
Agents can write into the user's daily notes, but only within limits: max 10 agent notes a day, max 500 characters, add only — no edit, no delete. A new from_agent column separates their lines from the user's so the cap counts the right ones and the UI can mark them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cf093e3178
commit
ae0c7d3771
@@ -0,0 +1,77 @@
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.External;
|
||||
using ClaudeDo.Worker.Hub;
|
||||
using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.External;
|
||||
|
||||
public sealed class NotesMcpToolsTests : IDisposable
|
||||
{
|
||||
private readonly DbFixture _db = new();
|
||||
private readonly ClaudeDoDbContext _ctx;
|
||||
private readonly DailyNoteRepository _notes;
|
||||
private readonly NotesMcpTools _sut;
|
||||
private static readonly DateOnly Today = DateOnly.FromDateTime(DateTime.Today);
|
||||
|
||||
public NotesMcpToolsTests()
|
||||
{
|
||||
_ctx = _db.CreateContext();
|
||||
_notes = new DailyNoteRepository(_ctx);
|
||||
_sut = new NotesMcpTools(_notes, new HubBroadcaster(new CapturingHubContext()));
|
||||
}
|
||||
|
||||
public void Dispose() { _ctx.Dispose(); _db.Dispose(); }
|
||||
|
||||
[Fact]
|
||||
public async Task AddNote_stores_it_on_today_flagged_as_agent_written()
|
||||
{
|
||||
var result = await _sut.AddNote(" Mika decided to keep the daily grouping. ");
|
||||
|
||||
Assert.True(result.Saved);
|
||||
var row = Assert.Single(await _notes.ListByDayAsync(Today));
|
||||
Assert.Equal("Mika decided to keep the daily grouping.", row.Text);
|
||||
Assert.True(row.FromAgent);
|
||||
Assert.Equal(NotesMcpTools.DailyAgentCap - 1, result.RemainingToday);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddNote_refuses_once_the_daily_cap_is_used_up_and_saves_nothing()
|
||||
{
|
||||
for (var i = 0; i < NotesMcpTools.DailyAgentCap; i++)
|
||||
await _sut.AddNote($"note {i}");
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.AddNote("one too many"));
|
||||
|
||||
Assert.Contains("cap", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Equal(NotesMcpTools.DailyAgentCap, (await _notes.ListByDayAsync(Today)).Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Notes_the_user_wrote_do_not_count_against_the_agent_cap()
|
||||
{
|
||||
for (var i = 0; i < NotesMcpTools.DailyAgentCap; i++)
|
||||
await _notes.AddAsync(Today, $"mine {i}");
|
||||
|
||||
var result = await _sut.AddNote("still allowed");
|
||||
|
||||
Assert.Equal(1, result.AgentNotesToday);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public async Task AddNote_rejects_empty_text(string text) =>
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.AddNote(text));
|
||||
|
||||
[Fact]
|
||||
public async Task AddNote_rejects_text_past_the_length_limit()
|
||||
{
|
||||
var tooLong = new string('x', NotesMcpTools.MaxLength + 1);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.AddNote(tooLong));
|
||||
|
||||
Assert.Contains(NotesMcpTools.MaxLength.ToString(), ex.Message);
|
||||
Assert.Empty(await _notes.ListByDayAsync(Today));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user