From 914f31359ce885263b6b069429e2c2b6de9f07f1 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 21 Oct 2026 21:44:58 -0400 Subject: [PATCH] =?UTF-8?q?docs(6209):=20the=20remaining=20test=20files=20?= =?UTF-8?q?=E2=80=94=20lessons,=20reply=20moment=20and=20shape,=20retrieva?= =?UTF-8?q?l=20specs=20and=20scopes,=20rule=20doors=20and=20judgments,=20s?= =?UTF-8?q?ervices,=20snippets,=20search=20scope;=20five=20tests=20renamed?= =?UTF-8?q?=20to=20their=20property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings, section headers and assertion messages drop the record number, milestone or step that introduced the behaviour, and incident accounts are restated as the present-tense failure shape. A dead pointer to a design doc that is not in the repo is removed. Renamed to the property they check: test_the_old_shape_would_be_caught -> test_a_rank_then_scope_query_is_caught test_a_ledger_repeat_no_longer_produces_that_case_at_all -> test_a_ledger_repeat_is_counted_not_withheld test_the_header_no_longer_promises_once_per_session -> test_the_header_explains_the_seen_marker test_the_portal_case_no_longer_stamps_one_canon_onto_a_whole_file -> test_one_canon_is_not_stamped_onto_a_whole_file test_the_repos_own_prose_no_longer_defines_with -> test_the_repos_own_prose_does_not_define_with test_the_original_regression_create_note_with_content -> test_the_real_server_refuses_content_on_create_note Fixture and example ids stay: "note 5" in a supersession chain, "rule 156" in an illustrative question, `#2275` as the hex-versus-citation case, `#3244` as the example of a bare number, fixed dates in date-filter tests. Co-Authored-By: Claude Opus 5.5 --- tests/test_hook_duplicate_confirmation.py | 13 ++++++------- tests/test_ledger_references_not_silence.py | 5 +++-- tests/test_lesson_claim.py | 6 +++--- tests/test_lesson_convergence.py | 6 +++--- tests/test_lesson_rule_link_doors.py | 6 +++--- tests/test_lesson_rule_link_ui.py | 6 +++--- tests/test_lesson_rule_soft_links.py | 2 +- tests/test_list_rows_brief.py | 12 ++++++------ tests/test_matched_passage_surfaces.py | 8 ++++---- tests/test_mcp_list_family.py | 2 +- tests/test_mcp_strict_args.py | 2 +- tests/test_mcp_tool_create_records.py | 2 +- tests/test_mcp_tool_design_systems.py | 6 +++--- tests/test_mcp_tool_milestones.py | 2 +- tests/test_mcp_tool_planning.py | 5 ++--- tests/test_mcp_tool_processes.py | 4 ++-- tests/test_mcp_tool_report_back_cue.py | 7 +++---- tests/test_mcp_tool_snippets.py | 2 +- tests/test_mcp_tool_tasks_kind.py | 10 +++++----- tests/test_measure_duplication.py | 2 +- tests/test_moment_actions.py | 2 +- tests/test_moments.py | 6 +++--- tests/test_named_records.py | 14 +++++++------- tests/test_platforms.py | 2 +- tests/test_precompact_hook.py | 4 ++-- tests/test_records_are_named_not_numbered.py | 7 +++---- tests/test_recurrence.py | 11 +++++------ tests/test_reply_moment.py | 7 +++---- tests/test_reply_shape_turn.py | 4 ++-- tests/test_retrieval_migration.py | 4 ++-- tests/test_retrieval_review.py | 4 ++-- tests/test_retrieval_scopes.py | 8 ++++---- tests/test_retrieval_specs.py | 12 ++++++------ tests/test_routes_design_systems.py | 8 ++++---- tests/test_routes_family.py | 2 +- tests/test_routes_platforms.py | 2 +- tests/test_rule_creation_asks_first.py | 8 ++++---- tests/test_rule_moment_doors.py | 4 ++-- tests/test_rule_moment_judgments.py | 6 +++--- tests/test_rule_moment_signal_hooks.py | 4 ++-- tests/test_rule_overlap_gate.py | 2 +- tests/test_rule_trigger_required.py | 6 +++--- tests/test_rule_via_lesson.py | 10 +++++----- tests/test_scribe_marker.py | 9 ++++----- tests/test_search_route.py | 6 +++--- tests/test_search_scope_shape.py | 6 +++--- tests/test_services_access_visibility.py | 2 +- tests/test_services_canonical_systems.py | 5 ++--- tests/test_services_dashboard.py | 2 +- tests/test_services_design_systems.py | 2 +- tests/test_services_knowledge_counts.py | 16 +++++++--------- tests/test_services_notes_sweep.py | 6 +++--- tests/test_services_notes_verification.py | 4 ++-- tests/test_services_project_summaries.py | 16 ++++++++-------- tests/test_services_record_refs.py | 4 ++-- tests/test_services_report_check.py | 8 ++++---- tests/test_services_shape_check.py | 2 +- tests/test_services_systems.py | 4 ++-- tests/test_services_trash.py | 1 - tests/test_session_context_failure.py | 14 +++++++------- tests/test_shape_check_hook.py | 2 +- tests/test_shape_form_gate.py | 6 +++--- tests/test_shared_write_access.py | 2 +- tests/test_skill_moments.py | 2 +- tests/test_snippet_drift_check.py | 4 ++-- tests/test_snippet_merge_provenance.py | 4 ++-- tests/test_snippet_notes.py | 12 ++++++------ tests/test_snippet_unmerge.py | 6 +++--- tests/test_system_rulings.py | 2 +- tests/test_verification_guidance_survives.py | 6 +++--- tests/test_version_pinning_prune.py | 2 -- tests/test_write_path_design_arm.py | 8 ++++---- tests/test_write_path_trigger.py | 9 +++++---- 73 files changed, 203 insertions(+), 214 deletions(-) diff --git a/tests/test_hook_duplicate_confirmation.py b/tests/test_hook_duplicate_confirmation.py index 13b96af1..e81b3418 100644 --- a/tests/test_hook_duplicate_confirmation.py +++ b/tests/test_hook_duplicate_confirmation.py @@ -99,14 +99,13 @@ def test_a_sentence_about_a_definition_is_not_a_definition(repo): assert "in 1 other file(s)" in out -def test_the_repos_own_prose_no_longer_defines_with(): - """Not a fixture — this repo's actual corpus, which is where the defect - was found. +def test_the_repos_own_prose_does_not_define_with(): + """Not a fixture — this repo's actual corpus, the realistic case. - Before the confirmation pass the arm named four files for `with`: - `…record type with no vector…`, `A class with no rules anywhere…` and two - more, all of them prose in docstrings and comments. It now names none, - and none is the correct answer: nothing here defines a symbol called + Without the confirmation pass the arm names files for `with` on prose in + docstrings and comments — `…record type with no vector…`, `A class with + no rules anywhere…`. With it the arm names none, and none is the correct + answer: nothing here defines a symbol called `with`, and in several of the languages in this tree it could not, because it is a keyword. diff --git a/tests/test_ledger_references_not_silence.py b/tests/test_ledger_references_not_silence.py index 8fcca1ce..610f1439 100644 --- a/tests/test_ledger_references_not_silence.py +++ b/tests/test_ledger_references_not_silence.py @@ -150,8 +150,9 @@ async def test_a_repeat_is_not_recorded_as_a_fresh_surfacing(): @pytest.mark.asyncio -async def test_the_header_no_longer_promises_once_per_session(): - """A contract stated in the prose is a contract, and this one changed. +async def test_the_header_explains_the_seen_marker(): + """A contract stated in the prose is a contract: the header explains the + `seen` marker and does not promise a record once per session. Cheap to forget and invisible when wrong: the menu would carry a marker the header had never explained, and a reader would have to guess. diff --git a/tests/test_lesson_claim.py b/tests/test_lesson_claim.py index 73e093b0..756f2cc3 100644 --- a/tests/test_lesson_claim.py +++ b/tests/test_lesson_claim.py @@ -1,8 +1,8 @@ -"""A lesson's name is one claim on one line (#4797). +"""A lesson's name is one claim on one line. `what` is the title every listing and menu prints. Written as the incident -instead of the claim, it turned a menu line into 1,416 characters with the -claim somewhere in the middle. Two halves are pinned: the doors refuse a new +instead of the claim, it turns a menu line into a paragraph with the claim +somewhere in the middle. Two halves are pinned: the doors refuse a new one, and the menu shows one line for a name stored before they did. """ from __future__ import annotations diff --git a/tests/test_lesson_convergence.py b/tests/test_lesson_convergence.py index a718ab3c..3e6bb297 100644 --- a/tests/test_lesson_convergence.py +++ b/tests/test_lesson_convergence.py @@ -1,4 +1,4 @@ -"""Convergence named at the write (milestone 440, #4634). +"""Convergence named at the write. When a lesson is answered "no rule fits", the response looks for other no-rule lessons in the same situation and, once there are enough, names the @@ -53,7 +53,7 @@ def test_one_incident_written_up_three_times_is_not_a_recurring_situation(): assert links_svc.convergence_group(same) is None -# ── The bar is relative to each lesson's own register (#5193) ────────────── +# ── The bar is relative to each lesson's own register ────────────────────── def test_too_little_background_says_nothing_stands_out(): @@ -138,7 +138,7 @@ async def test_lessons_that_stand_out_together_are_named(): @pytest.mark.asyncio async def test_a_large_no_rule_pool_in_one_flat_band_names_nothing(): - """The #5193 failure: every lesson answered "no rule", every score above + """The flat-band failure: every lesson answered "no rule", every score above a fixed bar, and a group the size of the pool. Nothing stands out of a flat band, so nothing is named however large the pool grows.""" a, b, c = _run(no_rule=set(_NOTES), sim=lambda x, y: 0.66 + 0.01 * ((x + y) % 5)) diff --git a/tests/test_lesson_rule_link_doors.py b/tests/test_lesson_rule_link_doors.py index 96ec27a2..5f63e2eb 100644 --- a/tests/test_lesson_rule_link_doors.py +++ b/tests/test_lesson_rule_link_doors.py @@ -1,4 +1,4 @@ -"""The lesson → rule link at its doors (milestone 440 step 1, #4630). +"""The lesson → rule link at its doors. The link's own behaviour — states, cascade, ACL — is tested against Postgres in tests/test_integration_lesson_rule_links.py. These pin the wiring a mock CAN @@ -112,7 +112,7 @@ def test_verdicts_name_real_states(): def test_the_migration_check_and_the_model_agree_on_the_states(): - """Rule 36's drift, guarded: the CHECK is written from the migration's + """Model/migration drift, guarded: the CHECK is written from the migration's tuple and the code from the model's, so they must be the same tuple.""" path = Path(__file__).resolve().parents[1] / "alembic" / "versions" / "0111_lesson_rule_links.py" spec = importlib.util.spec_from_file_location("m0111", path) @@ -145,7 +145,7 @@ def test_backup_keeps_an_unjudged_link_unjudged(): assert built.judged_at is None -# ── #4631: judged when written ─────────────────────────────────────────────── +# ── judged when written ────────────────────────────────────────────────────── @pytest.mark.asyncio diff --git a/tests/test_lesson_rule_link_ui.py b/tests/test_lesson_rule_link_ui.py index e3c2949f..bba55bd1 100644 --- a/tests/test_lesson_rule_link_ui.py +++ b/tests/test_lesson_rule_link_ui.py @@ -1,4 +1,4 @@ -"""Both records show the lesson → rule link in the web UI (milestone 440, #4635). +"""Both records show the lesson → rule link in the web UI. The frontend has no component test runner, so these read the source — and each is written against something the backend owns, so a drift on either @@ -122,7 +122,7 @@ def test_no_page_spells_its_own_write_check(): assert offenders == [] -# ── #4658: the web editor records the answer ───────────────────────────────── +# ── the web editor records the answer ──────────────────────────────────────── def _payload_fn(view: str) -> str: @@ -131,7 +131,7 @@ def _payload_fn(view: str) -> str: def test_the_editor_sends_the_answer_under_the_names_both_routes_read(): - """Rule 33's contract check: a field the form sends under another name is + """The contract check: a field the form sends under another name is dropped without an error, and the lesson saves unjudged.""" from scribe.routes import lessons as routes diff --git a/tests/test_lesson_rule_soft_links.py b/tests/test_lesson_rule_soft_links.py index 5dc0c650..50f840ad 100644 --- a/tests/test_lesson_rule_soft_links.py +++ b/tests/test_lesson_rule_soft_links.py @@ -1,4 +1,4 @@ -"""Soft links — the counting rules (milestone 440, #4637). +"""Soft links — the counting rules. What a suggested link's evidence counts is a design decision with a failure mode on each side: count every arrival and two similar records "prove" a diff --git a/tests/test_list_rows_brief.py b/tests/test_list_rows_brief.py index faab13e7..108b2be4 100644 --- a/tests/test_list_rows_brief.py +++ b/tests/test_list_rows_brief.py @@ -1,9 +1,9 @@ -"""List tools return rows that say what a record is, never what it says (#4061). +"""List tools return rows that say what a record is, never what it says. -list_tasks once returned every row's to_dict(), body included. A project's todo -list came to 93-165k characters, past what an MCP client accepts as a tool -result, so the list arrived as a file to page through — the failure #4045 fixed -for enter_project, one call over. A list is for choosing which record to open; +A list that returns every row's to_dict(), body included, runs a project's todo +list to 93-165k characters, past what an MCP client accepts as a tool result, +so the list arrives as a file to page through — the same failure enter_project +avoids by carrying a summary. A list is for choosing which record to open; get_task / get_note read one in full. """ import json @@ -41,7 +41,7 @@ def test_a_task_row_names_what_it_is_and_where_it_sits_but_not_its_body(): assert row["milestone_title"] == "An existing plan is found" assert {"id", "title", "status", "task_kind", "priority", "milestone_id", "tags", "updated_at", "project_id"} <= set(row) - # Empty optional fields are left off rather than sent as null (#2483). + # Empty optional fields are left off rather than sent as null. assert not {"description", "parent_id", "due_date"} & set(row) assert row["updated_at"] == WHEN.isoformat() diff --git a/tests/test_matched_passage_surfaces.py b/tests/test_matched_passage_surfaces.py index cf628f8a..991449e4 100644 --- a/tests/test_matched_passage_surfaces.py +++ b/tests/test_matched_passage_surfaces.py @@ -1,8 +1,8 @@ -"""Every semantic search hands on the passage that matched (#4243, #4250). +"""Every semantic search hands on the passage that matched. Three searches collapse chunk rows to the best one per record, so each of them -KNOWS which passage earned the hit. Each used to drop it, leaving every door to -preview the head of the document instead — a span the search had already scored +KNOWS which passage earned the hit. A search that drops it leaves every door to +preview the head of the document instead — a span the search has already scored lower, with nothing saying so. These pin the mechanism (`report["best_chunk"]` from all three searches) and @@ -26,7 +26,7 @@ from tests.helpers import make_mock_session def _searching(rows): """A patched session whose one query returns `rows` — the shared - make_mock_session (#2834) rather than a third local copy of the + make_mock_session rather than a third local copy of the __aenter__/__aexit__ dance.""" session = make_mock_session() result = MagicMock() diff --git a/tests/test_mcp_list_family.py b/tests/test_mcp_list_family.py index 352843bc..29b80218 100644 --- a/tests/test_mcp_list_family.py +++ b/tests/test_mcp_list_family.py @@ -1,4 +1,4 @@ -"""Cross-family contracts for the `list_*` MCP tools (#2278, shape 4). +"""Cross-family contracts for the `list_*` MCP tools. Per-tool tests cover what each list tool does. Nothing covered what the FAMILY owes its callers, which is where the missing-sibling shape hides: a capability diff --git a/tests/test_mcp_strict_args.py b/tests/test_mcp_strict_args.py index 7ecdc741..fcb2cb94 100644 --- a/tests/test_mcp_strict_args.py +++ b/tests/test_mcp_strict_args.py @@ -110,7 +110,7 @@ async def test_a_real_tools_refusal_reaches_the_caller(): assert "at least one verdict" in str(exc.value) -async def test_the_original_regression_create_note_with_content(): +async def test_the_real_server_refuses_content_on_create_note(): """Against the REAL server: `content=` on create_note must raise before dispatch — naming the bad argument and listing `body` among the accepted ones — instead of creating a body-less note.""" diff --git a/tests/test_mcp_tool_create_records.py b/tests/test_mcp_tool_create_records.py index 90076b64..6ed34674 100644 --- a/tests/test_mcp_tool_create_records.py +++ b/tests/test_mcp_tool_create_records.py @@ -1,4 +1,4 @@ -"""The batch create door, and the guessed-id refusal at every create/update door (#4016). +"""The batch create door, and the guessed-id refusal at every create/update door. What the unit lane can pin: the door refuses a guessed id BEFORE the duplicate gate or any write, a batch is refused whole, and a malformed record fails with diff --git a/tests/test_mcp_tool_design_systems.py b/tests/test_mcp_tool_design_systems.py index f4dc92f5..2e15dab1 100644 --- a/tests/test_mcp_tool_design_systems.py +++ b/tests/test_mcp_tool_design_systems.py @@ -181,8 +181,8 @@ async def test_resolve_returns_serialised_tokens_with_their_provenance(): @pytest.mark.asyncio async def test_update_token_can_clear_supersedes_with_an_empty_list(): """`[]` means "this token replaces nothing after all" — a real edit. Guarded - on `is not None` so it isn't swallowed as "unchanged", the same trap #2077 - recorded for update_snippet.""" + on `is not None` so it isn't swallowed as "unchanged", the same trap + update_snippet guards against.""" with patch("scribe.mcp.tools.design_systems.ds_svc") as svc: svc.update_token = AsyncMock(return_value=_fake_token()) from scribe.mcp.tools.design_systems import update_design_token @@ -203,7 +203,7 @@ async def test_update_token_leaves_supersedes_alone_when_omitted(): @pytest.mark.asyncio async def test_get_design_system_carries_the_chain_merged_guidance(): - """enter_project carries only a summary and points here (#4045). The + """enter_project carries only a summary and points here. The system's own `guidance` field is only its departures; a session building UI needs the house style too, so the merged form rides alongside.""" from scribe.mcp.tools.design_systems import get_design_system diff --git a/tests/test_mcp_tool_milestones.py b/tests/test_mcp_tool_milestones.py index 19a4b070..7284a8d9 100644 --- a/tests/test_mcp_tool_milestones.py +++ b/tests/test_mcp_tool_milestones.py @@ -126,7 +126,7 @@ async def test_get_milestone_returns_body_steps_and_rules(): assert out["milestone"]["body"] == "## Goal" assert out["milestone"]["total"] == 1 assert [(s["id"], s["title"], s["status"]) for s in out["steps"]] == [(9, "step 1", "todo")] - # A step's body is get_task's job (#4061); its placement is not. + # A step's body is get_task's job; its placement is not. assert "body" not in out["steps"][0] assert out["steps"][0]["milestone_title"] == m.title assert out["applicable_rules"] == [{"id": 1, "title": "r"}] diff --git a/tests/test_mcp_tool_planning.py b/tests/test_mcp_tool_planning.py index b0bf4670..eec7aff2 100644 --- a/tests/test_mcp_tool_planning.py +++ b/tests/test_mcp_tool_planning.py @@ -32,9 +32,8 @@ async def test_start_planning_tool_delegates_to_service(): @pytest.mark.asyncio async def test_start_planning_returns_the_plan_that_already_covers_it(): - """The FabledLibrarian failure (milestone 415): a session that could not see - the existing plan made a second one. Now it is handed the first, and - nothing is created.""" + """A session that cannot see the existing plan makes a second one. It is + handed the first instead, and nothing is created.""" match = {"duplicate": True, "existing_id": 12} svc = AsyncMock() with patch("scribe.mcp.tools.tasks.dedup_svc.plan_gate", diff --git a/tests/test_mcp_tool_processes.py b/tests/test_mcp_tool_processes.py index 5a8f421e..60429e9e 100644 --- a/tests/test_mcp_tool_processes.py +++ b/tests/test_mcp_tool_processes.py @@ -36,7 +36,7 @@ async def test_create_process_sets_note_type(): async def test_create_process_blocks_a_near_duplicate(): """The gate matters more for processes than for other kinds: each one becomes a skill file that auto-surfaces, so two near-identical procedures don't just - bloat the corpus — they compete to be followed (#2250).""" + bloat the corpus — they compete to be followed.""" from scribe.services.dedup import DuplicateMatch # The real dataclass, not a MagicMock: a mock answers every attribute, so it @@ -166,7 +166,7 @@ async def test_delete_process_refuses_a_plain_note(): mock_delete.assert_not_awaited() -# ── the moments a procedure is for (milestone 458 step 5) ──────────────── +# ── the moments a procedure is for ─────────────────────────────────────── @pytest.mark.asyncio async def test_create_process_stores_the_moments_it_is_for(): diff --git a/tests/test_mcp_tool_report_back_cue.py b/tests/test_mcp_tool_report_back_cue.py index feee6511..52843be4 100644 --- a/tests/test_mcp_tool_report_back_cue.py +++ b/tests/test_mcp_tool_report_back_cue.py @@ -1,11 +1,10 @@ """update_task closes a task with a one-line reminder of what the report needs. -The in-band half of milestone 409 step 3: the skill and static context only +This is the in-band half of reporting back: the skill and static context only exist in the Claude Code plugin, and a tool response reaches every MCP client at the moment a piece of work closes. Pinned on the response, not the wording. -The operator's own completion-report preferences used to ride here too, from -a fixed-question search (409 step 4). Milestone 500 retired that: they are +The operator's own completion-report preferences do not ride here: they are mounted on finishing work and arrive in `moment_rules` beside the delivered reply shape, so this response carries no key of its own for them. """ @@ -55,7 +54,7 @@ _OWED = {"idea_id": 9, "idea_title": "Signed APK lane", "project_id": 4, async def test_closing_names_the_owed_adoptions_filed_while_the_task_was_open(): - """The finishing moment of milestone 463 step 6: owed family work filed + """The finishing moment of family canon: owed family work filed into a project during this task comes back for the report to name.""" from scribe.services.family_adoption import OWED_CUE diff --git a/tests/test_mcp_tool_snippets.py b/tests/test_mcp_tool_snippets.py index c7bb5b54..ce15ce79 100644 --- a/tests/test_mcp_tool_snippets.py +++ b/tests/test_mcp_tool_snippets.py @@ -141,7 +141,7 @@ async def test_delete_snippet_retires_or_raises(): doomed = MagicMock() doomed.title = "debounce helper" - # The confirmation names what went (#3273) — after the delete there is + # The confirmation names what went — after the delete there is # nothing left to look the title up from. with patch("scribe.services.snippets.delete_snippet", AsyncMock(return_value=True)), \ patch("scribe.services.snippets.get_snippet", AsyncMock(return_value=doomed)): diff --git a/tests/test_mcp_tool_tasks_kind.py b/tests/test_mcp_tool_tasks_kind.py index 06c735ef..71037517 100644 --- a/tests/test_mcp_tool_tasks_kind.py +++ b/tests/test_mcp_tool_tasks_kind.py @@ -37,7 +37,7 @@ async def test_list_tasks_kind_empty_means_no_filter(): @pytest.mark.asyncio async def test_create_task_passes_spike(): - """The kind a failed rule-check asks for (milestone 312). + """The kind a failed rule-check asks for. Time-boxed, and its output is knowledge rather than a change — filing one as `work` makes a finished investigation look like an abandoned change. @@ -51,12 +51,12 @@ async def test_create_task_passes_spike(): @pytest.mark.asyncio async def test_update_task_can_re_file_a_kind(): - """A task's kind must be CORRECTABLE, not write-once (#3129). + """A task's kind must be CORRECTABLE, not write-once. What a piece of work turns out to be is often clear only once it is under - way. Before this, `update_task` had no `kind` parameter at all and the - REST route dropped the field the editor was already sending — the save - reported success and reverted on reload. + way. Without a `kind` parameter on `update_task`, and with a REST route that + drops the field the editor sends, the save reports success and reverts on + reload. """ mock = AsyncMock(return_value=fake_note(task_kind="spike")) with patch("scribe.mcp.tools.tasks.notes_svc.update_note", mock): diff --git a/tests/test_measure_duplication.py b/tests/test_measure_duplication.py index e392ff34..32a4cf42 100644 --- a/tests/test_measure_duplication.py +++ b/tests/test_measure_duplication.py @@ -1,4 +1,4 @@ -"""The DRY close-out measure (#4745). +"""The DRY close-out measure. Stdlib-only and kept in scripts/ so any project can run a scratch copy of it, so these tests import it by path rather than as a package. diff --git a/tests/test_moment_actions.py b/tests/test_moment_actions.py index 60dd7db4..1b7b89f2 100644 --- a/tests/test_moment_actions.py +++ b/tests/test_moment_actions.py @@ -1,4 +1,4 @@ -"""Which actions reach which moment (milestone 458 step 2) — the pure half. +"""Which actions reach which moment — the pure half. Matching and resolution take no database: the defaults are code and an install's mappings arrive as rows, so every case below hands `resolve` the diff --git a/tests/test_moments.py b/tests/test_moments.py index 92466e27..21123a52 100644 --- a/tests/test_moments.py +++ b/tests/test_moments.py @@ -1,4 +1,4 @@ -"""The moment catalog — the vocabulary rules mount on (milestone 458 step 1). +"""The moment catalog — the vocabulary rules mount on. What this pins is what a mount depends on: every name is well-formed and resolvable, an unknown name is refused rather than stored, the catalog speaks @@ -16,7 +16,7 @@ _NAME = re.compile(r"^[a-z]+\.[a-z]+$") def test_the_catalog_is_not_empty(): - """The sweeps below are vacuous over an empty catalog (rule 167).""" + """The sweeps below are vacuous over an empty catalog.""" assert len(moments.MOMENTS) >= 10 @@ -120,7 +120,7 @@ def test_the_tools_are_registered_and_classified(): def test_both_doors_read_one_catalog(): - """Rule 33 parity: the tool and the routes call the same services, so the + """Door parity: the tool and the routes call the same services, so the session and the Settings view cannot name different moments or disagree about what a mapping does.""" from scribe.mcp.tools import moments as tool diff --git a/tests/test_named_records.py b/tests/test_named_records.py index cade3b5f..2837a746 100644 --- a/tests/test_named_records.py +++ b/tests/test_named_records.py @@ -1,9 +1,9 @@ -"""A record the operator names by number is looked up, not left to ranking (#4796). +"""A record the operator names by number is looked up, not left to ranking. -"yes go ahead with 4448" says exactly which record is meant, and the prompt -menu could not use it: a number means nothing to an embedding, so the menu -filled with whatever resembled the words around it. The parser cases below are -drawn from real operator prompts, and the half that must NOT be read as a +"yes go ahead with 4448" says exactly which record is meant, and a menu +built by ranking cannot use it: a number means nothing to an embedding, so the +menu fills with whatever resembles the words around it. The parser cases below +are drawn from real operator prompts, and the half that must NOT be read as a reference matters as much as the half that must: every misread number is a stranger's record dropped in front of the reader. """ @@ -181,8 +181,8 @@ async def test_an_id_the_reader_cannot_open_is_dropped(): @pytest.mark.asyncio async def test_a_number_in_the_assistants_reply_is_not_named(): - """The query is enriched with the reply before a short prompt is searched - (#4364); only the operator's own words can name a record.""" + """The query is enriched with the reply before a short prompt is searched; + only the operator's own words can name a record.""" rec = _task(4448, "the named one") out, _, _ = await _hint( "sounds good", records={4448: rec}, diff --git a/tests/test_platforms.py b/tests/test_platforms.py index 71ecafc2..c9213c2f 100644 --- a/tests/test_platforms.py +++ b/tests/test_platforms.py @@ -1,4 +1,4 @@ -"""Platforms without a database (milestone 463 step 2). +"""Platforms without a database. Detection is pure — a catalog and a list of repo paths in, platform ids out — so it is pinned here against fixture trees. What it writes, and what it must diff --git a/tests/test_precompact_hook.py b/tests/test_precompact_hook.py index 620bc6c6..0d47963a 100644 --- a/tests/test_precompact_hook.py +++ b/tests/test_precompact_hook.py @@ -1,4 +1,4 @@ -"""The PreCompact hook steers the summary and never blocks the compaction (#3680). +"""The PreCompact hook steers the summary and never blocks the compaction. The mechanism this pins was read out of the installed Claude Code build, not out of the documentation, which describes a different one. Three facts decide @@ -49,7 +49,7 @@ EVENT = {"session_id": "s1", "transcript_path": "/tmp/t.jsonl", "cwd": "/repo", def _run(event: dict, tmpdir: str | None = None) -> subprocess.CompletedProcess: """Run the hook with its ledger directory ISOLATED. - The hook reads this session's rule ledgers since #4216, and they live under + The hook reads this session's rule ledgers, and they live under `$TMPDIR/scribe-priorart`. Without an override these tests would read the machine's real /tmp: on a developer box mid-session that is not empty, and the output would depend on what some other session happened to leave diff --git a/tests/test_records_are_named_not_numbered.py b/tests/test_records_are_named_not_numbered.py index 08ecdd5e..c666c8c2 100644 --- a/tests/test_records_are_named_not_numbered.py +++ b/tests/test_records_are_named_not_numbered.py @@ -2,11 +2,10 @@ THE PROBLEM THIS IS ABOUT. The agent has the record open; the operator does not. `#3244` reads as complete to the writer and as homework to the reader, -who has to look it up to know what their own conversation is about. The -operator's words: *"I don't know what a note, task, or milestone is by its ID -number."* +who has to look it up to know what their own conversation is about. Nobody +knows what a note, task or milestone is by its id number. -PRODUCT, NOT A RULE (rule 119). Every Scribe user hits this, so the fix is in +PRODUCT, NOT A RULE. Every Scribe user hits this, so the fix is in the surfaces the product ships — the skill that shapes how an agent writes, and the tool responses that hand a record back. A per-instance rule would fix it for one operator and leave the behaviour wrong for everyone else. diff --git a/tests/test_recurrence.py b/tests/test_recurrence.py index 2a26aca5..fb5ad87b 100644 --- a/tests/test_recurrence.py +++ b/tests/test_recurrence.py @@ -1,11 +1,10 @@ """Tests for task lifecycle timestamps and recurrence logic. -The note stand-ins come from `fake_note`, not a bare MagicMock. These four -predated that helper and broke the moment `update_note` started reading a -column they did not set (milestone 317): a fresh MagicMock is truthy on every -attribute, so `verify_with` read as "this record carries a check" and the -guard refused the write. That is note 2109's lesson, and the reason fake_note -exists — a stand-in has to be able to say NO. +The note stand-ins come from `fake_note`, not a bare MagicMock. A fresh +MagicMock is truthy on every attribute, so once `update_note` reads a column +the stand-in did not set, `verify_with` reads as "this record carries a check" +and the guard refuses the write. That is the reason fake_note exists — a +stand-in has to be able to say NO. """ from datetime import date, datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/test_reply_moment.py b/tests/test_reply_moment.py index 008ce820..4eee46c5 100644 --- a/tests/test_reply_moment.py +++ b/tests/test_reply_moment.py @@ -1,7 +1,6 @@ -"""The reply moment: the finished reply, checked before it goes out (milestone -458 step 4, folded in from milestone 456 step 8). +"""The reply moment: the finished reply, checked before it goes out. -The operator's ruling: an unopened rule above the stop bar HOLDS the reply +An unopened rule above the stop bar HOLDS the reply once, in the server's words — the backstop for whatever the earlier arms missed — and the rewrite is never held. These pin, with the database stubbed: - which moments a reply reaches; @@ -228,7 +227,7 @@ async def test_a_turn_that_closed_nothing_is_not_section_checked(): async def test_a_closing_turn_is_section_checked_and_both_holds_fold_into_one_reason(): - """One end-of-turn request (milestone 500 step 4): the section check and + """One end-of-turn request: the section check and the rule hold answer together, in one `reason`.""" body, _hold, check = await _reply_route( {"reply": "done", "closed": 1, "closed_task_ids": [41, True, "x"], "rewrite": False}, diff --git a/tests/test_reply_shape_turn.py b/tests/test_reply_shape_turn.py index 5575130d..9bd7a06c 100644 --- a/tests/test_reply_shape_turn.py +++ b/tests/test_reply_shape_turn.py @@ -1,4 +1,4 @@ -"""The core reply shape rides every turn (milestone 500 step 3). +"""The core reply shape rides every turn. Every turn ends in a reply, and the operator's prompt is the last point before it is written, so the per-turn retrieval carries the core shape ahead of @@ -119,7 +119,7 @@ def test_the_core_goes_whole_once_then_as_a_reminder_then_whole_after_a_compacti def test_the_hook_and_the_route_agree_on_the_ledgers_names(): - """Rule 33 across the seam: a renamed field fails silently.""" + """A contract across the seam: a renamed field fails silently.""" from scribe.routes import plugin as routes hook = HOOK.read_text() diff --git a/tests/test_retrieval_migration.py b/tests/test_retrieval_migration.py index cc41aa48..14bf60b6 100644 --- a/tests/test_retrieval_migration.py +++ b/tests/test_retrieval_migration.py @@ -1,4 +1,4 @@ -"""Carrying a floor across a calibration change (#4104). +"""Carrying a floor across a calibration change. WHAT THIS PINS @@ -15,7 +15,7 @@ WHAT THIS PINS mid-backfill would end up with every bar at zero. 4. **Every registry surface can be migrated.** A seventh arm that nobody adds a re-scorer for is one whose floor silently cannot survive a model change. - 5. **A half-migrated corpus is a refusal (#4132).** While any row the surface + 5. **A half-migrated corpus is a refusal.** While any row the surface searches is stamped with another model, a re-score measures a blend of two geometries — so nothing is sampled until the backfill has finished. """ diff --git a/tests/test_retrieval_review.py b/tests/test_retrieval_review.py index dae1e698..d3ea1f1c 100644 --- a/tests/test_retrieval_review.py +++ b/tests/test_retrieval_review.py @@ -1,4 +1,4 @@ -"""The review pass: judging whether an injected line RELATED (#4772). +"""The review pass: judging whether an injected line RELATED. Unit tests pin the refusals and the re-run's fidelity to the arm it replays. Integration tests pin the parts a mock would make true by construction: which @@ -60,7 +60,7 @@ async def test_the_re_run_searches_the_way_the_arm_does(): a menu nobody was shown. Both searches are RUN and their keywords compared, so a change to the arm's spec fails here until the review follows it — and the arm has to have filters for the check to mean - anything (rule 167).""" + anything.""" from scribe.services import retrieval_pipeline as rp arm_kw: dict = {} diff --git a/tests/test_retrieval_scopes.py b/tests/test_retrieval_scopes.py index 242f0d73..52906e66 100644 --- a/tests/test_retrieval_scopes.py +++ b/tests/test_retrieval_scopes.py @@ -167,8 +167,8 @@ async def test_injected_menu_labels_the_record_kind(): assert "[snippet]" in by_id[1] assert "[process]" in by_id[2] # Task-ness wins over note_type, and an issue says so rather than "task". - # A task also carries its status (#4154): #4 is DONE, and a line that said - # only "[task]" is what let a finished step be cited as an open one. + # A task also carries its status: #4 is DONE, and a line that says only + # "[task]" lets a finished step be cited as an open one. assert "[issue (todo)]" in by_id[3] assert "[task (done)]" in by_id[4] assert "[note]" in by_id[5] @@ -181,7 +181,7 @@ async def test_injected_menu_labels_the_record_kind(): def test_list_shaped_services_use_the_shared_visibility_clause(): """Every service that LISTS notes must declare its reach through notes_visibility_clause / browsable_notes_clause — never a bare owner - filter (#47, #2462). + filter. Source inspection, because this is the shape no behavioural test catches: an owner-only list returns correct-looking rows and simply omits the shared @@ -204,7 +204,7 @@ def test_list_shaped_services_use_the_shared_visibility_clause(): or "browsable_notes_clause" in source ), ( f"{label} does not reference a shared visibility clause — a bare " - f"owner filter silently hides shared-project records (#2462)" + f"owner filter silently hides shared-project records" ) # And it must not ALSO carry a bare owner filter on the main query, # which would quietly re-narrow whatever the clause granted. diff --git a/tests/test_retrieval_specs.py b/tests/test_retrieval_specs.py index 75b3f9d9..d82af19d 100644 --- a/tests/test_retrieval_specs.py +++ b/tests/test_retrieval_specs.py @@ -1,8 +1,8 @@ -"""The specs are the registry (milestone 456 step 5). +"""The specs are the registry. -Three lists used to be kept by hand beside the arms — the tuning table, the -ranked rows of the telemetry registry, and the rule-usage denominator — with -tests to make them agree. They are now read off the pipeline's specs. These +Three lists sit beside the arms — the tuning table, the ranked rows of the +telemetry registry, and the rule-usage denominator — and all three are read +off the pipeline's specs rather than kept by hand. These pin the derivation itself: every arm is tunable, every ranked source is measured with the declaration its spec carries, and the rule-usage denominator counts exactly the rule surfaces a ranker chose. @@ -34,7 +34,7 @@ def test_every_ranked_source_is_measured_as_its_spec_declares(): point = POINTS[spec.source] assert point.kind == UNBIDDEN assert point.what == d.what - # A quiet source must say why, and only a quiet source may (#2475). + # A quiet source must say why, and only a quiet source may. assert point.expects_traffic == (not d.quiet_because) assert point.quiet_because == d.quiet_because @@ -59,7 +59,7 @@ def test_the_rule_denominator_is_every_rule_surface_a_ranker_chose(): def test_an_arm_added_without_tuning_would_be_caught(): - """Rule 167: the first test has to be able to bite. SURFACES skips an arm + """The first test has to be able to bite. SURFACES skips an arm with no tuning rather than raising, so an arm added without one would silently be untunable — and the order-equality assertion is what notices, as this replays with one such arm appended.""" diff --git a/tests/test_routes_design_systems.py b/tests/test_routes_design_systems.py index 803e247c..ed857d0d 100644 --- a/tests/test_routes_design_systems.py +++ b/tests/test_routes_design_systems.py @@ -77,11 +77,11 @@ def test_service_functions_take_user_id(): def test_agent_and_web_surfaces_stay_at_parity(): """The MCP tools and the REST routes are two callers of one service; a - capability on one has to exist on the other (rule #33). + capability on one has to exist on the other. - This guard exists because the snippet surfaces drifted apart once — MCP had - no delete, the web side had no near-duplicate gate — and neither failed - anything until someone went looking. + Two surfaces drift apart without failing anything — one door with no + delete, the other with no near-duplicate gate — until someone goes + looking. """ from scribe.mcp.tools import design_systems as tools from scribe.routes import design_systems as routes diff --git a/tests/test_routes_family.py b/tests/test_routes_family.py index 9ad79fca..b2376683 100644 --- a/tests/test_routes_family.py +++ b/tests/test_routes_family.py @@ -1,4 +1,4 @@ -"""Structural tests for the family blueprint (milestone 463 steps 3 and 4) — every +"""Structural tests for the family blueprint — every endpoint is routed, and every write hands the service its caller, where the note's write gate lives. What the writes do is in tests/test_integration_family_promotion.py.""" diff --git a/tests/test_routes_platforms.py b/tests/test_routes_platforms.py index c7c9ad05..38c5b285 100644 --- a/tests/test_routes_platforms.py +++ b/tests/test_routes_platforms.py @@ -1,4 +1,4 @@ -"""Structural tests for the platforms blueprint (milestone 463 step 2) — +"""Structural tests for the platforms blueprint — registration and the route-to-service contract. What the writes do is in tests/test_integration_platforms.py.""" import inspect diff --git a/tests/test_rule_creation_asks_first.py b/tests/test_rule_creation_asks_first.py index 60ad4433..15329a04 100644 --- a/tests/test_rule_creation_asks_first.py +++ b/tests/test_rule_creation_asks_first.py @@ -1,4 +1,4 @@ -"""Both rule-creation tools run the propose-then-approve loop (#3557). +"""Both rule-creation tools run the propose-then-approve loop. WHY THIS EXISTS @@ -38,8 +38,8 @@ prevents a rule earns more than any question that improves one's wording. WHAT THIS PINS, AND WHAT IT DOES NOT -STRUCTURE, never wording — the same bargain the disambiguator guard (#3123) -strikes next door. Each element matches a family of synonyms, so the prose +STRUCTURE, never wording — the same bargain the disambiguator guard +(test_create_tools_disambiguate) strikes next door. Each element matches a family of synonyms, so the prose stays free to be rewritten, reordered or sharpened; only DELETING one fails. Pinning phrasing would make every improvement a red build, and a test that punishes editing is a test someone deletes. @@ -76,7 +76,7 @@ _ELEMENTS = { ), "how it would be enforced": ("enforc",), "the answers offered back": ("as written", "talk about it", "discuss"), - # The fourth and fifth answers (#3733, #3896). They are what makes the + # The fourth and fifth answers. They are what makes the # question a ROUTING question rather than a yes/no one: an observation # that turns out to be a preference or a lesson has found its home, not # failed to become a rule. Written in one pass, because two people each diff --git a/tests/test_rule_moment_doors.py b/tests/test_rule_moment_doors.py index 5fa1a07e..20612a29 100644 --- a/tests/test_rule_moment_doors.py +++ b/tests/test_rule_moment_doors.py @@ -1,4 +1,4 @@ -"""Every door that writes a rule can mount it on moments (milestone 458 step 3). +"""Every door that writes a rule can mount it on moments. THE SHAPE, NOT ONE FUNCTION. test_system_tagging_door_parity records how a capability goes missing: whichever door nobody exercised for a kind never @@ -80,7 +80,7 @@ def test_every_rest_rule_door_takes_moments_and_checks_them_first(handler, write def test_the_guard_can_fail(): - """A door with no check at all must be reported, not skipped (rule 167).""" + """A door with no check at all must be reported, not skipped.""" fn = ast.parse("async def f():\n await svc.create_rule()\n").body[0] assert _first_line_calling(fn, "require_moments") is None diff --git a/tests/test_rule_moment_judgments.py b/tests/test_rule_moment_judgments.py index 2084dccf..670b2ffc 100644 --- a/tests/test_rule_moment_judgments.py +++ b/tests/test_rule_moment_judgments.py @@ -1,8 +1,8 @@ -"""Moment proposals and judgments — what a mock can see (milestone 458 step 7). +"""Moment proposals and judgments — what a mock can see. The behaviour against real tables is tests/test_integration_rule_moment_judgments.py. These pin the pieces that need no database: that the migration's CHECKs and -the model agree (rule 36), which moments count as evidence, and that the line +the model agree, which moments count as evidence, and that the line the signal hands a session names the tool that answers it. """ from __future__ import annotations @@ -61,7 +61,7 @@ def test_the_proposal_line_names_the_tool_that_answers_it(): assert "judge_rule_moments" in line and '"reject"' in line -# ── The misfire (step 7b) ──────────────────────────────────────────────────── +# ── The misfire ────────────────────────────────────────────────────────────── def test_a_day_is_one_situation_and_days_are_distinct(): diff --git a/tests/test_rule_moment_signal_hooks.py b/tests/test_rule_moment_signal_hooks.py index be703a05..f47d44e5 100644 --- a/tests/test_rule_moment_signal_hooks.py +++ b/tests/test_rule_moment_signal_hooks.py @@ -1,4 +1,4 @@ -"""The plugin half of the rule↔moment signal (milestone 458 step 7), run for real. +"""The plugin half of the rule↔moment signal, run for real. `scribe_moment.sh` writes every call to an acts ledger; `scribe_record_opened.sh` sends the last few minutes of it with the rule a session opened; the server @@ -158,7 +158,7 @@ def test_session_end_removes_the_sessions_moment_files_and_no_others(sandbox): def test_the_hook_and_the_route_agree_on_the_body(): - """Rule 33 across the shell/Python seam: a renamed field fails silently.""" + """A contract across the shell/Python seam: a renamed field fails silently.""" src = (HOOKS / "scribe_record_opened.sh").read_text() route = inspect.getsource(plugin_routes.rule_opened) assert "/api/plugin/rule-opened" in src diff --git a/tests/test_rule_overlap_gate.py b/tests/test_rule_overlap_gate.py index 5ca8ffc3..0a36c2cc 100644 --- a/tests/test_rule_overlap_gate.py +++ b/tests/test_rule_overlap_gate.py @@ -1,4 +1,4 @@ -"""Rule and preference creates surface the records they overlap (#4134). +"""Rule and preference creates surface the records they overlap. Rules and preferences share one table and one ranking, so a preference that restates a rule splits one instruction in two — and when only the preference diff --git a/tests/test_rule_trigger_required.py b/tests/test_rule_trigger_required.py index be486541..436a5651 100644 --- a/tests/test_rule_trigger_required.py +++ b/tests/test_rule_trigger_required.py @@ -1,4 +1,4 @@ -"""A rule cannot be created, or edited into, having no trigger (#4099). +"""A rule cannot be created, or edited into, having no trigger. WHY THIS EXISTS @@ -18,10 +18,10 @@ WHAT THIS PINS The guard lives in the SERVICE, because both doors reach it: the MCP tools and the frontend's fast path in `routes/rulebooks.py`. A guard written in either one alone would leave the other able to create a rule that never fires, which -is the failure mode this whole change exists to remove. +is the failure mode this guard exists to remove. These call the service directly and assert on the refusal, so an empty guard -cannot make them pass by accident (rule 167). +cannot make them pass by accident. """ from __future__ import annotations diff --git a/tests/test_rule_via_lesson.py b/tests/test_rule_via_lesson.py index 688c2107..a8f76274 100644 --- a/tests/test_rule_via_lesson.py +++ b/tests/test_rule_via_lesson.py @@ -1,4 +1,4 @@ -"""A rule reached through its lessons (milestone 440, #4633). +"""A rule reached through its lessons. The step runs after each of the three rule arms. These pin, with the database and the embedder stubbed: that nothing is searched when no lesson carries a @@ -125,7 +125,7 @@ async def test_a_failure_brings_no_rule_and_raises_nothing(): @pytest.mark.asyncio async def test_the_step_follows_the_arm_on_its_query_and_skips_what_it_named(): - """One composer for all three rule builders (milestone 456 step 6): the + """One composer for all three rule builders: the via-lesson step searches with the query the arm searched with, skips the session's ledger and every rule the arm's band named, and its lines and fresh ids join the arm's — while `shown_rule_ids` stays the direct band.""" @@ -153,9 +153,9 @@ async def test_a_blank_prompt_runs_neither_the_arm_nor_the_step(): def test_only_the_composer_runs_a_rule_arm_or_the_via_lesson_step(): - """Structural (rule 167): every rule builder goes through `_rule_moment`, - so a builder that runs the arm or the via-lesson step by hand again is the - copy this step removed coming back — and a count is what lets it fail.""" + """Structural: every rule builder goes through `_rule_moment`, so a + builder that runs the arm or the via-lesson step by hand is a second copy + of the composer — and a count is what lets it fail.""" import ast from pathlib import Path diff --git a/tests/test_scribe_marker.py b/tests/test_scribe_marker.py index 1c40604f..23378293 100644 --- a/tests/test_scribe_marker.py +++ b/tests/test_scribe_marker.py @@ -1,9 +1,8 @@ -"""`.scribe` — the second key a directory can be scoped by (#4085). +"""`.scribe` — the second key a directory can be scoped by. -Every hook in `plugin/hooks/` scopes its request to a project, and until this -existed all six turned on one key: `git remote get-url origin`. That key does -not exist outside a git repo, so a session in a plain directory was unscoped -everywhere at once — silently, because a missing remote is indistinguishable +Every hook in `plugin/hooks/` scopes its request to a project. With only one +key, `git remote get-url origin`, a session outside a git repo has no key at +all, so a session in a plain directory is unscoped everywhere at once — silently, because a missing remote is indistinguishable from a remote nobody bound. `scribe_scope_query` is the shared answer, so these run the real shell rather diff --git a/tests/test_search_route.py b/tests/test_search_route.py index d5332d37..e6f4e8d3 100644 --- a/tests/test_search_route.py +++ b/tests/test_search_route.py @@ -1,8 +1,8 @@ """/api/search — the kind filter it offers, and what it does with a bad one. -This route used to carry its own two-value map (`note`/`task`/everything else -is `all`), which made it the third hand-kept copy of a vocabulary that lives in -one table. Both halves of #4250 are pinned here: every declared kind is +The route reads its kinds from the one table the vocabulary lives in, rather +than keeping a map of its own (`note`/`task`/everything else is `all`). Both +halves are pinned here: every declared kind is reachable, and a kind that does not exist is refused rather than widened. """ import pytest diff --git a/tests/test_search_scope_shape.py b/tests/test_search_scope_shape.py index 2b8c9ee3..c8a81f10 100644 --- a/tests/test_search_scope_shape.py +++ b/tests/test_search_scope_shape.py @@ -1,4 +1,4 @@ -"""Every semantic search scopes first, then ranks (#4958, #4961). +"""Every semantic search scopes first, then ranks. Ordered straight off a `*_embeddings` table, the planner walks the HNSW index: it takes ~`hnsw.ef_search` (40) nearest chunks from every owner and project @@ -59,8 +59,8 @@ def test_every_semantic_search_ranks_through_the_scoped_shape(): assert "order_by" not in calls, f"{name} orders by distance itself" -def test_the_old_shape_would_be_caught(): - """Rule 167: replay the guard against the query every search used to run.""" +def test_a_rank_then_scope_query_is_caught(): + """Replay the guard against a query that ranks first and scopes after.""" old = ast.parse( "async def semantic_search_x():\n" " rows = await session.execute(select(Note, distance).select_from(E)" diff --git a/tests/test_services_access_visibility.py b/tests/test_services_access_visibility.py index 421e5288..6b5da30b 100644 --- a/tests/test_services_access_visibility.py +++ b/tests/test_services_access_visibility.py @@ -5,7 +5,7 @@ list query can't use. These express the same resolution as set membership. They are pure SQL builders — group membership is a subquery rather than a fetched list, so they need no session and callers' unit tests need not know they exist. -Two scopes, deliberately different (decision note 2094): +Two scopes, deliberately different: readable_* — everything the ACL permits, for explicit acts (a typed search, a fetch by id). browsable_* — owner + project access only, for passive surfaces (browse lists, diff --git a/tests/test_services_canonical_systems.py b/tests/test_services_canonical_systems.py index 53b12ed9..6e29ecf1 100644 --- a/tests/test_services_canonical_systems.py +++ b/tests/test_services_canonical_systems.py @@ -1,8 +1,7 @@ """The canonical-slug matcher — the line between what maps mechanically and -what a human is asked to confirm (milestone 307, note 3026). +what a human is asked to confirm. -These cases are the real drift found across the author's own instance: one area -carrying three spellings, and two areas that LOOK alike and are not the same. +These cases are real-world drift: one area carrying three spellings, and two areas that LOOK alike and are not the same. Getting the boundary wrong in either direction is a silent failure — a missed mapping nobody thinks to make again, or a cross-project record surfacing in the wrong project. diff --git a/tests/test_services_dashboard.py b/tests/test_services_dashboard.py index 85b44b8e..61175740 100644 --- a/tests/test_services_dashboard.py +++ b/tests/test_services_dashboard.py @@ -18,7 +18,7 @@ def test_task_row_maps_fields(): n.priority = None # Named, not left to MagicMock: an unset attribute is a truthy Mock, so # the assertion would pass on a field the row never really carried - # (note 2109 — the reason fake_note exists). + # (the reason fake_note exists). n.task_kind = "spike" assert _task_row(n) == { "id": 5, "title": "Wire reminders", "status": "in_progress", diff --git a/tests/test_services_design_systems.py b/tests/test_services_design_systems.py index c83d9739..206dd80f 100644 --- a/tests/test_services_design_systems.py +++ b/tests/test_services_design_systems.py @@ -185,7 +185,7 @@ async def test_clearing_a_projects_design_system_skips_the_system_acl(): acc.can_read_design_system.assert_not_awaited() -# --- resolution (step 2) ---------------------------------------------------- +# --- resolution ------------------------------------------------------------- @pytest.mark.asyncio async def test_resolve_denied_returns_none_not_an_empty_set(): diff --git a/tests/test_services_knowledge_counts.py b/tests/test_services_knowledge_counts.py index 4164504c..be27200b 100644 --- a/tests/test_services_knowledge_counts.py +++ b/tests/test_services_knowledge_counts.py @@ -1,11 +1,10 @@ """get_knowledge_counts — one number per facet, and an honest total. -Two grouped queries, one per typing axis. It used to be three: a grouped count -over note_type restricted to ("note", "process"), a scalar count of tasks, and -a second scalar just for plans. That shape is why `issue` had no number — -every kind needed a query of its own and nobody added one — and why the "All" -chip sat ~90 below the list it labelled, since snippets were in the feed but -in no count (#3128). +Two grouped queries, one per typing axis. A query per kind — a grouped count +over note_type restricted to ("note", "process"), a scalar count of tasks, a +second scalar just for plans — leaves every new kind without a number until +someone adds its query, and puts the "All" chip below the list it labels, +since snippets are in the feed but in no count. """ from unittest.mock import AsyncMock, MagicMock, patch @@ -71,9 +70,8 @@ async def test_total_counts_snippets_and_counts_no_task_twice(): async def test_absent_facets_report_zero_rather_than_missing(): counts, _ = await _counts([("note", 1)], []) assert counts["note"] == 1 - # `lesson` is here from milestone 385 step 2: a kind added to the facet - # table and not to the counts would show an empty chip beside a feed that - # has rows in it, which is defect 3b in #3161 repeating itself. + # A kind added to the facet table and not to the counts would show an + # empty chip beside a feed that has rows in it. for key in ("process", "snippet", "lesson", "task", "work", "issue", "spike", "plan"): assert counts[key] == 0, key assert counts["total"] == 1 diff --git a/tests/test_services_notes_sweep.py b/tests/test_services_notes_sweep.py index a611b78b..e8463641 100644 --- a/tests/test_services_notes_sweep.py +++ b/tests/test_services_notes_sweep.py @@ -1,4 +1,4 @@ -"""The note staleness sweep (milestone 317 step 3). +"""The note staleness sweep. The query is a sibling of the rules sweep, not a shared implementation — a rule scopes by rulebook ownership, a note by the note ACL, so only the @@ -103,7 +103,7 @@ async def test_trashed_notes_are_excluded(): @pytest.mark.asyncio async def test_the_scope_is_browse_not_read(): - """Decision note 2094: a sweep is a PASSIVE surface, so a record shared + """A sweep is a PASSIVE surface, so a record shared one-to-one must not arrive in one unasked. `note_shares` is the tell — its presence would mean the read scope leaked in.""" sql = await _sweep_sql() @@ -207,7 +207,7 @@ async def test_a_note_with_no_check_cannot_be_verified(): @pytest.mark.asyncio async def test_a_reader_cannot_stamp(): - """Stamping is a mutation (rules 47/78) — an editor-share holder may make + """Stamping is a mutation — an editor-share holder may make it, a viewer may not.""" note = fake_note(verify_with="curl the docs", verified_at=None) out, _ = await _mark(note, writable=False) diff --git a/tests/test_services_notes_verification.py b/tests/test_services_notes_verification.py index 8b7debd7..720be104 100644 --- a/tests/test_services_notes_verification.py +++ b/tests/test_services_notes_verification.py @@ -1,6 +1,6 @@ -"""A note's own check — verify_with / expires_when (milestone 317 step 2). +"""A note's own check — verify_with / expires_when. -Three behaviours the rules path (#3096) had to get right and this one inherits: +Three behaviours this path shares with a rule's check: empty means NULL, clearing is explicit, and rewriting the check drops the stamp. Plus one this path adds: not every record may carry a check, and the rule is an INVARIANT over the record rather than a filter on the write. diff --git a/tests/test_services_project_summaries.py b/tests/test_services_project_summaries.py index a377fa94..31523bd2 100644 --- a/tests/test_services_project_summaries.py +++ b/tests/test_services_project_summaries.py @@ -1,10 +1,10 @@ -"""Batched project summaries (#2384). +"""Batched project summaries. -The bug was not wrong output — it was CONNECTION COUNT. A per-project summary -opened its own session and then one more per milestone, and the route fanned -that out with asyncio.gather. For 25 projects that asked for roughly 250 -checkouts against a pool of 15, so most waited out the 30-second timeout and -every other route on the instance queued behind them: +The property is CONNECTION COUNT, not output. A per-project summary that opens +its own session and then one more per milestone, fanned out with +asyncio.gather, asks 25 projects for roughly 250 checkouts against a pool of +15, so most wait out the 30-second timeout and every other route on the +instance queues behind them: QueuePool limit of size 5 overflow 10 reached, connection timed out GET /api/settings 500 30584.0ms @@ -12,7 +12,7 @@ every other route on the instance queued behind them: So these tests assert the number of sessions opened, not only the values returned. A version that produced identical output while opening a session per -project would pass a correctness test and reproduce the outage. +project would pass a correctness test and exhaust the pool. """ from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch @@ -118,7 +118,7 @@ async def test_milestone_summaries_for_many_projects_open_ONE_session(): assert opened[0] == 1 # Touched is the later of the milestone's own edit and its newest step - # update (#4045): steps closing today count, an old step doesn't pull it back. + # update: steps closing today count, an old step doesn't pull it back. assert out[1][0]["last_touched_at"] == step_closed.isoformat() assert out[2][0]["last_touched_at"] == written.isoformat() assert out[1][0]["completed"] == 2 and out[1][0]["total"] == 3 diff --git a/tests/test_services_record_refs.py b/tests/test_services_record_refs.py index 92ae27a6..a9f07cbb 100644 --- a/tests/test_services_record_refs.py +++ b/tests/test_services_record_refs.py @@ -1,6 +1,6 @@ -"""Guessed record ids are refused; `{{ref:N}}` placeholders resolve (#4016). +"""Guessed record ids are refused; `{{ref:N}}` placeholders resolve. -The defect: a session predicts the ids its next creates will get and writes +The failure: a session predicts the ids its next creates will get and writes them into a body before the records exist; any other session's create takes those numbers. These pin the recogniser — what counts as a `#N`, where a guess can sit relative to the highest id — and the placeholder rewrite the batch diff --git a/tests/test_services_report_check.py b/tests/test_services_report_check.py index ff17c0ba..a06f15ad 100644 --- a/tests/test_services_report_check.py +++ b/tests/test_services_report_check.py @@ -1,7 +1,7 @@ -"""The report-shape check (milestone 409 step 5): which completion sections a -task-closing reply lacks, the words it is sent back with, and the outcome -record. Since milestone 500 step 4 the check itself runs here, inside the one -end-of-turn request, rather than in a Stop hook of its own.""" +"""The report-shape check: which completion sections a task-closing reply +lacks, the words it is sent back with, and the outcome record. The check runs +here, inside the one end-of-turn request, rather than in a Stop hook of its +own.""" import json from unittest.mock import AsyncMock, patch diff --git a/tests/test_services_shape_check.py b/tests/test_services_shape_check.py index 30b6fc85..62c8e3df 100644 --- a/tests/test_services_shape_check.py +++ b/tests/test_services_shape_check.py @@ -1,4 +1,4 @@ -"""The server half of the end-of-turn shape check (milestone 439): what the +"""The server half of the end-of-turn shape check: what the agent is asked, what the hook's ledger may say, and the outcome record.""" import json from unittest.mock import patch diff --git a/tests/test_services_systems.py b/tests/test_services_systems.py index 7ffcc6a6..135922a3 100644 --- a/tests/test_services_systems.py +++ b/tests/test_services_systems.py @@ -61,7 +61,7 @@ async def test_count_open_issues_denied_returns_zero(): assert result == 0 -# ── assess_system_name — the one gate both doors call (milestone 307) ── +# ── assess_system_name — the one gate both doors call ── @pytest.mark.asyncio async def test_assess_matches_an_existing_system_on_case_and_spacing(): @@ -128,7 +128,7 @@ async def test_assess_says_nothing_about_a_nameless_system(): assert await assess_system_name(1, 5, " ") == {"duplicate": None, "canonical": None} -# ── path patterns — the files that ARE the area (milestone 444, #4756) ── +# ── path patterns — the files that ARE the area ── def test_patterns_are_tidied_to_repo_relative_and_deduplicated(): from scribe.services.systems import normalize_path_patterns diff --git a/tests/test_services_trash.py b/tests/test_services_trash.py index bc40333f..45f756c3 100644 --- a/tests/test_services_trash.py +++ b/tests/test_services_trash.py @@ -49,7 +49,6 @@ async def test_delete_project_cascades_to_notes_milestones_and_project_rules(): # exists-check + 4 cascade ops: # notes (soft) → milestones (soft) → project-scoped rules (soft) → # project (soft) - # The two suppression hard-DELETEs went with their tables (milestone 414). session.execute = AsyncMock(side_effect=[ _exists_result(True), MagicMock(), MagicMock(), MagicMock(), diff --git a/tests/test_session_context_failure.py b/tests/test_session_context_failure.py index da687272..f21091c1 100644 --- a/tests/test_session_context_failure.py +++ b/tests/test_session_context_failure.py @@ -1,11 +1,11 @@ -"""A SessionStart that loads no project says why, and names the first move (#4366). +"""A SessionStart that loads no project says why, and names the first move. -The fetch used to be `curl -f … || body=""`: a timeout, an HTTP error and a -refused key all produced one sentence, which ended by suggesting -`enter_project()` "as needed". A session that read it as optional started with -no recent milestones or open tasks, and so had no way to know which prior work -existed to look for. These pin the two halves of the fix on the path that needs -no server: the cause is named, and the fallback is a concrete first step. +A fetch written as `curl -f … || body=""` collapses a timeout, an HTTP error +and a refused key into one sentence, and a fallback that suggests +`enter_project()` "as needed" reads as optional. A session that skips it starts +with no recent milestones or open tasks, and so has no way to know which prior +work exists to look for. These pin both halves on the path that needs no +server: the cause is named, and the fallback is a concrete first step. """ from __future__ import annotations diff --git a/tests/test_shape_check_hook.py b/tests/test_shape_check_hook.py index 249fb8fd..56e8b719 100644 --- a/tests/test_shape_check_hook.py +++ b/tests/test_shape_check_hook.py @@ -1,4 +1,4 @@ -"""The end-of-turn shape check (milestone 439): the write hooks note what each +"""The end-of-turn shape check: the write hooks note what each write defined, and the Stop hook asks the instance which of those nobody has judged, blocking once in the server's words. diff --git a/tests/test_shape_form_gate.py b/tests/test_shape_form_gate.py index 875335a7..1c0ede2d 100644 --- a/tests/test_shape_form_gate.py +++ b/tests/test_shape_form_gate.py @@ -311,10 +311,10 @@ def test_a_resemblance_score_must_positively_agree() -> None: assert not _stamp_allowed(1, "type", "fn") -def test_the_portal_case_no_longer_stamps_one_canon_onto_a_whole_file() -> None: +def test_one_canon_is_not_stamped_onto_a_whole_file() -> None: """A class, an async method and a sync helper in one file, against one - payload-level score. Previously all three were stamped; now the score - alone cannot carry any of them, and only a matching form could.""" + payload-level score. The score alone cannot carry any of them; only a + matching form can.""" canon = "async-fn" written = [("class SessionAbsent(RuntimeError):", False), ("async def attach(self) -> None:", True), diff --git a/tests/test_shared_write_access.py b/tests/test_shared_write_access.py index c0d689ef..e587d0aa 100644 --- a/tests/test_shared_write_access.py +++ b/tests/test_shared_write_access.py @@ -85,7 +85,7 @@ async def test_merge_requires_write_on_target(): @pytest.mark.asyncio async def test_merge_skips_sources_owned_by_someone_else(): - """Cross-owner merge stays out of scope (#231): a source belonging to a + """Cross-owner merge stays out of scope: a source belonging to a different owner than the target is skipped, not silently folded in and trashed.""" from scribe.services import snippets as svc diff --git a/tests/test_skill_moments.py b/tests/test_skill_moments.py index fb8576eb..770c1cf4 100644 --- a/tests/test_skill_moments.py +++ b/tests/test_skill_moments.py @@ -1,4 +1,4 @@ -"""The bundled skills declare the moments they are for (milestone 458 step 5). +"""The bundled skills declare the moments they are for. Loading the reporting procedure is a report; loading the planning procedure is planning. Each SKILL.md says which moment it is for in its frontmatter diff --git a/tests/test_snippet_drift_check.py b/tests/test_snippet_drift_check.py index 3b3a0540..2bbc3f79 100644 --- a/tests/test_snippet_drift_check.py +++ b/tests/test_snippet_drift_check.py @@ -1,4 +1,4 @@ -"""Tests for the drift check (#2086) — does a snippet still match its source? +"""Tests for the drift check — does a snippet still match its source? The check itself runs agent-side (Scribe has no checkout), so what's testable here is everything around it: the verdict's shape, the rule that a verdict @@ -175,7 +175,7 @@ def test_python_dialect_on_a_row_with_no_data_at_all(): [("ok", False), ("attention", True), ("drifted", False), ("unverified", False)], ) def test_python_dialect_on_a_push_invalidated_ok_verdict(value, expected): - """#2691: a push touched the recorded location since the verdict. Like the + """A push touched the recorded location since the verdict. Like the expired case, it is neither drifted (nothing found wrong) nor unverified (a check happened) — but the repo moved under the blessing, so `attention` must include it and `ok` must not.""" diff --git a/tests/test_snippet_merge_provenance.py b/tests/test_snippet_merge_provenance.py index d4ea2de0..c0e503e7 100644 --- a/tests/test_snippet_merge_provenance.py +++ b/tests/test_snippet_merge_provenance.py @@ -1,4 +1,4 @@ -"""Merge records what it absorbed (#2087). +"""Merge records what it absorbed. Merging keeps the target's scalar fields, unions locations + tags, and trashes the sources. Without provenance the fact that a variant ever existed survives @@ -59,7 +59,7 @@ async def test_merge_accumulates_across_merges(): @pytest.mark.asyncio async def test_merge_does_not_record_a_skipped_cross_owner_source(): - """A source owned by someone else is skipped, not folded in (#231) — so it + """A source owned by someone else is skipped, not folded in — so it must not appear in the provenance either, or the record would claim to contain something it never absorbed.""" kwargs = await _run_merge( diff --git a/tests/test_snippet_notes.py b/tests/test_snippet_notes.py index 1bb21a3c..272675c8 100644 --- a/tests/test_snippet_notes.py +++ b/tests/test_snippet_notes.py @@ -1,10 +1,10 @@ -"""A snippet's notes — the explanation that had nowhere to go (#4378). +"""A snippet's notes — where the explanation goes. -Before this field, a snippet had name, code, signature, locations and -`when_to_use`, and nothing for prose. What a session learned about a snippet -went into `when_to_use` — the trigger, joined onto the title of every chunk the -snippet is embedded as — so a multi-paragraph write-up there blurred the one -situation it is ranked on. +A snippet has name, code, signature, locations and `when_to_use`. Without a +place for prose, what a session learned about a snippet goes into +`when_to_use` — the trigger, joined onto the title of every chunk the snippet +is embedded as — so a multi-paragraph write-up there blurs the one situation +it is ranked on. The rules pinned here, each with a way to rot silently: diff --git a/tests/test_snippet_unmerge.py b/tests/test_snippet_unmerge.py index d898c028..20cb2a66 100644 --- a/tests/test_snippet_unmerge.py +++ b/tests/test_snippet_unmerge.py @@ -1,7 +1,7 @@ -"""Tests for un-merge (#2165) — reversing one source out of a merged survivor. +"""Tests for un-merge — reversing one source out of a merged survivor. -The design question this task said to settle first was how to subtract without -stripping call sites the survivor legitimately owns. The answer is per-source +The design question is how to subtract without stripping call sites the +survivor legitimately owns. The answer is per-source attribution recorded AT MERGE TIME: each `merged_from` entry holds only what that source actually added. These tests pin that rule, and the refusal that guards the case where the attribution isn't there. diff --git a/tests/test_system_rulings.py b/tests/test_system_rulings.py index a3b14841..25521cd5 100644 --- a/tests/test_system_rulings.py +++ b/tests/test_system_rulings.py @@ -1,4 +1,4 @@ -"""An area's rulings reach the work that touches its files (milestone 444, #4757). +"""An area's rulings reach the work that touches its files. These pin: diff --git a/tests/test_verification_guidance_survives.py b/tests/test_verification_guidance_survives.py index 139440b9..ca73d72b 100644 --- a/tests/test_verification_guidance_survives.py +++ b/tests/test_verification_guidance_survives.py @@ -9,8 +9,8 @@ and means nothing. The signal is not "has a check" — it is "has a check AND almost nothing else does". So the guidance is not decoration on this feature, it IS the feature's -precondition, and rule 119 puts it in the tool docstrings and the skill rather -than in a Scribe rule. That makes it exactly the kind of prose a later +precondition, and product guidance lives in the tool docstrings and the skill +rather than in a Scribe rule. That makes it exactly the kind of prose a later docstring tidy-up deletes without noticing what it was for. WHAT THIS PINS, AND WHAT IT DOES NOT @@ -94,7 +94,7 @@ def test_the_skill_carries_the_test_a_writer_can_actually_apply(): from tests.helpers import skill_text # Reference files included: the full statement sits in writing-records.md - # and SKILL.md keeps the pointer (#4398). + # and SKILL.md keeps the pointer. text = " ".join(skill_text("using-scribe").split()) assert "could this note become false without anyone editing it" in text.lower(), ( "the using-scribe skill no longer carries the one-question test. That " diff --git a/tests/test_version_pinning_prune.py b/tests/test_version_pinning_prune.py index f0eb4b43..1bbb2c5e 100644 --- a/tests/test_version_pinning_prune.py +++ b/tests/test_version_pinning_prune.py @@ -1,7 +1,5 @@ """Tests for the prune logic — both rolling (pin_kind IS NULL) and the auto-pin bucket (pin_kind='auto'). - -Design: docs/superpowers/specs/2026-05-13-note-version-pinning-design.md """ from unittest.mock import AsyncMock, MagicMock, patch from tests.helpers import make_mock_session diff --git a/tests/test_write_path_design_arm.py b/tests/test_write_path_design_arm.py index 85a8faf8..b0ed81fb 100644 --- a/tests/test_write_path_design_arm.py +++ b/tests/test_write_path_design_arm.py @@ -1,8 +1,8 @@ -"""The write-path design arm: a UI write is told which design system binds it (#4256). +"""The write-path design arm: a UI write is told which design system binds it. -A design system binds like a rule, and before this it reached a session only -through the session-start block — complete for a session that knows to ask, -silent for one writing a component. These tests pin: +A design system binds like a rule. Delivered only through the session-start +block, it is complete for a session that knows to ask and silent for one +writing a component. These tests pin: - THE TRIGGER is the file, not a search: a UI path in a project that has a design system. No vectors, no score, no slot taken from the ranked menu. diff --git a/tests/test_write_path_trigger.py b/tests/test_write_path_trigger.py index 1319fadd..8c752c4c 100644 --- a/tests/test_write_path_trigger.py +++ b/tests/test_write_path_trigger.py @@ -1776,14 +1776,15 @@ async def test_a_record_this_arm_withheld_itself_is_not_a_near_miss(): @pytest.mark.asyncio -async def test_a_ledger_repeat_no_longer_produces_that_case_at_all(): +async def test_a_ledger_repeat_is_counted_not_withheld(): """The other direction: a ledger repeat is not withheld. - A record on the session ledger now clears the search, is rendered with a + A record on the session ledger clears the search, is rendered with a marker, and is counted in `suppressed` — so nothing is withheld and the reported score describes the bar. Pinned because the tempting shortcut is - to null the score whenever the ledger matches, which would delete the near-miss measurement on exactly the calls - where the bar is most worth reading. + to null the score whenever the ledger matches, which would delete the + near-miss measurement on exactly the calls where the bar is most worth + reading. """ rec = MagicMock() row = await _write_path_row(rec, exclude_ids=[7])