Source document copied on 20 October 2026. Its dated evidence and limitations remain as written. Feedback and decisions can be given in chat.

Collector LSP and independent review, 20 October 2026

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.

Verdict

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.

What I ran

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.

LSP lookups

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.

What held

Findings

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.

Environment notes

Suggested dispositions

Repair pass, 20 October 2026

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.

Dispositions

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.

Correction to the review

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.

LSP lookups in this pass

These ran in order, after the sync:

Commands in this pass

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.

Examples regenerated

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.

Remaining

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.