Problem / Goal
changes pulse shows the same entries on every run with no way to record that a change has been read and acted on. There is no feedback loop between seeing a change and marking it as acknowledged. After triaging an entry, users must remember mentally which items they have handled — re-running pulse resurfaced them in the same positions.
The service's goal is to help users act on important changes, not just view them. Without a way to record "I have seen and handled this," the tool stops at notification — exactly the problem the hypothesis says current tools already have.
Background
ChangeEntry in src/models.py does not include any reviewed/acknowledged field. FileStore in src/store.py has no method to update an existing entry.
Dependencies
None. This issue is self-contained.
Required changes
1. src/models.py
Add one optional field to ChangeEntry:
reviewed_at: Optional[datetime] = None
2. src/store.py
Add a method to FileStore:
def mark_reviewed(self, entry_id: str) -> bool:
"""Set reviewed_at to datetime.now on the ChangeEntry with the given id.
Returns True if the entry was found and updated, False if entry_id
does not exist in the store.
"""
The method should:
- Locate the entry by
id in the store's change feed.
- Set
reviewed_at to datetime.now(timezone.utc).
- Persist the updated store to disk.
- Return
True if found, False if not found.
Also update add_changes() upsert to preserve reviewed_at from the existing record if the incoming entry has reviewed_at = None.
3. src/cli.py
Add a new Typer command changes mark ENTRY_ID:
@app.command()
def mark(
entry_id: str = typer.Argument(..., help="Change entry ID to mark as reviewed"),
) -> None:
"""Mark a change entry as reviewed."""
- Call
store.mark_reviewed(entry_id).
- On success: print
Marked <entry_id> as reviewed.
- On failure (not found): print
Error: no entry with ID <entry_id>. and exit 1.
4. changes pulse filter (src/cli.py)
Add --include-reviewed flag to pulse():
include_reviewed: bool = typer.Option(
False, "--include-reviewed", help="Include entries already marked as reviewed"
)
When --include-reviewed is not set (default), filter out entries whose reviewed_at is not None before bucketing.
Design decisions settled
reviewed_at is a nullable datetime field — None means unreviewed. Using a datetime (not a bool) preserves when the review happened for future audit or expiry logic.
- There is no
unmark command in this issue. The decision to add one is deferred.
- Reviews persist across
changes sync. A new sync that upserts an existing entry (same id) must preserve reviewed_at. FileStore.add_changes() uses the entry id as the deduplication key — the upsert must merge reviewed_at from the existing record if the incoming entry has reviewed_at = None.
changes pulse hides reviewed entries by default. Users who want to see the full feed pass --include-reviewed.
- There is no bulk mark command in this issue.
changes mark operates on one entry at a time.
Success criteria
Files to change
src/models.py — add reviewed_at field to ChangeEntry
src/store.py — add mark_reviewed() method; update add_changes() upsert to preserve reviewed_at
src/cli.py — add changes mark command; add --include-reviewed flag to pulse()
tests/test_store.py — test mark_reviewed and add_changes upsert preservation
tests/test_cli.py — test changes mark command and pulse --include-reviewed filter
Not involved
src/render.py, src/enrich.py, src/priority.py, src/sync.py, src/summarize.py, src/github_client.py, data/, .github/
Problem / Goal
changes pulseshows the same entries on every run with no way to record that a change has been read and acted on. There is no feedback loop between seeing a change and marking it as acknowledged. After triaging an entry, users must remember mentally which items they have handled — re-running pulse resurfaced them in the same positions.The service's goal is to help users act on important changes, not just view them. Without a way to record "I have seen and handled this," the tool stops at notification — exactly the problem the hypothesis says current tools already have.
Background
ChangeEntryinsrc/models.pydoes not include any reviewed/acknowledged field.FileStoreinsrc/store.pyhas no method to update an existing entry.Dependencies
None. This issue is self-contained.
Required changes
1. src/models.py
Add one optional field to
ChangeEntry:2. src/store.py
Add a method to
FileStore:The method should:
idin the store's change feed.reviewed_attodatetime.now(timezone.utc).Trueif found,Falseif not found.Also update
add_changes()upsert to preservereviewed_atfrom the existing record if the incoming entry hasreviewed_at = None.3. src/cli.py
Add a new Typer command
changes mark ENTRY_ID:store.mark_reviewed(entry_id).Marked <entry_id> as reviewed.Error: no entry with ID <entry_id>.and exit 1.4. changes pulse filter (src/cli.py)
Add
--include-reviewedflag topulse():When
--include-reviewedis not set (default), filter out entries whosereviewed_atis not None before bucketing.Design decisions settled
reviewed_atis a nullable datetime field —Nonemeans unreviewed. Using a datetime (not a bool) preserves when the review happened for future audit or expiry logic.unmarkcommand in this issue. The decision to add one is deferred.changes sync. A new sync that upserts an existing entry (sameid) must preservereviewed_at.FileStore.add_changes()uses the entryidas the deduplication key — the upsert must mergereviewed_atfrom the existing record if the incoming entry hasreviewed_at = None.changes pulsehides reviewed entries by default. Users who want to see the full feed pass--include-reviewed.changes markoperates on one entry at a time.Success criteria
ChangeEntryhasreviewed_at: Optional[datetime] = Nonefield.FileStore.mark_reviewed(entry_id: str) -> boolis implemented and persists to disk.changes mark ENTRY_IDmarks the entry and prints confirmation; exits 1 with an error message if not found.changes pulseomits reviewed entries by default;--include-reviewedshows them.FileStore.add_changes()upsert preservesreviewed_atwhen re-adding an entry with the same id.pytest --tb=shortpasses with tests covering:mark_reviewedreturns True on valid id, returns False on unknown id,add_changesupsert preservesreviewed_at, andpulsestdout excludes reviewed entries without--include-reviewed.ruff check src/ tests/passes with no new errors.Files to change
src/models.py— addreviewed_atfield toChangeEntrysrc/store.py— addmark_reviewed()method; updateadd_changes()upsert to preservereviewed_atsrc/cli.py— addchanges markcommand; add--include-reviewedflag topulse()tests/test_store.py— testmark_reviewedandadd_changesupsert preservationtests/test_cli.py— testchanges markcommand andpulse --include-reviewedfilterNot involved
src/render.py,src/enrich.py,src/priority.py,src/sync.py,src/summarize.py,src/github_client.py,data/,.github/