Commit Graph
1058 Commits
Author SHA1 Message Date
ayushcodes10 23d8a8798d Add an end to end regression test for the reporter's exact repro
A file that only imports a package subpath must resolve just like one
that imports the bare package, using the exact package.json plus two
file shape from the issue, instead of coming out fully disconnected
from the framework it depends on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 5fb7fc34c1)
2026-09-17 11:01:38 +01:00
ayushcodes10 7aebd99372 Add a unit test for scoped package subpath resolution
A scoped package subpath lands on the scope plus package only, never
on a different package that happens to share the same scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 02183335e3)
2026-09-17 11:01:38 +01:00
ayushcodes10 3e9d22faf5 Add a unit test for plain package subpath resolution
A plain package subpath now lands on the same resolver target as its
bare import.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b703d3126e)
2026-09-17 11:01:38 +01:00
Dev Chiniwala 6e761f205a fix(diagnostics): separate external reference edges
(cherry picked from commit ba49e71a98)
2026-09-17 11:01:37 +01:00
safishamsiandClaude Opus 4.8 778c04726d fix(build): mint typed external stubs for undeclared import endpoints (#2873)
graph.json shipped imports/imports_from/re_exports edges whose target was an
external module (stdlib, a third-party dep) with no entry in nodes[]. Every
loader (networkx node_link_graph) materialises such an endpoint as an
attribute-less phantom, so declared node-count != loaded node-count, and
merge-graphs fragmented the same module into one node per repo.

- build_from_json now mints a typed external stub node (type=external,
  external=True) for an import-family edge whose target is otherwise undeclared,
  instead of dropping the edge. Non-import relations are unchanged, so a
  sourceless external call target stays suppressed (#3156, no phantom god-node).
- The --no-cluster / incremental update path writes the raw merged extraction
  rather than a builder graph, so it applies the same stubbing via the shared
  mint_external_stubs_in_data helper. Idempotent; grows the node set so the
  #479 shrink guard is unaffected.
- prefix_graph_for_global leaves external stubs unprefixed (no repo:: tag), so
  the same stdlib/dependency module unifies into one global node across a
  cross-repo merge instead of fragmenting per repo.

Adds regression tests for build-level stubbing, serialized-graph consistency,
the #3156 non-import drop, idempotency, cross-repo unification, and an
end-to-end `update --no-cluster` integrity check through the real rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-16 16:20:01 +01:00
safishamsiandClaude Opus 4.8 e3535e942a fix(elixir): guard nested-module capture and same-file contains clobber (#3603 follow-up)
#3603 resolves cross-file alias/import/require/use targets onto the real
defmodule node. Two guards ported from the competing #3566 so the resolver
does not over-reach:

- Only top-level modules are indexed as resolution targets. extract_elixir
  marks them with _elixir_module; a nested defmodule (labeled with its bare
  inner name) is left unmarked so an unrelated `use <Name>` from another file
  cannot latch onto it. The marker rides through incremental rebuilds via the
  resolution-context allow-list in watch.py and cli.py, so the fix does not
  decay on the `graphify update` / watch path.
- A same-file alias is left unresolved: the import edge's source is the file
  node, which already `contains` that module, so retargeting would duplicate
  the pair as a weaker `imports` and clobber `contains` in the non-multi graph.

Adds regression tests for both guards plus an incremental-rebuild test that
routes through the real watch._rebuild_code builder (the path #3566's own
test bypassed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-16 15:37:30 +01:00
Ryan Peterson b117a6e420 fix(watch): keep code-span mention edges through a rebuild over an existing graph
_reconcile_markdown_links treats every AST-tier references edge owned by a re-parsed
Markdown file as an authored [link](file) and prunes any pair it did not author. A code-span
mention is also a references edge owned by its document, so a plain `graphify update` over
an existing graph, with or without changes, dropped every mention edge; only a build from
scratch kept them.

A mention targets a code symbol, never a file representative, so the reconcile now leaves
those edges to the AST ownership rule: re-extracting the document regenerates them and
re-extracting the code side keeps or drops them with the target node.

(cherry picked from commit 73cf2ae56c)
2026-09-16 14:50:54 +01:00
Ryan Peterson 8cfebfdfd2 fix(markdown): honour dotted qualifiers and explicit relative cites in mention resolution
Three findings from the automated review of #3562, all reproduced:

A dotted span with a non-code extension (README.md, pyproject.toml) was classified as a
symbol because only CODE_EXTENSIONS was checked, and every dotted form was reduced to its
last segment, so time.sleep matched a repo's own sleep(). The dotted form now keeps its
whole chain, the file early-exit also checks DOC_EXTENSIONS, and a bare-name match must
carry every qualifier as an owner label or a segment or stem of its source path.

_match_cited_file used lstrip("./"), which strips characters, so a .github/... cite could
never match by suffix. The leading ./ and ../ segments are now stripped as segments, and
the suffix form also matches a root-level file.

An explicit ./ or ../ cite that escaped the corpus fell through to the suffix rule and
could land on an unrelated copy of the file. It now resolves relative to the document
only.

(cherry picked from commit 3f84e7f3e2)
2026-09-16 14:50:54 +01:00
ayushcodes10 7dbc668933 Add a regression test for a genuinely external module reference
A module with no matching definition anywhere in the corpus, stdlib
or a hex dependency, must stay an external reference rather than
getting fabricated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit da5e23a921)
2026-09-16 14:47:21 +01:00
ayushcodes10 50a65891f4 Add a regression test for the god node guard on a duplicate name
Two different files each declaring a module with the same bare name
must leave the reference exactly as extracted rather than guessing
which one the caller meant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 690d387626)
2026-09-16 14:47:21 +01:00
ayushcodes10 9a5b2dd7f9 Add a same file negative control test
A module importing something declared in the same file already
resolved before this fix, since both ids share the same stem, and
must keep working exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 8320da005a)
2026-09-16 14:47:21 +01:00
ayushcodes10 7406aae5e6 Add a regression test for the issue's own repro
A module aliased from a different file now resolves to that module's
real, non dangling node.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 53877c6874)
2026-09-16 14:47:21 +01:00
ayushcodes10 8c7b6675e8 Refuse to pool methods across two unrelated same named types
Pooling methods across every node sharing a type's bare label is
what makes a split impl block work, but it is only safe when every
one of those nodes really is an impl block for the SAME type. Two
unrelated structs that happen to share a bare name, each with its
own different methods, could still link a caller to a method that
only exists on the wrong one, most concretely when one type
overrides a trait default the other relies on and so never gets an
explicit method node for it.

A struct, enum, or trait declaration gets a contains edge from its
file; an impl block never does. So the type name itself is now
treated as ambiguous, and pooling skipped entirely, whenever two or
more nodes sharing the bare label are real declarations rather than
impl blocks. A single declaration with any number of impl blocks
spread across files, the shape this pass exists for, is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 141662b0eb)
2026-09-16 14:43:12 +01:00
ayushcodes10 58c497d2da Keep every method a duplicate index key maps to, not just the last one
A plain dict assignment silently overwrote an earlier target whenever
two method nodes shared both a source impl node and a stripped
label, which would have kept whichever one was seen last instead of
surfacing the case as the ambiguity it is. The index now keeps a set
per key and folds every match into the candidate set before the
exactly one candidate check runs, so a genuine duplicate still bails
instead of guessing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2cbbf1a670)
2026-09-16 14:43:12 +01:00
ayushcodes10 4f13b17aa2 Scope the self call dedup check to existing calls edges only
The check against a pair already having an edge matched on ANY
relation, so a caller that already had, say, a references edge to
the exact same target (from also naming the type elsewhere) silently
suppressed a real call to it. The two relations are not mutually
exclusive and an existing non calls edge says nothing about whether
a call was resolved. Scoped the check to calls edges only, matching
an existing precedent elsewhere in this module for the identical
class of mistake.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 94735ce241)
2026-09-16 14:43:12 +01:00
ayushcodes10 5483e5222c Add regression tests for the god node guard on self calls
Two different structs sharing a bare type name with a colliding
method name must yield no edge rather than a guess, and a self call
to a method that genuinely does not exist anywhere in the corpus
must not fabricate a target.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit afeeefa109)
2026-09-16 14:43:12 +01:00
ayushcodes10 d77dcb9a67 Add regression tests for the split impl block self call fix
Covers the issue's own shape (impl Foo split across two files, a
self call in one block reaching a method defined in the other) and a
same file negative control proving the pre existing bare name
resolution is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2d161af639)
2026-09-16 14:43:12 +01:00
oleksii-tumanov c5f57d3d61 fix: resolve inherited Ruby member calls
(cherry picked from commit 8f576afacc)
2026-09-16 14:43:05 +01:00
oleksii-tumanov eee5b02b7d fix: preserve Ruby constant lookup boundaries
(cherry picked from commit bd1a2d2651)
2026-09-15 18:03:49 +01:00
oleksii-tumanov 7e857e020b fix: guard Ruby namespace rebinding
(cherry picked from commit 1a7262e2a8)
2026-09-15 18:03:49 +01:00
oleksii-tumanov 436a735f7f fix: guard Ruby constant rebindings
(cherry picked from commit 5f28f2bb3e)
2026-09-15 18:03:49 +01:00
oleksii-tumanov ff75ca1029 fix: reject ambiguous Ruby constant mutations
(cherry picked from commit 891ebfae53)
2026-09-15 18:03:49 +01:00
oleksii-tumanov dd0b95d18a fix: harden Ruby inherited lookup proof
(cherry picked from commit 43f56f8633)
2026-09-15 18:03:12 +01:00
oleksii-tumanov bf28196db9 fix: promote inherited Ruby call confidence
(cherry picked from commit aec5c32000)
2026-09-15 18:03:12 +01:00
vstepko 161e5f2ef5 feat(terraform): resolve local module sources and directory topology
(cherry picked from commit e00593d10e)
2026-09-15 18:01:35 +01:00
Ryan Peterson c805c6fb9d feat(markdown): resolve code-span mentions into references edges
The markdown extractor's docstring promised a heading --references--> node edge
when a backtick Name matches a known pattern, but nothing implemented it, so an
AST build produced no document-to-code edge at all.

extract_markdown now collects inline code spans outside fences and frontmatter as
raw_calls tagged language "markdown", owned by the heading they sit under (the
page before the first heading). A new markdown_mentions language resolver runs
after the id-remap passes and turns them into references edges: the explicit
path::Symbol[::member] form resolves through the cited file's symbols and is
EXTRACTED 1.0; a bare or dotted name resolves only when exactly one callable code
node carries that label and is INFERRED 0.95. Ambiguous names, builtins, missing
files and non-callable matches emit nothing. The shared cross-file call pass
skips markdown raw_calls so prose never emits calls edges.

Tests cover span classification, raw_call ownership, end-to-end resolution,
ambiguity, and cache round-tripping.

(cherry picked from commit 2d833a0a3c)
2026-09-15 18:01:28 +01:00
safishamsi d4b704fa05 test(cluster): add the self-loop cohesion_score regressions #3558 described
#3558's fix landed but its diff omitted the 3 tests its description claimed;
add them (self-loop ignored, self-loops-only is 0, 0..1 bound holds). Also drop
a changelog line the #3559 cherry-pick injected into the released 0.9.61 section.
2026-09-14 18:55:31 +01:00
ayushcodes10 9b1db672fb Add regression test for dangling edge pruning consistency
group_skipped feeds the dangling reference pruning pass and the write
loop must agree with it on whether a recovered group is written or
skipped. This proves an edge between two nodes inside the same
recovered group survives instead of being pruned as referencing a
supposedly skipped id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 8a23b305fe)
2026-09-14 18:52:06 +01:00
ayushcodes10 6694f65465 Add regression test for unscoped callers with no allowlist
Confirms recovery never runs when allowed_source_files is omitted: a
genuinely bogus path is skipped exactly as before this fix, and a
normal well formed path in the same batch still saves and loads back.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 12999a80f6)
2026-09-14 18:52:06 +01:00
ayushcodes10 cf8ac5d035 Add regression test for an ambiguous basename staying skipped
Two allowed files sharing a basename must not let recovery guess
between them: an unresolvable source_file with an ambiguous basename
still falls through to the previous skip behavior unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit f59acf7c36)
2026-09-14 18:52:06 +01:00
ayushcodes10 a442031291 Correct recovered groups own source_file before writing them
A recovered group's write KEY pointed at the right file, but every
node, edge, and hyperedge inside it still carried the original
unresolvable source_file string. load_cached rejects an entry on read
if any item's source_file does not match the path it was loaded
under, so the previous commit alone wrote a group that reported as
saved but could never actually be read back, silently reproducing the
exact loss this fix exists for.

This corrects every item's source_file to the recovered path's
portable form before the group is written, only when recovery
actually redirected the key, so an unrecovered group stays byte
identical to before.

Includes the regression test proving the full round trip: a group
recovered by basename now survives a subsequent load_cached call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 4b91dcd462)
2026-09-14 18:52:06 +01:00
ayushcodes10 5d858ad37a Add regression test for the clustered path replace fallback
Same #2689 fallback requirement as the no_cluster test, exercised
against the default clustered rebuild path, the second of the two
watch.py call sites the issue reports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 366ec5ef1f)
2026-09-14 18:52:05 +01:00
ayushcodes10 0da4fe68db Add regression test for the no_cluster path replace fallback
Simulates the VMware HGFS PermissionError this issue reports by
monkeypatching os.replace to always raise it, then confirms an
incremental no_cluster rebuild still succeeds and lands the new
content instead of aborting the whole run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 9e75593b02)
2026-09-14 18:52:05 +01:00
breken df9f37612f fix(hooks): resolve the output dir from GRAPHIFY_OUT in the sh gates, not a literal graphify-out/
#1423 moved the Python rebuild bodies onto GRAPHIFY_OUT, but the shell that
runs BEFORE them still hardcoded graphify-out/. With the documented override
(#686, e.g. GRAPHIFY_OUT=custom-out for worktree/shared-output setups):

- post-checkout exited at `[ ! -d "graphify-out" ]`, so a branch switch never
  launched the rebuild - a silent no-op with nothing in the log.
- post-commit's "only graph artifacts changed" filter only recognised
  graphify-out/ paths, so a commit touching only custom-out/graph.json
  launched a full rebuild - the loop the filter exists to prevent.

Both gates now read ${GRAPHIFY_OUT:-graphify-out}, the same source the rebuild
body reads. The commit filter is a POSIX case loop instead of grep so the dir
name is matched literally (no regex metacharacters); the leading ( on each
pattern keeps bash 3.2 (macOS /bin/sh) from mis-parsing the pattern's ) inside
the $(...) substitution.

Regression: tests/test_hooks.py runs the emitted scripts under sh with the
launcher stubbed and asserts whether the hook reaches the launch line.

Claude-Session: https://claude.ai/code/session_0134ujLF81GyXsCByibLcYsz
(cherry picked from commit 9efd052de4)
2026-09-14 18:49:21 +01:00
ayushcodes10 bf5769585a Update thin community tests for the corrected count
Three existing tests pinned the old, undercounting behavior directly,
including one whose own docstring asserted the exact shape this fix
corrects. Updates all three to the now correct figures and adds a
dedicated test asserting the two invariants the issue itself
suggested, shown equals what actually renders and shown plus thin
equals the total, at both thresholds already exercised in this file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit dd056ab9a8)
2026-09-14 18:49:20 +01:00
ayushcodes10 c5ad8d6852 Add a regression test for unexported require destructures
Pins the exact interaction the fix commit already describes: a
CommonJS const equals require of a module destructure must still
resolve to exactly one EXTRACTED calls edge, not zero, confirming
the export only gate keeps it out of the per name split entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit e9c10258a7)
2026-09-14 18:49:19 +01:00
ayushcodes10 93820c9c01 Add a test for rest pattern skip in destructured exports
A rest element does not correspond to one discrete exported name, so
it must be skipped rather than mangled into a bogus node, and its
sibling names must still extract cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 7091925a3a)
2026-09-14 18:49:19 +01:00
ayushcodes10 7be28b1ca7 Add a test for renamed property export naming
A renamed destructured property must export under its property key,
never under the local alias it is bound to, since that alias is not
what an importer would ever bind by.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 8fae543236)
2026-09-14 18:49:19 +01:00
ayushcodes10 37b3db9654 Add a test for cross file resolution through a split export
A single name import must resolve to its own split node, and a call
made through it must resolve too, not just the import edge on its
own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 78a3ca4bf7)
2026-09-14 18:49:19 +01:00
ayushcodes10 025caeaa93 Add a test for the per name destructured export split
Four destructured names on one export statement must each get their
own node, and the old combined pattern syntax label must not survive
anywhere in the output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 3f62c3d85c)
2026-09-14 18:49:19 +01:00
ayushcodes10 61316e80b6 Add negative control test for an ordinary ASCII path
Confirms the reconstructed prefix set for an ASCII path is byte for
byte identical to what the old ASCII only regex produced, so the fix
changes behavior only for paths the old code was already getting
wrong.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 97bd1f37f0)
2026-09-14 18:49:17 +01:00
ayushcodes10 0ea296e9ba Add end to end regression test for the issue own repro shape
Runs deduplicate_entities on the defining and referencing Korean
node pair in both arrival orders and asserts the defining file own
node survives either way, matching the concrete case the issue
reports: a page whose filename equals the concept name losing its own
node to a page that merely mentions it in passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 852c61098f)
2026-09-14 18:49:17 +01:00
ayushcodes10 ff6707a015 Add regression test for defines_id on a non Latin path
Confirms the layer above _id_prefixes actually benefits from the
fix: a node whose id and source_file both encode a Korean path is
recognized as defining that id, and a node sharing that id from a
different file is correctly recognized as merely referencing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 7d15c1bb26)
2026-09-14 18:49:17 +01:00
ayushcodes10 48380444b1 Add regression test for non Latin segments in id prefixes
Confirms the exact repro shape from the issue: the reconstructed
prefix set for a Korean file path preserves every segment instead of
collapsing them to nothing, matching what the file's own id is
actually minted as.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit ab4bde5413)
2026-09-14 18:49:17 +01:00
A.Levin 6cebd67d3a test: cover bare require() imports edges for Lua (#3320)
A bare `require("mod")` statement with no assignment produces no imports
edge, while `local x = require("mod")` does. Cover the bare form in the
flattened and lua/config layouts, with and without parentheses, plus
guards for the callback recursion a fix must preserve and for method-style
calls that must not fabricate an edge.

(cherry picked from commit c87586c343)
2026-09-13 17:47:57 +01:00
Dev Chiniwala 46c93444b9 fix(merge): respect C# type visibility across repos
(cherry picked from commit cf6b7f7b68)
2026-09-13 17:47:56 +01:00
ayushcodes10 737db3ac05 Add regression tests for module level anonymous calls
Covers the same file case, the doubly nested repro shape from the
report itself, confirms the number 1740 named enclosing function
case is unaffected, and documents the direct import collision gap
called out in the fix commit as a pin rather than a silent
assumption.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 7983186f2f)
2026-09-13 17:47:55 +01:00
Dev Chiniwala 50c9c7d895 fix(extract): skip calls to sourceless stubs
(cherry picked from commit 9f0ad48259)
2026-09-13 17:47:54 +01:00
ayushcodes10 fc33e2ccbd Add regression tests for the guard hook timeouts
One per hook, matching the existing matcher tests, checking the
timeout key is present and stays within a small bounded range rather
than pinning the exact value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 59535b3e01)
2026-09-13 17:47:54 +01:00
HARSHAVARDHAN-RAJU5 3eb2486803 fix: bare plural "tokens" in a prose file is no longer treated as a secret
TOKENS.md / tokens.md read as design-token reference docs, not credential
dumps, unlike singular "token.md" or another keyword's bare plural
("secrets.md"), which still exclude. The .dart/.ts design-token source
files from the original report are already covered by the existing
graphable-source exemption; this closes the remaining prose-note gap.

Fixes #3527

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6anVY9hgp5ph5qLXRmc2p
(cherry picked from commit 64e85524ef)
2026-09-13 17:47:53 +01:00