Source document copied on 20 October 2026. Its dated evidence and limitations remain as written. Feedback and decisions can be given in chat.
Reviewed head: 2f207d594ce1d47776457ea58531cc8f16b4316e on altriasagent/astra-jr-seed-proposals, in the worktree /Users/toberton/orca/workspaces/JustAnother/astra-jr-seed-proposals. The branch moved to a00779df during the review. That commit changes only the JR overhaul docs and the source index; the collector, its tests, its guide and its examples are byte-identical at both heads, so every finding below applies to a00779df as well.
Reviewer process: Claude Haiku 5.5 (claude-haiku-5-5) as a dispatched Orca worker. The live process is PID 4988, claude --dangerously-skip-permissions --model claude-haiku-5-5 --effort xhigh, launched from a login zsh (PID 4582). I did not change any code, commit, push, or call a live service. This file is the only thing I wrote in the repository.
The collector keeps its core promises. It reads one selected file, copies the selected text exactly, labels the file's Git state honestly, sets seed eligibility to false, and has no write or service path. The three saved packets match their sources. Two findings are real enough to fix before anyone relies on the packets: the debrief account_of label is a caller's assertion presented as a fact, and line numbers drift on files that contain certain Unicode line separators. The remaining findings are narrower.
| Step | Result |
|---|---|
git rev-parse HEAD, branch and status |
2f207d59. Only the Astra JR files and the other reviewer's files were dirty; nothing under scripts/ or docs/memory-seed/ was. |
uv sync --locked at the root, before any LSP lookup |
Resolved 26, checked 24, exit 0. Interpreter is CPython 3.14.8, from .python-version. |
uv lock --check |
Exit 0. |
.venv/bin/python -B -m pytest -q -p no:cacheprovider scripts/tests/test_memory_candidates.py |
25 passed in 0.28s. I reran the builder's run rather than reuse it, because this is the head I am reviewing. |
ruff check and ruff format --check on both collector files |
All checks passed; 2 files already formatted. Ruff 0.16.10, the dev pin. |
pyright at the repository root |
0 errors, 0 warnings, 0 informations, exit 0. Pyright 1.1.414, the same pin as .github/workflows/python-types.yml. |
| Scratch repros (below) | Five edge cases run through the CLI. None touched the repository. |
Each lookup used the project's .venv after the sync. Positions are 1-based.
| Symbol and position | Operation | What came back |
|---|---|---|
collect, memory_candidates.py:130:5 |
findReferences | 130:5 and 239:18 (the CLI main). The test file's call sites were not listed; see the note below. |
collector.collect, test_memory_candidates.py:19:24 |
goToDefinition | memory_candidates.py:130:5. Module resolution through from scripts import memory_candidates as collector works. |
source_revision, :19:5 |
findReferences | 19:5 and 170:16 (inside collect). |
source_date, :41:5 |
findReferences | 41:5 and 162:17. |
section_range, :84:5 |
findReferences | 84:5 and 166:14. |
roster_range, :110:5 |
findReferences | 110:5 and 164:9. |
ROOT, :15:1 |
findReferences | 15:1 and 145:37. The test's monkeypatch.setattr(collector, "ROOT", tmp_path) at test line 189 was not listed. |
collect, :130:5 |
hover | Signature ends -> dict[Unknown, Unknown]. The return annotation is a bare dict. |
| documentSymbol | outline | Every function and its locals, including _lane at line 200. |
Diagnostics block. The lookups returned one Pyright hint, memory_candidates.py [Line 200:27] "_lane" is not accessed. It came attached to the lookup results, not to an edit, because I made no edits. The command-line pyright does not print hints, so it reports zero.
What the LSP could not show. Definitions across files work, and within-file references look right. Cross-module references do not: the reference search for collect and ROOT omitted the test file. The text search showed ten of the test file's direct collector.collect( lines before its result cap, and the file reads as seventeen direct calls. An incomplete reference list is not proof a symbol is unused, so for module-attribute uses the text search has to run alongside the LSP, as the procedure already says.
team.md lines 7 to 15 are the header, separator and seven rows. The seven candidates run 9 to 15 and match the Person and Role cells. The Main lane column, including the Messages routing for Chrissy, does not appear in any candidate. The test asserts that, and it passed.### Palette {Palette} is line 11 and ### Pop-up flyer is line 19, so the block is 11 to 18. The saved excerpt matches those lines character for character. The recorded date is line 3, Tuesday 11 August 2026, read as 2026-08-11. A second heading, ## 1. Palette {Palette} at line 46, is a narrative version of the same topic; the test exercises it, and both selections resolve to exactly one heading.working_tree: differs from or absent at git:1432494e marker is accurate: the fixture was untracked at that revision and was added in 2f207d59.git:1432494e. The team.md and minutes blobs are identical at 1432494e and at HEAD (5ebf0ff4 for team.md), so the saved marker is correct for those bytes. A fresh run today would say git:2f207d59. That is snapshot semantics, not a defect.argparse, json, re, subprocess, datetime, pathlib. The only subprocess is read-only git. The memory_writes: false in the return value is a literal, so it states the design rather than measuring anything.toberton_only (memory_candidates.py:191). team is accepted only for roster candidates (lines 147 to 148), and the test confirms it is refused for other kinds.Severity scale: Medium means a stated guarantee fails for some input. Low means a limitation, a silent scope difference, a fail-closed gap, or hygiene.
| ID | Severity | Where | Summary |
|---|---|---|---|
| F1 | Medium | memory_candidates.py:172 and :176 to :182 |
Debrief account_of is set from the flag alone and stated as fact. |
| F2 | Medium | memory_candidates.py:159 |
Line numbers drift on files containing U+2028, and other splitlines boundaries. |
| F3 | Low | memory_candidates.py:98 |
Indented ATX headings are not treated as boundaries. |
| F4 | Low | memory_candidates.py:98 |
Closing-hash headings such as ## Decision ## are not matched. |
| F5 | Low | memory_candidates.py:52 to :55 |
The date scan reads fenced content, so a quoted Date: can become the source date. |
| F6 | Low | memory_candidates.py:206 |
Team-audience candidates carry the absolute local path. |
| F7 | Low | memory_candidates.py:130 |
collect returns a bare dict. |
| F8 | Cosmetic | memory_candidates.py:200 |
_lane is unpacked and never used; LSP hint only. |
F1, the debrief attribution. The code at line 172 is "account_of": "Toberton" if kind == "debrief" else None, and the basis string at line 176 onward says "Toberton's account". Nothing checks who wrote the text. I ran a file that says "Ben said the door comp is final." through --kind debrief, and the packet came back with account_of: "Toberton" and that basis. The mentioned list, by contrast, carries mentioned_origin: "caller_supplied; not speaker identification", so the packet does mark one caller claim and leaves the other unmarked. The test at test_memory_candidates.py:134 asserts this behavior on purpose, which makes it a design question for Astra: either mark account_of as caller-supplied the same way, or name the field after what the flag actually establishes.
F2, the line numbers. content.decode("utf-8").splitlines(keepends=True) splits on more than newlines. Python's str.splitlines also breaks on U+2028, U+2029, U+0085, U+000B, U+000C and U+001C to U+001E. Repro: a file with foo, then U+2028, then bar, then ## Decision. grep -n gives line 3 for the heading and the collector gives line_start 4. The excerpt text stays exact, because keepends preserves the separator, but the stated line range is wrong. The tests cannot catch this, because they compute their expected lines with the same splitlines. The fix is to split on \n only and keep each terminator.
F3, indented headings. The heading regex at line 98 tests line, not stripped, so ## B (one leading space, valid ATX in CommonMark) is treated as body text. Repro: --section A on a file with ## A, x, ## B, y, ## C returns lines 1 to 4, which includes the ## B text, with no error. --section B exits 2 with "found 0". The first case is a silent over-inclusion. I did not survey the whole minutes corpus; the one minutes file I read has no indented headings.
F4, closing hashes. ## Decision ## captures Decision ## as the title. --section Decision exits 2 with "found 0". This fails closed, but it means the heading cannot be selected.
F5, dates inside fences. source_date scans every line of the file and does not track fences. Repro: a pasted message in a fence containing Date: 2026-08-15, with no header date. The result is origin: "recorded", line: 4, inside the quoted text. The line number is honest, but the packet does not say that the line is inside quoted material. A second, unfenced Date would fail closed with "multiple Date fields", which is the right outcome but an odd one to rely on.
F6, the absolute path. Each of the seven team-audience candidates in docs/memory-seed/examples/team.json carries "path": "/Users/toberton/orca/workspaces/JustAnother/astra-jr-seed-proposals/team.md". The block carries it too, which is fine inside a toberton_only packet. A team-audience proposal that moved to a team surface would disclose the local username and workspace path. A repository-relative path on the candidates would avoid that.
F7 and F8. collect returns a bare dict. Pyright shows dict[Unknown, Unknown], and standard mode does not report it. A TypedDict at the boundary would fit the procedure's third-party-typing guidance, or the bare type can be accepted as is. The _lane hint is cosmetic; the CLI is silent on it.
uv --version on this Mac reports 0.13.1 (Homebrew). The python-code-intelligence reference records 0.12.24 as the installed version, and says the record and CI pins move together. I did not read setup.json, and uv lock --check passed on 0.13.1, so this is worth a look by whoever owns the record..venv and .ruff_cache existed and were used as-is. I started no servers or background processes. The scratch repros are in /private/tmp/claude-501/-Users-toberton-orca-workspaces-JustAnother-astra-jr-seed-proposals/5122d735-36b2-4f6a-8e93-b2dce26b1cbc/scratchpad/repro/. The temporary pyright output I wrote one level above the worktree has been deleted.account_of as caller-supplied is a small change; renaming it is a bigger one.grep -n. The existing tests will not catch it.TypedDict in a later pass.This section records the repair dispatch that followed the review. It ran in the same checkout after uv sync --locked, starting from head a00779df1d4b65fb3301312cb3122afb4d1f9797. The repair commit that saves these files is the head reported to the coordinator. The tested tree is that head plus the changes described here.
| ID | Disposition | What changed | Evidence |
|---|---|---|---|
| F1 | Fixed | An explicit --kind debrief still records account_of: "Toberton", now with account_of_origin: "caller_supplied_not_verified". The basis says the holder is declared, not verified. |
test_debrief_kind_declares_the_account_holder_without_verifying_the_author; the attribution asserts in the transcript and debrief test; regenerated personal-debrief.json |
| F2 | Fixed | Lines split on LF only (split_lines). CRLF and every other byte stay in the excerpt. The U+2028 repro now reports lines 3 to 4, matching grep -n. |
test_line_numbers_count_lf_only_and_keep_crlf_bytes with literal 3 and 4. The real-file tests now use an independent oracle, lf_excerpt, which plain str.split drives, and literal line numbers. |
| F3 | Fixed | A heading may begin with up to three spaces. The indented repro now ends section A before ## B, and --section B selects it. |
test_atx_headings_allow_three_spaces_and_closing_hashes, literal spans |
| F4 | Fixed | A closing run of hashes is removed from the title, while C# and D# keep their hash. |
Same test, cases C and D# |
| F5 | Fixed | Headings and Date lines inside fences are ignored through one shared helper, prose_indexes. An explicit --date still wins. |
test_date_fields_inside_fenced_code_are_not_the_source_date and test_only_a_fenced_date_leaves_the_source_date_unknown |
| F6 | Accepted, no code change | Absolute provenance stays in the private packet. The guide now names it and says to remove it before a team-audience proposal goes anywhere else. | Guide, "Audience and attribution" |
| F7 | Accepted, no code change | collect still returns a bare dict. No typing refactor was made. |
Repository pyright, 0 errors |
| F8 | Accepted, no code change | _lane is still unpacked and unused. The LSP still shows the hint, and the command-line pyright stays silent. |
LSP diagnostics below |
The five scratchpad repros, re-run against the repaired code, now give these results. The debrief packet reports account_of Toberton with origin caller_supplied_not_verified. The U+2028 file reports lines 3 to 4 with excerpt ## Decision\ntext\n. Section A of the indented file reports lines 1 to 2, and section B reports lines 3 to 4 with excerpt ## B\ny\n. The closing-hash heading now matches Decision. The fenced quote's date is now unknown.
The review said the reference search missed the test file's call sites, and that the text search showed ten of the test file's collector.collect( lines against a count of seventeen. Both statements were wrong. The count came from my reading of the test file, and it was incomplete. The reference search now lists the definition, the main call at line 273, and 24 call sites in the test file. My best guess is that the server had not yet opened the test file when the first reference search ran. A definition lookup that opened the file happened earlier in this pass, and the same search then found all of them. I did not isolate that cause, so treat it as a likely explanation, not a verified one. The original sentences above stay as written for the audit trail.
These ran in order, after the sync:
section_range at memory_candidates.py:84:5 returned 84:5 and 166:14. source_date at 41:5 returned 41:5 and 162:17. Both match the review._lane not accessed, now at 234:27 because the new helpers moved it down. No new errors.prose_indexes at 32:5 returned the definition, 91:22 in source_date and 128:18 in section_range. split_lines at 19:5 returned the definition and 191:13 in collect. section_range at 121:5 returned 121:5 and 198:14 in collect.collector.collect at test lines 28:24 and 49:24 both resolved to memory_candidates.py:162:5.lf_excerpt at test 17:5 returned the definition plus 32:32 and 52:32.collect at 162:5 returned the definition, 273:18 and 24 test call sites from line 28 to line 319._lane hint above was reported.uv sync --locked: 26 resolved, 24 checked, exit 0..venv/bin/python -B -m pytest -q -p no:cacheprovider scripts/tests/test_memory_candidates.py: 30 passed in 0.28s. Twenty-five are the existing tests and five are new.ruff check on both Python files: all checks passed. ruff format --check on both: already formatted.pyright at the repository root: 0 errors, 0 warnings, 0 informations, exit 0.Not rerun: uv lock --check, because pyproject.toml, uv.lock and pyrightconfig.json are unchanged since the reviewed head. No other test file, because nothing else imports the collector. No live call of any kind.
The three examples were generated into the scratchpad first, checked as valid JSON, diffed, and then copied in. The differences from the saved versions are limited to three kinds of field. Revisions now read git:a00779df…. Every packet has a new account_of_origin field, which is null outside debriefs. The debrief basis wording changed. The debrief revision moved from a working-tree marker to a git revision, because the fixture is now tracked and its bytes match HEAD. Line ranges, excerpts and candidate lists did not change.
F1 through F5 are fixed. F6 through F8 are accepted with the notes above. The _lane hint stays, and the cross-module reference gap from the review was not reproduced in this pass.