mdbind 0.1.1__tar.gz → 0.1.2__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.
- mdbind-0.1.2/PKG-INFO +227 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/pyproject.toml +8 -1
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/cli.py +37 -37
- mdbind-0.1.2/src/mdbind.egg-info/PKG-INFO +227 -0
- mdbind-0.1.1/PKG-INFO +0 -9
- mdbind-0.1.1/src/mdbind.egg-info/PKG-INFO +0 -9
- {mdbind-0.1.1 → mdbind-0.1.2}/README.md +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/setup.cfg +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/__init__.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/cache.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/composer.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/cycle.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/directives.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/index.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/models.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind/parser.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind.egg-info/SOURCES.txt +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind.egg-info/dependency_links.txt +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind.egg-info/entry_points.txt +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind.egg-info/requires.txt +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/src/mdbind.egg-info/top_level.txt +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cache.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cli_compose.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cli_get.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cli_ia_v1.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cli_ia_v2.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cli_tree.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cli_validate.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_cycle_detection.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_directives.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_examples.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_index.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_models.py +0 -0
- {mdbind-0.1.1 → mdbind-0.1.2}/tests/test_parser.py +0 -0
mdbind-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mdbind
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: MdBind — Structured memory in plain Markdown
|
|
5
|
+
Project-URL: Homepage, https://github.com/seu-usuario/mdbind
|
|
6
|
+
Project-URL: Repository, https://github.com/seu-usuario/mdbind
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/seu-usuario/mdbind/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/seu-usuario/mdbind#readme
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Requires-Dist: markdown-it-py>=3.0
|
|
13
|
+
Requires-Dist: typer>=0.12
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
|
|
16
|
+
<div align="center">
|
|
17
|
+
|
|
18
|
+
# MdBind
|
|
19
|
+
|
|
20
|
+
**Structured memory in plain Markdown.**
|
|
21
|
+
|
|
22
|
+
Transform your Markdown files into a navigable knowledge graph —
|
|
23
|
+
without databases, embeddings, or proprietary formats.
|
|
24
|
+
|
|
25
|
+
[](https://www.python.org/)
|
|
26
|
+
[](#development)
|
|
27
|
+
[](#installation)
|
|
28
|
+
[](#license)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What is MdBind?
|
|
35
|
+
|
|
36
|
+
MdBind turns Markdown files into a **directed knowledge graph** where every section is an addressable node with stable identity, metadata, and explicit relationships.
|
|
37
|
+
|
|
38
|
+
Your files stay **plain text, Git-friendly, and human-readable** — but gain:
|
|
39
|
+
|
|
40
|
+
- Graph traversal and dependency resolution
|
|
41
|
+
- Stable URIs that survive reorganization
|
|
42
|
+
- Structured metadata queries
|
|
43
|
+
- AI-oriented context retrieval with bounded token consumption
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Why not embeddings?
|
|
48
|
+
|
|
49
|
+
| Approach | Inspectable | Versionable | Deterministic | Human-readable |
|
|
50
|
+
|---|:---:|:---:|:---:|:---:|
|
|
51
|
+
| Vector databases | ✗ | ✗ | ✗ | ✗ |
|
|
52
|
+
| Proprietary stores | ✗ | partial | partial | ✗ |
|
|
53
|
+
| **MdBind** | ✓ | ✓ | ✓ | ✓ |
|
|
54
|
+
|
|
55
|
+
Every node, every edge, every relationship is visible in the source file. What an agent reads, a human can audit.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 1. Clone and install
|
|
63
|
+
git clone <repo-url> && cd mdbind
|
|
64
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
65
|
+
pip install -e .
|
|
66
|
+
|
|
67
|
+
# 2. Point it at your docs
|
|
68
|
+
mdb validate --root docs/
|
|
69
|
+
|
|
70
|
+
# 3. Query the graph
|
|
71
|
+
mdb get docs/auth.md#auth --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## See it in action
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Navigate the dependency tree
|
|
80
|
+
$ mdb tree docs/auth.md#auth --root docs/
|
|
81
|
+
|
|
82
|
+
auth [docs/auth.md]
|
|
83
|
+
├── jwt [include] docs/security.md#jwt
|
|
84
|
+
└── permissions [ref] docs/users.md#permissions
|
|
85
|
+
|
|
86
|
+
# Compose a unified document by expanding all @include directives
|
|
87
|
+
$ mdb compose docs/auth.md#auth --root docs/ --depth 2
|
|
88
|
+
|
|
89
|
+
# Find everything that depends on a node (reverse BFS)
|
|
90
|
+
$ mdb impact docs/auth.md#auth --root docs/ --json
|
|
91
|
+
|
|
92
|
+
# Boolean metadata query
|
|
93
|
+
$ mdb query "tag:api AND NOT status=obsolete" --root docs/ --json
|
|
94
|
+
|
|
95
|
+
# Bounded context for LLM consumption
|
|
96
|
+
$ mdb context-compose docs/auth.md#auth --root docs/ --depth 2 --token-limit 2000 --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Syntax
|
|
102
|
+
|
|
103
|
+
### Declaring a section
|
|
104
|
+
|
|
105
|
+
A section is a Markdown heading followed immediately by a YAML block with a `section:` field:
|
|
106
|
+
|
|
107
|
+
````markdown
|
|
108
|
+
## Authentication
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
section: auth
|
|
112
|
+
title: Authentication
|
|
113
|
+
type: domain
|
|
114
|
+
owner: security-team
|
|
115
|
+
tags: [auth, core]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Authentication is responsible for user identity.
|
|
119
|
+
|
|
120
|
+
[@include: JWT handling](security.md#jwt)
|
|
121
|
+
|
|
122
|
+
See also: [@ref: permissions model](users.md#permissions)
|
|
123
|
+
````
|
|
124
|
+
|
|
125
|
+
- The YAML block must be the **first element** after the heading
|
|
126
|
+
- `section:` is mandatory and must be **unique per repository**
|
|
127
|
+
- Any additional fields are preserved as queryable metadata
|
|
128
|
+
|
|
129
|
+
### Directives (graph edges)
|
|
130
|
+
|
|
131
|
+
```markdown
|
|
132
|
+
[@include: label](path/to/file.md#section-id) <!-- expands inline during compose -->
|
|
133
|
+
[@ref: label](path/to/file.md#section-id) <!-- records dependency, no expansion -->
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Directives are standard Markdown links — they render correctly in any Markdown viewer.
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
auth
|
|
140
|
+
├── jwt [include]
|
|
141
|
+
└── permissions [ref]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Commands
|
|
147
|
+
|
|
148
|
+
### Quick reference
|
|
149
|
+
|
|
150
|
+
| Command | Description |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `mdb get <URI>` | Extract a section with full documentary fidelity |
|
|
153
|
+
| `mdb tree <URI>` | Visual dependency hierarchy |
|
|
154
|
+
| `mdb compose <URI>` | Materialize a unified document (expands `@include`) |
|
|
155
|
+
| `mdb validate` | Check integrity: broken refs, cycles, duplicate IDs |
|
|
156
|
+
| `mdb context <URI>` | Metadata + immediate 1-hop neighborhood |
|
|
157
|
+
| `mdb backlinks <URI>` | All sections that reference this URI |
|
|
158
|
+
| `mdb search <predicate>` | Search sections by metadata |
|
|
159
|
+
| `mdb impact <URI>` | All nodes that depend on this URI (reverse BFS) |
|
|
160
|
+
| `mdb neighbors <URI>` | All nodes reachable within N hops |
|
|
161
|
+
| `mdb explain <URI_A> <URI_B>` | All directed paths between two nodes |
|
|
162
|
+
| `mdb diff` | Structural graph diff against a git reference |
|
|
163
|
+
| `mdb query <expression>` | Boolean metadata query (`AND`, `OR`, `NOT`) |
|
|
164
|
+
| `mdb context-compose <URI>` | Bounded materialization for LLM consumption |
|
|
165
|
+
|
|
166
|
+
All commands accept `--json` for machine-readable output.
|
|
167
|
+
All outputs are deterministic and JSON-serializable. All URIs are stable across sessions.
|
|
168
|
+
|
|
169
|
+
### Selected examples
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Validate an entire repository
|
|
173
|
+
mdb validate --root docs/ --json
|
|
174
|
+
|
|
175
|
+
# 1-hop neighborhood of a node
|
|
176
|
+
mdb context docs/auth.md#auth --root docs/ --json
|
|
177
|
+
|
|
178
|
+
# Find all sections tagged api that are not obsolete
|
|
179
|
+
mdb query "tag:api AND NOT status=obsolete" --root docs/ --json
|
|
180
|
+
|
|
181
|
+
# Bounded context for LLM — depth 2, max 2000 tokens
|
|
182
|
+
mdb context-compose docs/auth.md#auth --root docs/ --depth 2 --token-limit 2000 --json
|
|
183
|
+
|
|
184
|
+
# What changed structurally since the last commit?
|
|
185
|
+
mdb diff --root docs/ --since HEAD~1 --json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Philosophy
|
|
191
|
+
|
|
192
|
+
Five principles behind every design decision:
|
|
193
|
+
|
|
194
|
+
1. **Markdown is the source of truth** — no proprietary formats, no hidden state
|
|
195
|
+
2. **Knowledge should be inspectable** — every node, every edge, every relationship is visible in the source
|
|
196
|
+
3. **Relationships should be explicit** — `@include` and `@ref` are first-class graph primitives
|
|
197
|
+
4. **Stable identifiers are better than headings** — `file.md#id` survives reorganization
|
|
198
|
+
5. **AI memory should remain human-readable** — what an agent reads, a human can audit
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Examples
|
|
203
|
+
|
|
204
|
+
See the [examples/](examples/) directory for a complete working knowledge base demonstrating sections, directives, composition, and graph traversal.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Install in editable mode
|
|
212
|
+
pip install -e .
|
|
213
|
+
|
|
214
|
+
# Run the full test suite
|
|
215
|
+
python -m pytest
|
|
216
|
+
|
|
217
|
+
# Run a specific module
|
|
218
|
+
python -m pytest tests/test_cli_validate.py -v
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
> 209 tests, 0 failures.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT
|
|
@@ -4,8 +4,9 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mdbind"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "MdBind — Structured memory in plain Markdown"
|
|
9
|
+
readme = "README.md"
|
|
9
10
|
requires-python = ">=3.11"
|
|
10
11
|
dependencies = [
|
|
11
12
|
"pydantic>=2.0",
|
|
@@ -22,3 +23,9 @@ where = ["src"]
|
|
|
22
23
|
|
|
23
24
|
[tool.pytest.ini_options]
|
|
24
25
|
testpaths = ["tests"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/seu-usuario/mdbind"
|
|
29
|
+
Repository = "https://github.com/seu-usuario/mdbind"
|
|
30
|
+
"Bug Tracker" = "https://github.com/seu-usuario/mdbind/issues"
|
|
31
|
+
Documentation = "https://github.com/seu-usuario/mdbind#readme"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
|
-
CLI
|
|
2
|
+
CLI for mdbind — main entrypoint.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
get <URI>
|
|
6
|
-
tree <URI>
|
|
7
|
-
compose <URI>
|
|
4
|
+
Commands:
|
|
5
|
+
get <URI> Extract a section with documentary fidelity (raw lines)
|
|
6
|
+
tree <URI> Display visual hierarchy of dependencies
|
|
7
|
+
compose <URI> Materialize unified document (B-007)
|
|
8
8
|
"""
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
@@ -43,23 +43,23 @@ def _split_uri(uri: str) -> tuple[str, str]:
|
|
|
43
43
|
|
|
44
44
|
@app.command()
|
|
45
45
|
def get(
|
|
46
|
-
uri: str = typer.Argument(..., help="URI
|
|
46
|
+
uri: str = typer.Argument(..., help="URI of the section in the format file.md#id"),
|
|
47
47
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
48
48
|
) -> None:
|
|
49
49
|
"""
|
|
50
|
-
|
|
50
|
+
Extract a section with 100%% documentary fidelity (raw lines from the source file).
|
|
51
51
|
"""
|
|
52
52
|
file_path_str, section_id = _split_uri(uri)
|
|
53
53
|
file_path = Path(file_path_str).resolve()
|
|
54
54
|
|
|
55
55
|
if not file_path.exists():
|
|
56
|
-
typer.echo(f"
|
|
56
|
+
typer.echo(f"Error: file not found: '{file_path}'", err=True)
|
|
57
57
|
raise typer.Exit(code=1)
|
|
58
58
|
|
|
59
59
|
try:
|
|
60
60
|
sections = parse_file(file_path)
|
|
61
61
|
except ParseError as exc:
|
|
62
|
-
typer.echo(f"
|
|
62
|
+
typer.echo(f"Parse error: {exc}", err=True)
|
|
63
63
|
raise typer.Exit(code=1)
|
|
64
64
|
|
|
65
65
|
matched = next(
|
|
@@ -69,15 +69,15 @@ def get(
|
|
|
69
69
|
|
|
70
70
|
if matched is None:
|
|
71
71
|
typer.echo(
|
|
72
|
-
f"
|
|
72
|
+
f"Error: section '{section_id}' not found in '{file_path}'",
|
|
73
73
|
err=True,
|
|
74
74
|
)
|
|
75
75
|
raise typer.Exit(code=1)
|
|
76
76
|
|
|
77
|
-
#
|
|
77
|
+
# Documentary slicing: preserves the exact text from the source file
|
|
78
78
|
lines = file_path.read_text(encoding="utf-8").splitlines(keepends=True)
|
|
79
79
|
start = matched.raw.source_start_line - 1 # base-0
|
|
80
|
-
end = matched.raw.source_end_line # slice
|
|
80
|
+
end = matched.raw.source_end_line # exclusive slice = last line inclusive
|
|
81
81
|
|
|
82
82
|
output = "".join(lines[start:end])
|
|
83
83
|
# Garantir newline final sem adicionar extra
|
|
@@ -103,23 +103,23 @@ def get(
|
|
|
103
103
|
|
|
104
104
|
@app.command()
|
|
105
105
|
def tree(
|
|
106
|
-
uri: str = typer.Argument(..., help="URI
|
|
106
|
+
uri: str = typer.Argument(..., help="URI of the section in the format file.md#id"),
|
|
107
107
|
root: Optional[Path] = typer.Option(
|
|
108
108
|
None, "--root", "-r",
|
|
109
|
-
help="
|
|
109
|
+
help="Root directory of the repository (default: directory of the file).",
|
|
110
110
|
),
|
|
111
111
|
refs: bool = typer.Option(
|
|
112
112
|
False, "--refs",
|
|
113
|
-
help="
|
|
113
|
+
help="Display backlinks (who depends on this section).",
|
|
114
114
|
),
|
|
115
115
|
depth: Optional[int] = typer.Option(
|
|
116
116
|
None, "--depth", "-d",
|
|
117
|
-
help="
|
|
117
|
+
help="Maximum depth of the tree (default: unlimited).",
|
|
118
118
|
),
|
|
119
119
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
120
120
|
) -> None:
|
|
121
121
|
"""
|
|
122
|
-
|
|
122
|
+
Display the visual hierarchy of dependencies of a section.
|
|
123
123
|
"""
|
|
124
124
|
from mdbind.index import index_repository
|
|
125
125
|
|
|
@@ -131,14 +131,14 @@ def tree(
|
|
|
131
131
|
try:
|
|
132
132
|
graph = index_repository(repo_root)
|
|
133
133
|
except ParseError as exc:
|
|
134
|
-
typer.echo(f"
|
|
134
|
+
typer.echo(f"Parse error: {exc}", err=True)
|
|
135
135
|
raise typer.Exit(code=1)
|
|
136
136
|
|
|
137
|
-
#
|
|
137
|
+
# Build absolute URI for lookup
|
|
138
138
|
abs_uri = str(file_path) + "#" + section_id
|
|
139
139
|
|
|
140
140
|
if abs_uri not in graph.index.sections:
|
|
141
|
-
typer.echo(f"
|
|
141
|
+
typer.echo(f"Error: URI '{abs_uri}' not found in the index.", err=True)
|
|
142
142
|
raise typer.Exit(code=1)
|
|
143
143
|
|
|
144
144
|
if json_output:
|
|
@@ -160,7 +160,7 @@ def _label(uri: str, graph) -> str:
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
def _print_tree_outgoing(uri: str, graph, prefix: str, visited: set, depth: Optional[int] = None) -> None:
|
|
163
|
-
marker = "(
|
|
163
|
+
marker = "(cycle)" if uri in visited else ""
|
|
164
164
|
typer.echo(f"{prefix}{_label(uri, graph)} {marker}".rstrip())
|
|
165
165
|
if uri in visited:
|
|
166
166
|
return
|
|
@@ -175,7 +175,7 @@ def _print_tree_outgoing(uri: str, graph, prefix: str, visited: set, depth: Opti
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
def _print_tree_incoming(uri: str, graph, prefix: str, visited: set, depth: Optional[int] = None) -> None:
|
|
178
|
-
marker = "(
|
|
178
|
+
marker = "(cycle)" if uri in visited else ""
|
|
179
179
|
typer.echo(f"{prefix}{_label(uri, graph)} {marker}".rstrip())
|
|
180
180
|
if uri in visited:
|
|
181
181
|
return
|
|
@@ -233,21 +233,21 @@ def _build_tree_incoming(uri: str, graph, visited: set, depth: Optional[int] = N
|
|
|
233
233
|
|
|
234
234
|
@app.command()
|
|
235
235
|
def compose(
|
|
236
|
-
uri: str = typer.Argument(..., help="URI
|
|
236
|
+
uri: str = typer.Argument(..., help="URI of the root section in the format file.md#id"),
|
|
237
237
|
root: Optional[Path] = typer.Option(
|
|
238
238
|
None, "--root", "-r",
|
|
239
|
-
help="
|
|
239
|
+
help="Root directory of the repository (default: directory of the file).",
|
|
240
240
|
),
|
|
241
|
-
strict: bool = typer.Option(False, "--strict", help="
|
|
242
|
-
deduplicate: bool = typer.Option(False, "--deduplicate", help="
|
|
243
|
-
json_output: bool = typer.Option(False, "--json", help="
|
|
241
|
+
strict: bool = typer.Option(False, "--strict", help="Abort on unresolved URI."),
|
|
242
|
+
deduplicate: bool = typer.Option(False, "--deduplicate", help="Deduplicate repeated nodes."),
|
|
243
|
+
json_output: bool = typer.Option(False, "--json", help="Export as structured JSON."),
|
|
244
244
|
depth: Optional[int] = typer.Option(
|
|
245
245
|
None, "--depth", "-d",
|
|
246
|
-
help="
|
|
246
|
+
help="Maximum depth of @include expansion (default: unlimited).",
|
|
247
247
|
),
|
|
248
248
|
) -> None:
|
|
249
249
|
"""
|
|
250
|
-
|
|
250
|
+
Materializes a unified Markdown document by recursively expanding @include.
|
|
251
251
|
"""
|
|
252
252
|
import json as json_mod
|
|
253
253
|
from mdbind.composer import compose as do_compose
|
|
@@ -257,7 +257,7 @@ def compose(
|
|
|
257
257
|
file_path = Path(file_path_str).resolve()
|
|
258
258
|
|
|
259
259
|
if not file_path.exists():
|
|
260
|
-
typer.echo(f"
|
|
260
|
+
typer.echo(f"Error: file not found: '{file_path}'", err=True)
|
|
261
261
|
raise typer.Exit(code=1)
|
|
262
262
|
|
|
263
263
|
repo_root = root.resolve() if root else file_path.parent
|
|
@@ -265,13 +265,13 @@ def compose(
|
|
|
265
265
|
try:
|
|
266
266
|
graph = index_repository(repo_root)
|
|
267
267
|
except ParseError as exc:
|
|
268
|
-
typer.echo(f"
|
|
268
|
+
typer.echo(f"Parse error: {exc}", err=True)
|
|
269
269
|
raise typer.Exit(code=1)
|
|
270
270
|
|
|
271
271
|
abs_uri = str(file_path) + "#" + section_id
|
|
272
272
|
|
|
273
273
|
if abs_uri not in graph.index.sections:
|
|
274
|
-
typer.echo(f"
|
|
274
|
+
typer.echo(f"Error: URI '{abs_uri}' not found in the index.", err=True)
|
|
275
275
|
raise typer.Exit(code=1)
|
|
276
276
|
|
|
277
277
|
collected_warnings: list[str] = []
|
|
@@ -285,11 +285,11 @@ def compose(
|
|
|
285
285
|
depth=depth,
|
|
286
286
|
)
|
|
287
287
|
except ValueError as exc:
|
|
288
|
-
typer.echo(f"
|
|
288
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
289
289
|
raise typer.Exit(code=1)
|
|
290
290
|
|
|
291
291
|
for w in collected_warnings:
|
|
292
|
-
typer.echo(f"
|
|
292
|
+
typer.echo(f"Warning: {w}", err=True)
|
|
293
293
|
|
|
294
294
|
if json_output:
|
|
295
295
|
typer.echo(json_mod.dumps({"uri": abs_uri, "content": result}, ensure_ascii=False))
|
|
@@ -305,12 +305,12 @@ def compose(
|
|
|
305
305
|
def validate(
|
|
306
306
|
root: Optional[Path] = typer.Option(
|
|
307
307
|
None, "--root", "-r",
|
|
308
|
-
help="
|
|
308
|
+
help="Root directory of the repository (default: current directory).",
|
|
309
309
|
),
|
|
310
|
-
json_output: bool = typer.Option(False, "--json", help="
|
|
310
|
+
json_output: bool = typer.Option(False, "--json", help="Export result as JSON."),
|
|
311
311
|
) -> None:
|
|
312
312
|
"""
|
|
313
|
-
|
|
313
|
+
Verifies the structural integrity of the Markdown graph repository.
|
|
314
314
|
|
|
315
315
|
Checks: broken refs/includes, duplicate section IDs, include cycles,
|
|
316
316
|
sections without required payload.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mdbind
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: MdBind — Structured memory in plain Markdown
|
|
5
|
+
Project-URL: Homepage, https://github.com/seu-usuario/mdbind
|
|
6
|
+
Project-URL: Repository, https://github.com/seu-usuario/mdbind
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/seu-usuario/mdbind/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/seu-usuario/mdbind#readme
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Requires-Dist: markdown-it-py>=3.0
|
|
13
|
+
Requires-Dist: typer>=0.12
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
|
|
16
|
+
<div align="center">
|
|
17
|
+
|
|
18
|
+
# MdBind
|
|
19
|
+
|
|
20
|
+
**Structured memory in plain Markdown.**
|
|
21
|
+
|
|
22
|
+
Transform your Markdown files into a navigable knowledge graph —
|
|
23
|
+
without databases, embeddings, or proprietary formats.
|
|
24
|
+
|
|
25
|
+
[](https://www.python.org/)
|
|
26
|
+
[](#development)
|
|
27
|
+
[](#installation)
|
|
28
|
+
[](#license)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What is MdBind?
|
|
35
|
+
|
|
36
|
+
MdBind turns Markdown files into a **directed knowledge graph** where every section is an addressable node with stable identity, metadata, and explicit relationships.
|
|
37
|
+
|
|
38
|
+
Your files stay **plain text, Git-friendly, and human-readable** — but gain:
|
|
39
|
+
|
|
40
|
+
- Graph traversal and dependency resolution
|
|
41
|
+
- Stable URIs that survive reorganization
|
|
42
|
+
- Structured metadata queries
|
|
43
|
+
- AI-oriented context retrieval with bounded token consumption
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Why not embeddings?
|
|
48
|
+
|
|
49
|
+
| Approach | Inspectable | Versionable | Deterministic | Human-readable |
|
|
50
|
+
|---|:---:|:---:|:---:|:---:|
|
|
51
|
+
| Vector databases | ✗ | ✗ | ✗ | ✗ |
|
|
52
|
+
| Proprietary stores | ✗ | partial | partial | ✗ |
|
|
53
|
+
| **MdBind** | ✓ | ✓ | ✓ | ✓ |
|
|
54
|
+
|
|
55
|
+
Every node, every edge, every relationship is visible in the source file. What an agent reads, a human can audit.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 1. Clone and install
|
|
63
|
+
git clone <repo-url> && cd mdbind
|
|
64
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
65
|
+
pip install -e .
|
|
66
|
+
|
|
67
|
+
# 2. Point it at your docs
|
|
68
|
+
mdb validate --root docs/
|
|
69
|
+
|
|
70
|
+
# 3. Query the graph
|
|
71
|
+
mdb get docs/auth.md#auth --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## See it in action
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Navigate the dependency tree
|
|
80
|
+
$ mdb tree docs/auth.md#auth --root docs/
|
|
81
|
+
|
|
82
|
+
auth [docs/auth.md]
|
|
83
|
+
├── jwt [include] docs/security.md#jwt
|
|
84
|
+
└── permissions [ref] docs/users.md#permissions
|
|
85
|
+
|
|
86
|
+
# Compose a unified document by expanding all @include directives
|
|
87
|
+
$ mdb compose docs/auth.md#auth --root docs/ --depth 2
|
|
88
|
+
|
|
89
|
+
# Find everything that depends on a node (reverse BFS)
|
|
90
|
+
$ mdb impact docs/auth.md#auth --root docs/ --json
|
|
91
|
+
|
|
92
|
+
# Boolean metadata query
|
|
93
|
+
$ mdb query "tag:api AND NOT status=obsolete" --root docs/ --json
|
|
94
|
+
|
|
95
|
+
# Bounded context for LLM consumption
|
|
96
|
+
$ mdb context-compose docs/auth.md#auth --root docs/ --depth 2 --token-limit 2000 --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Syntax
|
|
102
|
+
|
|
103
|
+
### Declaring a section
|
|
104
|
+
|
|
105
|
+
A section is a Markdown heading followed immediately by a YAML block with a `section:` field:
|
|
106
|
+
|
|
107
|
+
````markdown
|
|
108
|
+
## Authentication
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
section: auth
|
|
112
|
+
title: Authentication
|
|
113
|
+
type: domain
|
|
114
|
+
owner: security-team
|
|
115
|
+
tags: [auth, core]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Authentication is responsible for user identity.
|
|
119
|
+
|
|
120
|
+
[@include: JWT handling](security.md#jwt)
|
|
121
|
+
|
|
122
|
+
See also: [@ref: permissions model](users.md#permissions)
|
|
123
|
+
````
|
|
124
|
+
|
|
125
|
+
- The YAML block must be the **first element** after the heading
|
|
126
|
+
- `section:` is mandatory and must be **unique per repository**
|
|
127
|
+
- Any additional fields are preserved as queryable metadata
|
|
128
|
+
|
|
129
|
+
### Directives (graph edges)
|
|
130
|
+
|
|
131
|
+
```markdown
|
|
132
|
+
[@include: label](path/to/file.md#section-id) <!-- expands inline during compose -->
|
|
133
|
+
[@ref: label](path/to/file.md#section-id) <!-- records dependency, no expansion -->
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Directives are standard Markdown links — they render correctly in any Markdown viewer.
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
auth
|
|
140
|
+
├── jwt [include]
|
|
141
|
+
└── permissions [ref]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Commands
|
|
147
|
+
|
|
148
|
+
### Quick reference
|
|
149
|
+
|
|
150
|
+
| Command | Description |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `mdb get <URI>` | Extract a section with full documentary fidelity |
|
|
153
|
+
| `mdb tree <URI>` | Visual dependency hierarchy |
|
|
154
|
+
| `mdb compose <URI>` | Materialize a unified document (expands `@include`) |
|
|
155
|
+
| `mdb validate` | Check integrity: broken refs, cycles, duplicate IDs |
|
|
156
|
+
| `mdb context <URI>` | Metadata + immediate 1-hop neighborhood |
|
|
157
|
+
| `mdb backlinks <URI>` | All sections that reference this URI |
|
|
158
|
+
| `mdb search <predicate>` | Search sections by metadata |
|
|
159
|
+
| `mdb impact <URI>` | All nodes that depend on this URI (reverse BFS) |
|
|
160
|
+
| `mdb neighbors <URI>` | All nodes reachable within N hops |
|
|
161
|
+
| `mdb explain <URI_A> <URI_B>` | All directed paths between two nodes |
|
|
162
|
+
| `mdb diff` | Structural graph diff against a git reference |
|
|
163
|
+
| `mdb query <expression>` | Boolean metadata query (`AND`, `OR`, `NOT`) |
|
|
164
|
+
| `mdb context-compose <URI>` | Bounded materialization for LLM consumption |
|
|
165
|
+
|
|
166
|
+
All commands accept `--json` for machine-readable output.
|
|
167
|
+
All outputs are deterministic and JSON-serializable. All URIs are stable across sessions.
|
|
168
|
+
|
|
169
|
+
### Selected examples
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Validate an entire repository
|
|
173
|
+
mdb validate --root docs/ --json
|
|
174
|
+
|
|
175
|
+
# 1-hop neighborhood of a node
|
|
176
|
+
mdb context docs/auth.md#auth --root docs/ --json
|
|
177
|
+
|
|
178
|
+
# Find all sections tagged api that are not obsolete
|
|
179
|
+
mdb query "tag:api AND NOT status=obsolete" --root docs/ --json
|
|
180
|
+
|
|
181
|
+
# Bounded context for LLM — depth 2, max 2000 tokens
|
|
182
|
+
mdb context-compose docs/auth.md#auth --root docs/ --depth 2 --token-limit 2000 --json
|
|
183
|
+
|
|
184
|
+
# What changed structurally since the last commit?
|
|
185
|
+
mdb diff --root docs/ --since HEAD~1 --json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Philosophy
|
|
191
|
+
|
|
192
|
+
Five principles behind every design decision:
|
|
193
|
+
|
|
194
|
+
1. **Markdown is the source of truth** — no proprietary formats, no hidden state
|
|
195
|
+
2. **Knowledge should be inspectable** — every node, every edge, every relationship is visible in the source
|
|
196
|
+
3. **Relationships should be explicit** — `@include` and `@ref` are first-class graph primitives
|
|
197
|
+
4. **Stable identifiers are better than headings** — `file.md#id` survives reorganization
|
|
198
|
+
5. **AI memory should remain human-readable** — what an agent reads, a human can audit
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Examples
|
|
203
|
+
|
|
204
|
+
See the [examples/](examples/) directory for a complete working knowledge base demonstrating sections, directives, composition, and graph traversal.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Install in editable mode
|
|
212
|
+
pip install -e .
|
|
213
|
+
|
|
214
|
+
# Run the full test suite
|
|
215
|
+
python -m pytest
|
|
216
|
+
|
|
217
|
+
# Run a specific module
|
|
218
|
+
python -m pytest tests/test_cli_validate.py -v
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
> 209 tests, 0 failures.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT
|
mdbind-0.1.1/PKG-INFO
DELETED
|
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
|