graphmark 0.3.2__tar.gz → 0.3.3__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.2 → graphmark-0.3.3}/CHANGELOG.md +26 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/PKG-INFO +1 -1
- {graphmark-0.3.2 → graphmark-0.3.3}/pyproject.toml +1 -1
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/graph.py +5 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_graph.py +47 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_unresolved.py +5 -5
- {graphmark-0.3.2 → graphmark-0.3.3}/.gitignore +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/LICENSE +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/README.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/docs/ROADMAP.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/__init__.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/check.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/cli.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/config.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/dismiss.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/export.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/interfaces.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/metrics.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/model.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/parse.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/src/graphmark/py.typed +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/README.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/config.toml +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/expected.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/daily/2026-07-01.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/docs/alpha.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/docs/center.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/misc/echo.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/misc/foxtrot.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/misc/orphan.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/misc/stub.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/refs/beta.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/refs/delta.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/alt/vault/refs/gamma.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/dismiss/expected.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/dismiss/vault/.claude/data/connect-dismissed.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/dismiss/vault/alpha.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/dismiss/vault/beta.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/dismiss/vault/gamma.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/config.toml +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/expected.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/expected_oog.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/similar.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/similar_oog.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/vault/docs/a.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/vault/docs/hub.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/vault/misc/c.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/vault/misc/d.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/vault/refs/b.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/gaps/vault/refs/e.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/scoped/config.toml +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/scoped/expected.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/scoped/vault/docs/one.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/scoped/vault/junk/ignored.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/scoped/vault/misc/excluded.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/scoped/vault/refs/two.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/selflink/config.toml +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/selflink/expected.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/selflink/vault/a.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/selflink/vault/b.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/config.toml +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/expected.json +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/vault/brain/alpha.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/vault/brain/hub.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/vault/personal/beta.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/vault/personal/gamma.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/vault/reference/island.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/fixtures/simple/vault/reference/stub.md +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_build_filters.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_check.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_check_policy.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_cli.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_config.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_degenerate.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_dismiss.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_export.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_gaps.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_gaps_banding.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_gaps_oog.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_gaps_params.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_metrics.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_model.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_packaging.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_pagerank.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_parse.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_public_api.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_scoped.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_selflink.py +0 -0
- {graphmark-0.3.2 → graphmark-0.3.3}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.3.3 (2026-07-25)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **resolve**: [[note.md]] resolves like [[Note]]
|
|
9
|
+
([#105](https://github.com/cdcoonce/graphmark/pull/105),
|
|
10
|
+
[`916da52`](https://github.com/cdcoonce/graphmark/commit/916da52465cc3c5295a9440ae86af1ee6065e1d5))
|
|
11
|
+
|
|
12
|
+
Obsidian accepts an explicit .md extension, so [[Note.md]] and [[Note]] are the same link. graphmark
|
|
13
|
+
resolved only the second: the normalizer turned "Note.md" into the key "note md" while the file's
|
|
14
|
+
catalog key is "note", so the link never matched and was reported as broken. The path-suffix
|
|
15
|
+
branch was worse — it appends ".md" itself, so [[folder/note.md]] searched for folder/note.md.md.
|
|
16
|
+
|
|
17
|
+
Strip a case-insensitive trailing ".md" after alias/anchor stripping and before both resolution
|
|
18
|
+
branches. A missing [[Nowhere.md]] stays unresolved and an ambiguous stem stays ambiguous; a title
|
|
19
|
+
merely ending in the word "MD" is untouched.
|
|
20
|
+
|
|
21
|
+
Parity-safe: no frozen fixture uses a .md-style link, so every expected.json is unchanged (81 oracle
|
|
22
|
+
tests green). The change only adds resolutions that previously failed — it never removes one.
|
|
23
|
+
|
|
24
|
+
Also updates the test added in #101 that documented this gap as current behavior; it now asserts the
|
|
25
|
+
fix.
|
|
26
|
+
|
|
27
|
+
Closes #104
|
|
28
|
+
|
|
29
|
+
|
|
4
30
|
## v0.3.2 (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.3
|
|
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
|
|
@@ -84,6 +84,11 @@ class NormalizeResolver:
|
|
|
84
84
|
display = display.split("|")[0]
|
|
85
85
|
# Strip anchor: "Note#Section" → "Note"
|
|
86
86
|
display = display.split("#")[0]
|
|
87
|
+
# Obsidian accepts an explicit extension, so "Note.md" is the same link as "Note".
|
|
88
|
+
# Stripped before both branches below: the path-suffix branch appends ".md" itself,
|
|
89
|
+
# and the bare branch would otherwise normalize to the key "note md".
|
|
90
|
+
if display.lower().endswith(".md"):
|
|
91
|
+
display = display[: -len(".md")]
|
|
87
92
|
|
|
88
93
|
if "/" in display:
|
|
89
94
|
# Path-suffix resolution: find unique rel_path ending with "display.md"
|
|
@@ -190,3 +190,50 @@ class TestVaultGraphBuild:
|
|
|
190
190
|
assert degree("personal/gamma.md") == 2
|
|
191
191
|
assert degree("reference/island.md") == 0
|
|
192
192
|
assert degree("reference/stub.md") == 0
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestMarkdownExtensionInLinks:
|
|
196
|
+
"""Obsidian treats [[Note.md]] and [[Note]] as the same link; so must the resolver."""
|
|
197
|
+
|
|
198
|
+
def setup_method(self):
|
|
199
|
+
self.resolver = NormalizeResolver()
|
|
200
|
+
|
|
201
|
+
def _catalog(self, *rel_paths: str) -> dict[str, list[str]]:
|
|
202
|
+
return build_catalog([_doc(p) for p in rel_paths])
|
|
203
|
+
|
|
204
|
+
def test_bare_link_with_md_extension_resolves(self):
|
|
205
|
+
catalog = self._catalog("brain/alpha.md")
|
|
206
|
+
assert self.resolver.resolve("alpha.md", catalog) == "brain/alpha.md"
|
|
207
|
+
|
|
208
|
+
def test_extension_match_is_case_insensitive(self):
|
|
209
|
+
catalog = self._catalog("brain/alpha.md")
|
|
210
|
+
assert self.resolver.resolve("Alpha.MD", catalog) == "brain/alpha.md"
|
|
211
|
+
|
|
212
|
+
def test_md_extension_with_alias_resolves(self):
|
|
213
|
+
catalog = self._catalog("brain/alpha.md")
|
|
214
|
+
assert self.resolver.resolve("alpha.md|the first note", catalog) == "brain/alpha.md"
|
|
215
|
+
|
|
216
|
+
def test_md_extension_with_anchor_resolves(self):
|
|
217
|
+
catalog = self._catalog("brain/alpha.md")
|
|
218
|
+
assert self.resolver.resolve("alpha.md#Section", catalog) == "brain/alpha.md"
|
|
219
|
+
|
|
220
|
+
def test_path_style_link_with_md_extension_resolves(self):
|
|
221
|
+
catalog = self._catalog("brain/alpha.md", "personal/beta.md")
|
|
222
|
+
assert self.resolver.resolve("brain/alpha.md", catalog) == "brain/alpha.md"
|
|
223
|
+
|
|
224
|
+
def test_missing_target_with_md_extension_stays_unresolved(self):
|
|
225
|
+
catalog = self._catalog("brain/alpha.md")
|
|
226
|
+
assert self.resolver.resolve("nowhere.md", catalog) is None
|
|
227
|
+
|
|
228
|
+
def test_ambiguous_stem_stays_ambiguous_with_the_extension(self):
|
|
229
|
+
catalog = {"note": ["dir1/note.md", "dir2/note.md"]}
|
|
230
|
+
assert self.resolver.resolve("note.md", catalog) is None
|
|
231
|
+
|
|
232
|
+
def test_extensionless_links_are_unaffected(self):
|
|
233
|
+
catalog = self._catalog("brain/alpha.md")
|
|
234
|
+
assert self.resolver.resolve("alpha", catalog) == "brain/alpha.md"
|
|
235
|
+
|
|
236
|
+
def test_a_title_ending_in_md_as_a_word_is_not_stripped(self):
|
|
237
|
+
# "Doctor MD" ends with "md" but not ".md" — it must not be truncated.
|
|
238
|
+
catalog = self._catalog("org/Doctor MD.md")
|
|
239
|
+
assert self.resolver.resolve("Doctor MD", catalog) == "org/Doctor MD.md"
|
|
@@ -204,13 +204,13 @@ class TestNonMarkdownTargets:
|
|
|
204
204
|
assert graph.out_links["a.md"] == set()
|
|
205
205
|
|
|
206
206
|
def test_md_targets_are_exempt_from_the_rule(self, tmp_path):
|
|
207
|
-
# .md is
|
|
208
|
-
#
|
|
209
|
-
# ".md" (an Obsidian-compatibility gap tracked separately) — the point here is only
|
|
210
|
-
# that this rule does not silently swallow it.
|
|
207
|
+
# .md is excluded from the non-note rule, so a link ending in .md is left to the
|
|
208
|
+
# resolver — which now resolves it (a trailing ".md" is stripped, matching Obsidian).
|
|
211
209
|
_write(tmp_path, "a.md", "See [[b.md]].\n")
|
|
212
210
|
_write(tmp_path, "b.md", "Target.\n")
|
|
213
|
-
|
|
211
|
+
graph = _build(tmp_path)
|
|
212
|
+
assert graph.unresolved == {}
|
|
213
|
+
assert graph.out_links["a.md"] == {"b.md"}
|
|
214
214
|
|
|
215
215
|
def test_missing_md_target_is_still_unresolved(self, tmp_path):
|
|
216
216
|
_write(tmp_path, "a.md", "See [[Nowhere.md]].\n")
|
|
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.2 → graphmark-0.3.3}/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
|