@henryavila/mdprobe 0.1.0

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.
package/schema.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "mdprobe annotation sidecar",
4
+ "description": "Schema for YAML sidecar annotation files used by mdprobe",
5
+ "type": "object",
6
+ "required": ["version", "source", "source_hash", "annotations"],
7
+ "properties": {
8
+ "version": {
9
+ "type": "integer"
10
+ },
11
+ "source": {
12
+ "type": "string"
13
+ },
14
+ "source_hash": {
15
+ "type": "string"
16
+ },
17
+ "sections": {
18
+ "type": "array",
19
+ "items": {
20
+ "$ref": "#/definitions/section"
21
+ }
22
+ },
23
+ "annotations": {
24
+ "type": "array",
25
+ "items": {
26
+ "$ref": "#/definitions/annotation"
27
+ }
28
+ }
29
+ },
30
+ "definitions": {
31
+ "position": {
32
+ "type": "object",
33
+ "required": ["startLine", "startColumn", "endLine", "endColumn"],
34
+ "properties": {
35
+ "startLine": { "type": "integer" },
36
+ "startColumn": { "type": "integer" },
37
+ "endLine": { "type": "integer" },
38
+ "endColumn": { "type": "integer" }
39
+ }
40
+ },
41
+ "quote": {
42
+ "type": "object",
43
+ "required": ["exact", "prefix", "suffix"],
44
+ "properties": {
45
+ "exact": { "type": "string" },
46
+ "prefix": { "type": "string" },
47
+ "suffix": { "type": "string" }
48
+ }
49
+ },
50
+ "selectors": {
51
+ "type": "object",
52
+ "required": ["position", "quote"],
53
+ "properties": {
54
+ "position": { "$ref": "#/definitions/position" },
55
+ "quote": { "$ref": "#/definitions/quote" }
56
+ }
57
+ },
58
+ "reply": {
59
+ "type": "object",
60
+ "required": ["author", "comment", "created_at"],
61
+ "properties": {
62
+ "author": { "type": "string" },
63
+ "comment": { "type": "string" },
64
+ "created_at": { "type": "string" }
65
+ }
66
+ },
67
+ "annotation": {
68
+ "type": "object",
69
+ "required": ["id", "selectors", "comment", "tag", "status", "author", "created_at", "updated_at", "replies"],
70
+ "properties": {
71
+ "id": { "type": "string" },
72
+ "selectors": { "$ref": "#/definitions/selectors" },
73
+ "comment": { "type": "string" },
74
+ "tag": {
75
+ "type": "string",
76
+ "enum": ["bug", "question", "suggestion", "nitpick"]
77
+ },
78
+ "status": {
79
+ "type": "string",
80
+ "enum": ["open", "resolved"]
81
+ },
82
+ "author": { "type": "string" },
83
+ "created_at": { "type": "string" },
84
+ "updated_at": { "type": "string" },
85
+ "replies": {
86
+ "type": "array",
87
+ "items": { "$ref": "#/definitions/reply" }
88
+ }
89
+ }
90
+ },
91
+ "section": {
92
+ "type": "object",
93
+ "required": ["heading", "status"],
94
+ "properties": {
95
+ "heading": { "type": "string" },
96
+ "level": { "type": "integer", "minimum": 1, "maximum": 6 },
97
+ "status": {
98
+ "type": "string",
99
+ "enum": ["approved", "rejected", "pending"]
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,358 @@
1
+ ---
2
+ name: mdprobe
3
+ description: Use mdprobe to render markdown in the browser and collect structured human feedback (annotations, section approvals) via YAML sidecar files
4
+ ---
5
+
6
+ # mdprobe — Markdown Viewer & Reviewer
7
+
8
+ Render markdown in the browser. Collect structured feedback from humans. Read it back as YAML.
9
+
10
+ ## When to Use
11
+
12
+ - Output longer than 40-60 lines (specs, RFCs, ADRs, design docs)
13
+ - Tables, Mermaid diagrams, math/LaTeX, syntax-highlighted code
14
+ - When you need the human to **review and annotate** before you proceed
15
+ - When you need **section-level approval** (approved/rejected per heading)
16
+
17
+ ## When NOT to Use
18
+
19
+ - Short answers or code snippets (< 40 lines)
20
+ - Simple text responses
21
+ - Interactive debugging sessions
22
+
23
+ ---
24
+
25
+ ## View Mode — render and continue working
26
+
27
+ Write markdown to a file, launch mdprobe in the background. The human reads while you keep working.
28
+
29
+ ```bash
30
+ # Write your output
31
+ cat > output.md << 'EOF'
32
+ # Your spec here
33
+ EOF
34
+
35
+ # Launch viewer (browser opens automatically, process runs in background)
36
+ mdprobe output.md &
37
+ ```
38
+
39
+ Use `run_in_background: true` when calling via Bash tool. Add `--no-open` if you don't want the browser to auto-open.
40
+
41
+ The server watches for file changes — if you update the `.md` file, the browser hot-reloads automatically.
42
+
43
+ ## Review Mode — block until human finishes
44
+
45
+ When you need the human to review and annotate before you continue:
46
+
47
+ ```bash
48
+ # This BLOCKS until the human clicks "Finish Review" in the browser
49
+ mdprobe spec.md --once
50
+ ```
51
+
52
+ The process prints paths to generated `.annotations.yaml` files on exit. Exit code 0 means review complete.
53
+
54
+ ### Full agent workflow
55
+
56
+ ```
57
+ 1. Agent writes spec.md
58
+ 2. Agent runs: mdprobe spec.md --once (process BLOCKS here)
59
+ 3. Human opens browser, reads the rendered markdown
60
+ 4. Human selects text → adds annotations (bug, question, suggestion, nitpick)
61
+ 5. Human approves/rejects sections via heading buttons
62
+ 6. Human clicks "Finish Review"
63
+ 7. Process unblocks, prints YAML paths to stdout
64
+ 8. Agent reads spec.annotations.yaml
65
+ 9. Agent addresses each annotation
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Reading Annotations
71
+
72
+ After review, load the YAML sidecar and process the feedback:
73
+
74
+ ```javascript
75
+ import { AnnotationFile } from '@henryavila/mdprobe/annotations'
76
+
77
+ const af = await AnnotationFile.load('spec.annotations.yaml')
78
+
79
+ // Query annotations
80
+ const open = af.getOpen() // all unresolved annotations
81
+ const bugs = af.getByTag('bug') // only bugs
82
+ const questions = af.getByTag('question')
83
+ const mine = af.getByAuthor('Alice')
84
+ const resolved = af.getResolved() // already handled
85
+ const one = af.getById('a1b2c3d4') // specific annotation
86
+
87
+ // Each annotation has:
88
+ // {
89
+ // id, selectors: { position: { startLine, startColumn, endLine, endColumn },
90
+ // quote: { exact, prefix, suffix } },
91
+ // comment, tag, status, author, created_at, updated_at,
92
+ // replies: [{ author, comment, created_at }]
93
+ // }
94
+
95
+ // Process feedback
96
+ for (const ann of open) {
97
+ console.log(`[${ann.tag}] Line ${ann.selectors.position.startLine}: ${ann.comment}`)
98
+ if (ann.replies.length > 0) {
99
+ for (const reply of ann.replies) {
100
+ console.log(` ↳ ${reply.author}: ${reply.comment}`)
101
+ }
102
+ }
103
+ }
104
+
105
+ // Mark as handled
106
+ af.resolve(bugs[0].id)
107
+ await af.save('spec.annotations.yaml')
108
+ ```
109
+
110
+ ## Checking Section Approvals
111
+
112
+ The human can approve or reject each section (heading) of the document. Check the status:
113
+
114
+ ```javascript
115
+ const af = await AnnotationFile.load('spec.annotations.yaml')
116
+
117
+ // sections: [{ heading, level, status }]
118
+ // status is: 'approved', 'rejected', or 'pending'
119
+ for (const section of af.sections) {
120
+ console.log(`${section.heading}: ${section.status}`)
121
+ }
122
+
123
+ // Check if all sections were approved
124
+ const allApproved = af.sections.every(s => s.status === 'approved')
125
+
126
+ // Find rejected sections that need rework
127
+ const rejected = af.sections.filter(s => s.status === 'rejected')
128
+ ```
129
+
130
+ Approval cascades: if the human approves a parent heading (e.g., H2), all child headings (H3, H4...) under it are also approved. Same for reject and reset.
131
+
132
+ ## Export Formats
133
+
134
+ ```javascript
135
+ import { exportJSON, exportSARIF, exportReport } from '@henryavila/mdprobe/export'
136
+ import { readFile } from 'node:fs/promises'
137
+
138
+ const af = await AnnotationFile.load('spec.annotations.yaml')
139
+ const source = await readFile('spec.md', 'utf-8')
140
+
141
+ const json = exportJSON(af) // plain JS object
142
+ const sarif = exportSARIF(af, 'spec.md') // SARIF 2.1.0 (open annotations only)
143
+ const report = exportReport(af, source) // markdown review report
144
+ ```
145
+
146
+ SARIF maps tags to severity: `bug` = error, `suggestion` = warning, `question`/`nitpick` = note.
147
+
148
+ ## Annotation Tags
149
+
150
+ | Tag | Meaning | When the human uses it |
151
+ |-----|---------|----------------------|
152
+ | `bug` | Something is wrong | Factual errors, incorrect logic, broken examples |
153
+ | `question` | Needs clarification | Ambiguous requirements, missing context |
154
+ | `suggestion` | Improvement idea | Better approach, additional feature, alternative |
155
+ | `nitpick` | Minor style/wording | Typos, formatting, naming preferences |
156
+
157
+ ## Interacting with Annotations
158
+
159
+ ### Resolving annotations after you fix them
160
+
161
+ After addressing an annotation, mark it as resolved so the human knows it's been handled:
162
+
163
+ ```javascript
164
+ import { AnnotationFile } from '@henryavila/mdprobe/annotations'
165
+
166
+ const af = await AnnotationFile.load('spec.annotations.yaml')
167
+
168
+ for (const ann of af.getOpen()) {
169
+ // Process the annotation (fix the bug, answer the question, etc.)
170
+
171
+ // Mark as resolved
172
+ af.resolve(ann.id)
173
+ }
174
+
175
+ // Persist changes — the human will see these as resolved in the UI
176
+ await af.save('spec.annotations.yaml')
177
+ ```
178
+
179
+ ### Replying to annotations
180
+
181
+ Add a reply to explain what you did, ask for clarification, or acknowledge the feedback:
182
+
183
+ ```javascript
184
+ const af = await AnnotationFile.load('spec.annotations.yaml')
185
+
186
+ const bugs = af.getByTag('bug')
187
+ for (const bug of bugs) {
188
+ af.addReply(bug.id, {
189
+ author: 'Agent',
190
+ comment: `Fixed in commit abc123. Changed line ${bug.selectors.position.startLine}.`,
191
+ })
192
+ af.resolve(bug.id)
193
+ }
194
+
195
+ await af.save('spec.annotations.yaml')
196
+ ```
197
+
198
+ ### Creating annotations before human review
199
+
200
+ Pre-annotate sections you're unsure about, so the human knows where to focus:
201
+
202
+ ```javascript
203
+ const af = await AnnotationFile.load('spec.annotations.yaml')
204
+
205
+ af.add({
206
+ selectors: {
207
+ position: { startLine: 42, startColumn: 1, endLine: 42, endColumn: 60 },
208
+ quote: { exact: 'Rate limit: 100 requests per minute', prefix: '', suffix: '' },
209
+ },
210
+ comment: 'Is 100/min enough? The load test showed spikes of 300/min.',
211
+ tag: 'question',
212
+ author: 'Agent',
213
+ })
214
+
215
+ await af.save('spec.annotations.yaml')
216
+ ```
217
+
218
+ ### Interacting via HTTP API (while server is running)
219
+
220
+ If the server is running (view mode), you can interact without touching the YAML file directly:
221
+
222
+ ```bash
223
+ # Create an annotation
224
+ curl -X POST http://127.0.0.1:3000/api/annotations -H 'Content-Type: application/json' -d '{
225
+ "file": "spec.md",
226
+ "action": "add",
227
+ "data": {
228
+ "selectors": {
229
+ "position": { "startLine": 10, "startColumn": 1, "endLine": 10, "endColumn": 40 },
230
+ "quote": { "exact": "text to annotate", "prefix": "", "suffix": "" }
231
+ },
232
+ "comment": "This needs work",
233
+ "tag": "suggestion",
234
+ "author": "Agent"
235
+ }
236
+ }'
237
+
238
+ # Resolve an annotation
239
+ curl -X POST http://127.0.0.1:3000/api/annotations -H 'Content-Type: application/json' -d '{
240
+ "file": "spec.md",
241
+ "action": "resolve",
242
+ "data": { "id": "a1b2c3d4" }
243
+ }'
244
+
245
+ # Add a reply
246
+ curl -X POST http://127.0.0.1:3000/api/annotations -H 'Content-Type: application/json' -d '{
247
+ "file": "spec.md",
248
+ "action": "reply",
249
+ "data": { "id": "a1b2c3d4", "author": "Agent", "comment": "Fixed." }
250
+ }'
251
+
252
+ # Approve a section
253
+ curl -X POST http://127.0.0.1:3000/api/sections -H 'Content-Type: application/json' -d '{
254
+ "file": "spec.md",
255
+ "action": "approve",
256
+ "heading": "Requirements"
257
+ }'
258
+ ```
259
+
260
+ The browser auto-updates when annotations change — the human sees your replies and resolutions in real time.
261
+
262
+ ### Iterative review loop
263
+
264
+ When the first review produces feedback, fix the issues and re-launch for a second pass:
265
+
266
+ ```
267
+ Round 1:
268
+ 1. Agent writes spec.md
269
+ 2. mdprobe spec.md --once → human annotates 5 bugs, 3 questions
270
+ 3. Agent reads feedback, fixes all 5 bugs, answers 3 questions
271
+ 4. Agent marks all 8 as resolved, adds replies explaining fixes
272
+
273
+ Round 2:
274
+ 5. Agent re-launches: mdprobe spec.md --once
275
+ 6. Human sees resolved items (greyed out), reviews fixes
276
+ 7. Human adds 1 new nitpick, approves all sections
277
+ 8. Agent reads feedback — 1 nitpick to fix, all sections approved
278
+ 9. Done — proceed to implementation
279
+ ```
280
+
281
+ ```javascript
282
+ // After fixing issues from round 1:
283
+ const af = await AnnotationFile.load('spec.annotations.yaml')
284
+
285
+ // Mark everything as resolved with explanations
286
+ for (const ann of af.getOpen()) {
287
+ af.addReply(ann.id, {
288
+ author: 'Agent',
289
+ comment: 'Addressed in updated spec.',
290
+ })
291
+ af.resolve(ann.id)
292
+ }
293
+ await af.save('spec.annotations.yaml')
294
+
295
+ // Re-launch for round 2
296
+ // exec: mdprobe spec.md --once
297
+ ```
298
+
299
+ ## Drift Detection
300
+
301
+ If you modify the source `.md` after annotations were created, mdprobe warns the human that the source has changed (annotations may be stale). The hash is stored in the YAML:
302
+
303
+ ```yaml
304
+ source_hash: "sha256:abc123..."
305
+ ```
306
+
307
+ ## Schema Validation
308
+
309
+ A JSON Schema is available for validating annotation YAML files:
310
+
311
+ ```javascript
312
+ import schema from '@henryavila/mdprobe/schema.json'
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Recommended Patterns
318
+
319
+ ### Pattern: spec review before implementation
320
+
321
+ ```bash
322
+ # 1. Write the spec
323
+ cat > spec.md << 'SPEC'
324
+ # Feature: User Authentication
325
+ ## Requirements
326
+ ...
327
+ SPEC
328
+
329
+ # 2. Get human review (blocks until done)
330
+ mdprobe spec.md --once
331
+
332
+ # 3. Read feedback
333
+ node -e "
334
+ import { AnnotationFile } from '@henryavila/mdprobe/annotations'
335
+ const af = await AnnotationFile.load('spec.annotations.yaml')
336
+ console.log(JSON.stringify(af.getOpen(), null, 2))
337
+ "
338
+ ```
339
+
340
+ ### Pattern: background viewer while working
341
+
342
+ ```bash
343
+ # Start viewer in background
344
+ mdprobe docs/ --no-open &
345
+
346
+ # Continue working — browser shows rendered docs with live reload
347
+ # Human reads at their own pace
348
+ ```
349
+
350
+ ### Pattern: check if human approved all sections
351
+
352
+ ```javascript
353
+ const af = await AnnotationFile.load('spec.annotations.yaml')
354
+ const pending = af.sections.filter(s => s.status !== 'approved')
355
+ if (pending.length > 0) {
356
+ console.log('Sections not yet approved:', pending.map(s => s.heading))
357
+ }
358
+ ```