graphmark 0.3.3__tar.gz → 0.3.4__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.3 → graphmark-0.3.4}/CHANGELOG.md +34 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/PKG-INFO +1 -1
- {graphmark-0.3.3 → graphmark-0.3.4}/pyproject.toml +1 -1
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/graph.py +54 -16
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_unresolved.py +103 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/.gitignore +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/LICENSE +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/README.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/docs/ROADMAP.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/__init__.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/check.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/cli.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/config.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/dismiss.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/export.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/interfaces.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/metrics.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/model.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/parse.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/src/graphmark/py.typed +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/README.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/config.toml +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/expected.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/daily/2026-07-01.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/docs/alpha.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/docs/center.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/misc/echo.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/misc/foxtrot.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/misc/orphan.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/misc/stub.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/refs/beta.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/refs/delta.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/alt/vault/refs/gamma.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/dismiss/expected.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/dismiss/vault/.claude/data/connect-dismissed.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/dismiss/vault/alpha.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/dismiss/vault/beta.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/dismiss/vault/gamma.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/config.toml +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/expected.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/expected_oog.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/similar.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/similar_oog.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/vault/docs/a.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/vault/docs/hub.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/vault/misc/c.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/vault/misc/d.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/vault/refs/b.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/gaps/vault/refs/e.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/scoped/config.toml +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/scoped/expected.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/scoped/vault/docs/one.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/scoped/vault/junk/ignored.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/scoped/vault/misc/excluded.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/scoped/vault/refs/two.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/selflink/config.toml +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/selflink/expected.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/selflink/vault/a.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/selflink/vault/b.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/config.toml +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/expected.json +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/vault/brain/alpha.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/vault/brain/hub.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/vault/personal/beta.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/vault/personal/gamma.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/vault/reference/island.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/fixtures/simple/vault/reference/stub.md +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_build_filters.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_check.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_check_policy.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_cli.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_config.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_degenerate.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_dismiss.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_export.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_gaps.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_gaps_banding.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_gaps_oog.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_gaps_params.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_graph.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_metrics.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_model.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_packaging.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_pagerank.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_parse.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_public_api.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_scoped.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_selflink.py +0 -0
- {graphmark-0.3.3 → graphmark-0.3.4}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.3.4 (2026-07-25)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **graph**: Links to existing out-of-scope notes are not broken (#107)
|
|
9
|
+
([#108](https://github.com/cdcoonce/graphmark/pull/108),
|
|
10
|
+
[`9e386f6`](https://github.com/cdcoonce/graphmark/commit/9e386f6df700b1d866b7d50854b0cc8734a08bf5))
|
|
11
|
+
|
|
12
|
+
build() dropped unscoped folders, excluded dirs and rules files from the catalog and then forgot
|
|
13
|
+
they existed, so a link to one failed the resolver and landed in unresolved. The link is correct —
|
|
14
|
+
Obsidian follows it — it just points somewhere graphmark deliberately does not index, so there was
|
|
15
|
+
nothing for anyone to fix. On the live vault that was 11 of 155 reported breaks: 8 [[CLAUDE]], 1
|
|
16
|
+
[[AGENTS]], 2 into templates/.
|
|
17
|
+
|
|
18
|
+
The same rglob that builds the catalog now records what it skipped, so no extra I/O buys the ability
|
|
19
|
+
to tell "exists but out of scope" apart from "exists nowhere". Consulted only AFTER the resolver
|
|
20
|
+
fails — mirroring _targets_non_note_file — so an in-graph note always wins over an out-of-scope
|
|
21
|
+
namesake and keeps its edge. Any candidate suppresses: out-of-scope notes are never link targets,
|
|
22
|
+
so ambiguity among them says nothing about whether the in-graph link is broken.
|
|
23
|
+
|
|
24
|
+
Alias/anchor/.md stripping moved into a shared _strip_display so the resolver and the new check
|
|
25
|
+
cannot drift on what a display names. That refactor also strips surrounding whitespace, which the
|
|
26
|
+
resolver did not: 13 column-aligned links of the form [[folder/note | alias]] were reported broken
|
|
27
|
+
while pointing at real notes, and now resolve into edges (3570 → 3583 on the live vault). Covered
|
|
28
|
+
by its own test class.
|
|
29
|
+
|
|
30
|
+
Fixtures are untouched: only the unresolved path changes shape, no expected.json carries an
|
|
31
|
+
unresolved key, and edges only grow via the whitespace fix. uv.lock catches up to the 0.3.3
|
|
32
|
+
release bump.
|
|
33
|
+
|
|
34
|
+
Live vault: 155 → 130 unresolved (12 suppressed, 13 resolved), no genuine break lost — verified by
|
|
35
|
+
diffing the full before/after sets.
|
|
36
|
+
|
|
37
|
+
|
|
4
38
|
## v0.3.3 (2026-07-25)
|
|
5
39
|
|
|
6
40
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphmark
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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
|
|
@@ -25,6 +25,19 @@ def _normalize(text: str) -> str:
|
|
|
25
25
|
_FILE_SUFFIX_RE = re.compile(r"\.[A-Za-z0-9]{1,10}$")
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _strip_display(display: str) -> str:
|
|
29
|
+
"""Reduce a raw wikilink display to its note part: no alias, no anchor, no ``.md``.
|
|
30
|
+
|
|
31
|
+
Obsidian treats ``[[Note]]``, ``[[Note|alias]]``, ``[[Note#Section]]`` and ``[[Note.md]]`` as
|
|
32
|
+
the same link, so every place that asks "which note does this display name?" must strip the
|
|
33
|
+
same three things. Shared by the resolver and the out-of-scope check so they cannot drift.
|
|
34
|
+
"""
|
|
35
|
+
target = display.split("|")[0].split("#")[0].strip()
|
|
36
|
+
if target.lower().endswith(".md"):
|
|
37
|
+
target = target[: -len(".md")]
|
|
38
|
+
return target
|
|
39
|
+
|
|
40
|
+
|
|
28
41
|
def _targets_non_note_file(display: str) -> bool:
|
|
29
42
|
"""True for a link to a file graphmark does not index, e.g. ``[[Board.canvas]]``.
|
|
30
43
|
|
|
@@ -40,6 +53,28 @@ def _targets_non_note_file(display: str) -> bool:
|
|
|
40
53
|
return bool(match) and match.group(0).lower() != ".md"
|
|
41
54
|
|
|
42
55
|
|
|
56
|
+
def _targets_out_of_scope_note(display: str, out_of_scope: dict[str, list[str]]) -> bool:
|
|
57
|
+
"""True for a link to a markdown note that exists but is outside the configured scope.
|
|
58
|
+
|
|
59
|
+
``build`` drops unscoped folders, excluded dirs and rules files from the catalog, so links to
|
|
60
|
+
them fail the resolver — yet the link is correct, Obsidian follows it, and there is nothing for
|
|
61
|
+
anyone to fix. Reporting it as broken just fills the vault-health count with noise.
|
|
62
|
+
|
|
63
|
+
Only ever consulted after the resolver has already failed, so an in-graph note always wins over
|
|
64
|
+
an out-of-scope namesake. **Any** candidate suppresses: out-of-scope notes are never link
|
|
65
|
+
targets, so ambiguity among them says nothing about whether the in-graph link is broken.
|
|
66
|
+
"""
|
|
67
|
+
target = _strip_display(display)
|
|
68
|
+
if not target:
|
|
69
|
+
return False
|
|
70
|
+
if "/" in target:
|
|
71
|
+
suffix = target.lower() + ".md"
|
|
72
|
+
return any(
|
|
73
|
+
path.lower().endswith(suffix) for paths in out_of_scope.values() for path in paths
|
|
74
|
+
)
|
|
75
|
+
return _normalize(target) in out_of_scope
|
|
76
|
+
|
|
77
|
+
|
|
43
78
|
def _is_intra_note_reference(display: str) -> bool:
|
|
44
79
|
"""True for a link that targets no note, e.g. ``[[#Heading]]`` or ``[[#^block]]``.
|
|
45
80
|
|
|
@@ -80,15 +115,10 @@ class NormalizeResolver:
|
|
|
80
115
|
return self._flat_cache
|
|
81
116
|
|
|
82
117
|
def resolve(self, display: str, catalog: dict[str, list[str]]) -> str | None:
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
#
|
|
86
|
-
display = display
|
|
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")]
|
|
118
|
+
# Alias ("Note|alias"), anchor ("Note#Section") and an explicit ".md" extension all name
|
|
119
|
+
# the same note. Stripped before both branches below: the path-suffix branch appends
|
|
120
|
+
# ".md" itself, and the bare branch would otherwise normalize to the key "note md".
|
|
121
|
+
display = _strip_display(display)
|
|
92
122
|
|
|
93
123
|
if "/" in display:
|
|
94
124
|
# Path-suffix resolution: find unique rel_path ending with "display.md"
|
|
@@ -142,13 +172,19 @@ class VaultGraph:
|
|
|
142
172
|
|
|
143
173
|
scoped = set(config.scoped_folders)
|
|
144
174
|
md_files: list[Path] = []
|
|
175
|
+
# Markdown that exists but is out of scope, normalized stem → rel_paths. Collected in
|
|
176
|
+
# this same walk (no extra I/O) so a link to one can be told apart from a link to a note
|
|
177
|
+
# that exists nowhere at all.
|
|
178
|
+
out_of_scope: dict[str, list[str]] = {}
|
|
145
179
|
for path in sorted(root.rglob("*.md")):
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
180
|
+
rel = path.relative_to(root)
|
|
181
|
+
rel_parts = rel.parts
|
|
182
|
+
if (
|
|
183
|
+
(scoped and rel_parts[0] not in scoped)
|
|
184
|
+
or any(p in excluded for p in rel_parts[:-1])
|
|
185
|
+
or path.name in rules
|
|
186
|
+
):
|
|
187
|
+
out_of_scope.setdefault(_normalize(path.stem), []).append(rel.as_posix())
|
|
152
188
|
continue
|
|
153
189
|
md_files.append(path)
|
|
154
190
|
|
|
@@ -167,7 +203,9 @@ class VaultGraph:
|
|
|
167
203
|
continue
|
|
168
204
|
target = resolver.resolve(display, catalog)
|
|
169
205
|
if target is None:
|
|
170
|
-
if _targets_non_note_file(display)
|
|
206
|
+
if _targets_non_note_file(display) or _targets_out_of_scope_note(
|
|
207
|
+
display, out_of_scope
|
|
208
|
+
):
|
|
171
209
|
continue # out of scope, not a broken note link
|
|
172
210
|
# Unresolvable OR ambiguous — the Resolver protocol conflates the two, and
|
|
173
211
|
# both are equally broken from a vault-health view. Record the raw display
|
|
@@ -233,3 +233,106 @@ class TestNonMarkdownTargets:
|
|
|
233
233
|
def test_mixed_note_keeps_only_the_missing_note(self, tmp_path):
|
|
234
234
|
_write(tmp_path, "a.md", "[[Chart.base]] and [[Missing]] and [[#local]].\n")
|
|
235
235
|
assert _build(tmp_path).unresolved == {"a.md": ["Missing"]}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TestPaddedDisplays:
|
|
239
|
+
"""Whitespace around the note part must not stop a link from resolving.
|
|
240
|
+
|
|
241
|
+
Real vaults contain column-aligned tables — ``[[folder/note | alias]]`` — where the
|
|
242
|
+
note part carries trailing spaces. Both resolution branches now see the stripped display
|
|
243
|
+
(13 such links on a live vault were reported broken while pointing at real notes).
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
def test_padded_bare_display_resolves(self, tmp_path):
|
|
247
|
+
_write(tmp_path, "a.md", "See [[ b | the alias]].\n")
|
|
248
|
+
_write(tmp_path, "b.md", "Target.\n")
|
|
249
|
+
graph = _build(tmp_path)
|
|
250
|
+
assert graph.unresolved == {}
|
|
251
|
+
assert graph.out_links["a.md"] == {"b.md"}
|
|
252
|
+
|
|
253
|
+
def test_padded_path_qualified_display_resolves(self, tmp_path):
|
|
254
|
+
_write(tmp_path, "a.md", "See [[docs/b | the alias]].\n")
|
|
255
|
+
_write(tmp_path, "docs/b.md", "Target.\n")
|
|
256
|
+
graph = _build(tmp_path)
|
|
257
|
+
assert graph.unresolved == {}
|
|
258
|
+
assert graph.out_links["a.md"] == {"docs/b.md"}
|
|
259
|
+
|
|
260
|
+
def test_padded_md_extension_still_strips(self, tmp_path):
|
|
261
|
+
_write(tmp_path, "a.md", "See [[b.md | alias]].\n")
|
|
262
|
+
_write(tmp_path, "b.md", "Target.\n")
|
|
263
|
+
assert _build(tmp_path).out_links["a.md"] == {"b.md"}
|
|
264
|
+
|
|
265
|
+
def test_a_padded_missing_note_is_still_unresolved(self, tmp_path):
|
|
266
|
+
_write(tmp_path, "a.md", "See [[ Nowhere | alias]].\n")
|
|
267
|
+
assert _build(tmp_path).unresolved == {"a.md": [" Nowhere | alias"]}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class TestOutOfScopeNoteTargets:
|
|
271
|
+
"""A link to a note that exists but is out of graph scope is out of scope, not broken.
|
|
272
|
+
|
|
273
|
+
build() drops unscoped folders, excluded dirs and rules files from the catalog and then
|
|
274
|
+
forgets they exist, so links to them fail the resolver. The link is correct — Obsidian
|
|
275
|
+
follows it — it just points somewhere graphmark deliberately does not index. On a real
|
|
276
|
+
vault these were 7% of the reported total ([[CLAUDE]], [[AGENTS]], a templates/ note).
|
|
277
|
+
|
|
278
|
+
Consulted only AFTER the resolver fails, so nothing that resolves in-graph is affected.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def test_link_to_a_rules_file_is_not_unresolved(self, tmp_path):
|
|
282
|
+
_write(tmp_path, "notes/a.md", "See [[CLAUDE]].\n")
|
|
283
|
+
_write(tmp_path, "CLAUDE.md", "Rules.\n")
|
|
284
|
+
graph = _build(tmp_path, rules_files=["CLAUDE.md"])
|
|
285
|
+
assert graph.unresolved == {}
|
|
286
|
+
assert graph.out_links["notes/a.md"] == set()
|
|
287
|
+
|
|
288
|
+
def test_link_into_an_unscoped_folder_is_not_unresolved(self, tmp_path):
|
|
289
|
+
_write(tmp_path, "docs/a.md", "See [[Intake Guide]].\n")
|
|
290
|
+
_write(tmp_path, "templates/Intake Guide.md", "Template.\n")
|
|
291
|
+
assert _build(tmp_path, scoped_folders=["docs"]).unresolved == {}
|
|
292
|
+
|
|
293
|
+
def test_link_into_an_excluded_dir_is_not_unresolved(self, tmp_path):
|
|
294
|
+
_write(tmp_path, "a.md", "See [[old thing]].\n")
|
|
295
|
+
_write(tmp_path, "archive/old thing.md", "Archived.\n")
|
|
296
|
+
assert _build(tmp_path, excluded_dirs=["archive"]).unresolved == {}
|
|
297
|
+
|
|
298
|
+
@pytest.mark.parametrize(
|
|
299
|
+
"display",
|
|
300
|
+
["CLAUDE", "claude", "CLAUDE|the rules", "CLAUDE#Non-negotiables", "CLAUDE.md"],
|
|
301
|
+
)
|
|
302
|
+
def test_alias_anchor_case_and_md_forms_all_suppress(self, tmp_path, display):
|
|
303
|
+
_write(tmp_path, "notes/a.md", f"See [[{display}]].\n")
|
|
304
|
+
_write(tmp_path, "CLAUDE.md", "Rules.\n")
|
|
305
|
+
assert _build(tmp_path, rules_files=["CLAUDE.md"]).unresolved == {}
|
|
306
|
+
|
|
307
|
+
def test_path_qualified_link_to_an_out_of_scope_note_is_not_unresolved(self, tmp_path):
|
|
308
|
+
_write(tmp_path, "docs/a.md", "See [[templates/data/Intake Guide]].\n")
|
|
309
|
+
_write(tmp_path, "templates/data/Intake Guide.md", "Template.\n")
|
|
310
|
+
assert _build(tmp_path, scoped_folders=["docs"]).unresolved == {}
|
|
311
|
+
|
|
312
|
+
def test_an_ambiguous_out_of_scope_stem_still_suppresses(self, tmp_path):
|
|
313
|
+
# Out-of-scope notes are never link targets, so ambiguity among them says nothing
|
|
314
|
+
# about whether the in-graph link is broken. Any candidate suppresses.
|
|
315
|
+
_write(tmp_path, "docs/a.md", "See [[shared]].\n")
|
|
316
|
+
_write(tmp_path, "templates/one/shared.md", "")
|
|
317
|
+
_write(tmp_path, "templates/two/shared.md", "")
|
|
318
|
+
assert _build(tmp_path, scoped_folders=["docs"]).unresolved == {}
|
|
319
|
+
|
|
320
|
+
def test_a_note_that_exists_nowhere_is_still_unresolved(self, tmp_path):
|
|
321
|
+
_write(tmp_path, "docs/a.md", "See [[Nowhere]].\n")
|
|
322
|
+
_write(tmp_path, "templates/Something Else.md", "")
|
|
323
|
+
assert _build(tmp_path, scoped_folders=["docs"]).unresolved == {"docs/a.md": ["Nowhere"]}
|
|
324
|
+
|
|
325
|
+
def test_an_in_scope_note_still_wins_over_an_out_of_scope_namesake(self, tmp_path):
|
|
326
|
+
# The rule only applies after the resolver fails, so the real in-graph note wins and
|
|
327
|
+
# keeps its edge.
|
|
328
|
+
_write(tmp_path, "docs/a.md", "See [[guide]].\n")
|
|
329
|
+
_write(tmp_path, "docs/guide.md", "In scope.\n")
|
|
330
|
+
_write(tmp_path, "templates/guide.md", "Out of scope.\n")
|
|
331
|
+
graph = _build(tmp_path, scoped_folders=["docs"])
|
|
332
|
+
assert graph.unresolved == {}
|
|
333
|
+
assert graph.out_links["docs/a.md"] == {"docs/guide.md"}
|
|
334
|
+
|
|
335
|
+
def test_mixed_note_keeps_only_the_real_break(self, tmp_path):
|
|
336
|
+
_write(tmp_path, "docs/a.md", "[[Intake Guide]] and [[Missing]].\n")
|
|
337
|
+
_write(tmp_path, "templates/Intake Guide.md", "Template.\n")
|
|
338
|
+
assert _build(tmp_path, scoped_folders=["docs"]).unresolved == {"docs/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.3 → graphmark-0.3.4}/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
|