graphmark 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graphmark-0.3.0 → graphmark-0.3.1}/CHANGELOG.md +26 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/PKG-INFO +1 -1
- {graphmark-0.3.0 → graphmark-0.3.1}/pyproject.toml +1 -1
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/graph.py +12 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_unresolved.py +45 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/.gitignore +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/LICENSE +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/README.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/docs/ROADMAP.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/__init__.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/check.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/cli.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/config.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/dismiss.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/export.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/interfaces.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/metrics.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/model.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/parse.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/src/graphmark/py.typed +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/README.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/config.toml +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/expected.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/daily/2026-07-01.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/docs/alpha.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/docs/center.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/misc/echo.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/misc/foxtrot.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/misc/orphan.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/misc/stub.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/refs/beta.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/refs/delta.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/alt/vault/refs/gamma.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/dismiss/expected.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/dismiss/vault/.claude/data/connect-dismissed.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/dismiss/vault/alpha.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/dismiss/vault/beta.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/dismiss/vault/gamma.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/config.toml +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/expected.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/expected_oog.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/similar.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/similar_oog.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/vault/docs/a.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/vault/docs/hub.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/vault/misc/c.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/vault/misc/d.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/vault/refs/b.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/gaps/vault/refs/e.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/scoped/config.toml +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/scoped/expected.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/scoped/vault/docs/one.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/scoped/vault/junk/ignored.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/scoped/vault/misc/excluded.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/scoped/vault/refs/two.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/selflink/config.toml +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/selflink/expected.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/selflink/vault/a.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/selflink/vault/b.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/config.toml +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/expected.json +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/vault/brain/alpha.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/vault/brain/hub.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/vault/personal/beta.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/vault/personal/gamma.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/vault/reference/island.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/simple/vault/reference/stub.md +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_build_filters.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_check.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_check_policy.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_cli.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_config.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_degenerate.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_dismiss.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_export.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_gaps.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_gaps_banding.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_gaps_oog.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_gaps_params.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_graph.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_metrics.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_model.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_packaging.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_pagerank.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_parse.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_public_api.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_scoped.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_selflink.py +0 -0
- {graphmark-0.3.0 → graphmark-0.3.1}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.3.1 (2026-07-25)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **graph**: Stop counting same-note anchor links as unresolved
|
|
9
|
+
([#99](https://github.com/cdcoonce/graphmark/pull/99),
|
|
10
|
+
[`a1d03f3`](https://github.com/cdcoonce/graphmark/commit/a1d03f3eb0e7b36b6d44d2a4eecfe8bb70da8d11))
|
|
11
|
+
|
|
12
|
+
unresolved counted anchor-only wikilinks — [[#Heading]], [[#Heading|alias]], [[#^blockref]] — as
|
|
13
|
+
broken. Those are Obsidian same-note references: they target no note at all, so they are neither
|
|
14
|
+
an edge nor a broken link. Counting them corrupted the very metric Track D was built around,
|
|
15
|
+
max_unresolved_links.
|
|
16
|
+
|
|
17
|
+
Measured on the owner's live 521-note vault: 40 of 213 reported unresolved links (19%) were
|
|
18
|
+
anchor-only, and the note reported as the worst offender earned that spot purely by navigating
|
|
19
|
+
itself heavily.
|
|
20
|
+
|
|
21
|
+
build() now skips a display whose note part (before | and #) is empty. [[Note#Heading]] is
|
|
22
|
+
unaffected: it still resolves via Note, and still counts as unresolved when Note is missing.
|
|
23
|
+
|
|
24
|
+
This refines the #75 semantics, which defined ambiguous-counts and self-link-doesn't but never
|
|
25
|
+
considered anchor-only links.
|
|
26
|
+
|
|
27
|
+
Closes #98
|
|
28
|
+
|
|
29
|
+
|
|
4
30
|
## v0.3.0 (2026-07-25)
|
|
5
31
|
|
|
6
32
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphmark
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Deterministic knowledge-graph analysis for markdown / [[wikilink]] vaults.
|
|
5
5
|
Project-URL: Homepage, https://github.com/cdcoonce/graphmark
|
|
6
6
|
Project-URL: Repository, https://github.com/cdcoonce/graphmark
|
|
@@ -18,6 +18,16 @@ def _normalize(text: str) -> str:
|
|
|
18
18
|
return " ".join(text.lower().translate(_PUNCT_TABLE).split())
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
def _is_intra_note_reference(display: str) -> bool:
|
|
22
|
+
"""True for a link that targets no note, e.g. ``[[#Heading]]`` or ``[[#^block]]``.
|
|
23
|
+
|
|
24
|
+
Obsidian uses an empty note part to mean "somewhere in this same note". Such a link is
|
|
25
|
+
neither an edge nor a broken link, so it must not be recorded as unresolved — otherwise
|
|
26
|
+
a note that navigates itself heavily looks like the vault's worst offender.
|
|
27
|
+
"""
|
|
28
|
+
return not display.split("|")[0].split("#")[0].strip()
|
|
29
|
+
|
|
30
|
+
|
|
21
31
|
def build_catalog(docs: list[Document]) -> dict[str, list[str]]:
|
|
22
32
|
"""Map normalized stem → list of rel_paths (len > 1 means ambiguous)."""
|
|
23
33
|
catalog: dict[str, list[str]] = {}
|
|
@@ -126,6 +136,8 @@ class VaultGraph:
|
|
|
126
136
|
|
|
127
137
|
for doc in docs:
|
|
128
138
|
for display in extractor.extract(doc.text):
|
|
139
|
+
if _is_intra_note_reference(display):
|
|
140
|
+
continue
|
|
129
141
|
target = resolver.resolve(display, catalog)
|
|
130
142
|
if target is None:
|
|
131
143
|
# Unresolvable OR ambiguous — the Resolver protocol conflates the two, and
|
|
@@ -130,3 +130,48 @@ class TestUnresolvedIntegration:
|
|
|
130
130
|
assert isinstance(graph.unresolved, dict)
|
|
131
131
|
# stats is untouched by this addition — the oracle still governs it.
|
|
132
132
|
assert set(stats(graph)) == {"notes", "edges", "orphans", "clusters", "density"}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestIntraNoteAnchorLinks:
|
|
136
|
+
"""[[#Heading]] is a same-note reference, not a link to another note.
|
|
137
|
+
|
|
138
|
+
It is neither an edge nor a broken link, so counting it as unresolved inflates the
|
|
139
|
+
check gate's max_unresolved_links threshold (measured: 19% of a real vault's total).
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def test_anchor_only_link_is_not_unresolved(self, tmp_path):
|
|
143
|
+
_write(tmp_path, "a.md", "# Top\n\nJump to [[#Section]].\n\n## Section\n")
|
|
144
|
+
graph = _build(tmp_path)
|
|
145
|
+
assert graph.unresolved == {}
|
|
146
|
+
|
|
147
|
+
def test_anchor_only_link_with_alias_is_not_unresolved(self, tmp_path):
|
|
148
|
+
_write(tmp_path, "a.md", "See [[#Design cruxes|crux 2]].\n")
|
|
149
|
+
assert _build(tmp_path).unresolved == {}
|
|
150
|
+
|
|
151
|
+
def test_block_reference_is_not_unresolved(self, tmp_path):
|
|
152
|
+
_write(tmp_path, "a.md", "See [[#^abc123]].\n")
|
|
153
|
+
assert _build(tmp_path).unresolved == {}
|
|
154
|
+
|
|
155
|
+
def test_anchor_only_link_creates_no_edge(self, tmp_path):
|
|
156
|
+
_write(tmp_path, "a.md", "Jump to [[#Section]].\n")
|
|
157
|
+
graph = _build(tmp_path)
|
|
158
|
+
assert graph.out_links["a.md"] == set()
|
|
159
|
+
|
|
160
|
+
def test_whitespace_only_display_is_ignored(self, tmp_path):
|
|
161
|
+
_write(tmp_path, "a.md", "Odd [[ ]] link.\n")
|
|
162
|
+
assert _build(tmp_path).unresolved == {}
|
|
163
|
+
|
|
164
|
+
def test_cross_note_anchor_still_resolves(self, tmp_path):
|
|
165
|
+
_write(tmp_path, "a.md", "See [[b#Section]].\n")
|
|
166
|
+
_write(tmp_path, "b.md", "# B\n\n## Section\n")
|
|
167
|
+
graph = _build(tmp_path)
|
|
168
|
+
assert graph.unresolved == {}
|
|
169
|
+
assert graph.out_links["a.md"] == {"b.md"}
|
|
170
|
+
|
|
171
|
+
def test_cross_note_anchor_to_a_missing_note_is_still_unresolved(self, tmp_path):
|
|
172
|
+
_write(tmp_path, "a.md", "See [[Nowhere#Section]].\n")
|
|
173
|
+
assert _build(tmp_path).unresolved == {"a.md": ["Nowhere#Section"]}
|
|
174
|
+
|
|
175
|
+
def test_mixed_note_keeps_only_the_real_break(self, tmp_path):
|
|
176
|
+
_write(tmp_path, "a.md", "[[#Local]] and [[Missing]] and [[#Other|x]].\n")
|
|
177
|
+
assert _build(tmp_path).unresolved == {"a.md": ["Missing"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphmark-0.3.0 → graphmark-0.3.1}/tests/fixtures/dismiss/vault/.claude/data/connect-dismissed.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|