dotmd-cli 0.7.0 → 0.8.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dotmd
2
2
 
3
- Zero-dependency CLI for managing markdown documents with YAML frontmatter.
3
+ CLI for managing markdown documents with YAML frontmatter.
4
4
 
5
5
  Index, query, validate, and lifecycle-manage any collection of `.md` files — plans, ADRs, RFCs, design docs, meeting notes. Built for AI-assisted development workflows where structured docs need to stay current.
6
6
 
@@ -34,16 +34,19 @@ eval "$(dotmd completions zsh)" # add to ~/.zshrc
34
34
 
35
35
  ## What It Does
36
36
 
37
- dotmd scans a directory of markdown files, parses their YAML frontmatter, and gives you tools to work with them:
38
-
39
37
  - **Index** — group docs by status, show progress bars, next steps
40
38
  - **Query** — filter by status, keyword, module, surface, owner, staleness
41
- - **Validate** — check for missing fields, broken references, stale dates, broken body links
39
+ - **Validate** — check for missing fields, broken references, broken body links, stale dates
40
+ - **Stats** — health dashboard with staleness, completeness, audit coverage
42
41
  - **Graph** — visualize document relationships as text, Graphviz DOT, or JSON
42
+ - **Deps** — dependency tree or overview of what blocks what
43
43
  - **Lifecycle** — transition statuses, auto-archive with `git mv` and reference updates
44
44
  - **Doctor** — auto-fix broken refs, lint issues, date drift, and stale indexes in one pass
45
45
  - **Scaffold** — create new docs from templates (plan, ADR, RFC, audit, design)
46
- - **Index generation** — auto-generate a `docs.md` index block
46
+ - **AI summaries** — summarize docs via local MLX model or custom hook
47
+ - **Export** — generate concatenated markdown, static HTML site, or JSON bundle
48
+ - **Notion** — import from, export to, and bidirectionally sync with Notion databases
49
+ - **Multi-root** — manage docs across multiple directories with a single config
47
50
  - **Context briefing** — compact summary designed for AI/LLM consumption
48
51
  - **Dry-run** — preview any mutation with `--dry-run` before committing
49
52
 
@@ -72,17 +75,19 @@ Design doc content here...
72
75
  - [ ] Add tests
73
76
  ```
74
77
 
75
- The only required field is `status`. Everything else is optional but unlocks more features (staleness detection, filtering, coverage reports, graph visualization).
78
+ The only required field is `status`. Everything else is optional but unlocks more features.
76
79
 
77
80
  ## Commands
78
81
 
79
82
  ```
80
83
  dotmd list [--verbose] List docs grouped by status (default)
81
84
  dotmd json Full index as JSON
82
- dotmd check [--errors-only] [--fix] Validate frontmatter and references
85
+ dotmd check [flags] Validate frontmatter and references
83
86
  dotmd coverage [--json] Metadata coverage report
87
+ dotmd stats [--json] Doc health dashboard
84
88
  dotmd graph [--dot|--json] Visualize document relationships
85
- dotmd context Compact briefing (LLM-oriented)
89
+ dotmd deps [file] Dependency tree or overview
90
+ dotmd context [--summarize] Compact briefing (LLM-oriented)
86
91
  dotmd focus [status] Detailed view for one status group
87
92
  dotmd query [filters] Filtered search
88
93
  dotmd index [--write] Generate/update docs.md index block
@@ -95,6 +100,9 @@ dotmd fix-refs Auto-fix broken reference paths
95
100
  dotmd lint [--fix] Check and auto-fix frontmatter issues
96
101
  dotmd rename <old> <new> Rename doc and update references
97
102
  dotmd migrate <f> <old> <new> Batch update a frontmatter field
103
+ dotmd notion <sub> [db-id] Notion import/export/sync
104
+ dotmd export [file] Export docs as md, html, or json
105
+ dotmd summary <file> AI summary of a document
98
106
  dotmd watch [command] Re-run a command on file changes
99
107
  dotmd diff [file] Show changes since last updated date
100
108
  dotmd new <name> Create a new document from template
@@ -107,6 +115,7 @@ dotmd completions <shell> Output shell completion script (bash, zsh)
107
115
  ```
108
116
  --config <path> Explicit config file path
109
117
  --dry-run, -n Preview changes without writing anything
118
+ --root <name> Filter to a specific docs root
110
119
  --verbose Show resolved config details
111
120
  --help, -h Show help (per-command with: dotmd <cmd> --help)
112
121
  --version, -v Show version
@@ -119,9 +128,11 @@ dotmd query --status active,ready --module auth
119
128
  dotmd query --keyword "token" --has-next-step
120
129
  dotmd query --stale --sort updated --all
121
130
  dotmd query --surface backend --checklist-open
131
+ dotmd query --status active --summarize # AI summaries
132
+ dotmd query --status active --summarize --summarize-limit 3
122
133
  ```
123
134
 
124
- Flags: `--status`, `--keyword`, `--module`, `--surface`, `--domain`, `--owner`, `--updated-since`, `--stale`, `--has-next-step`, `--has-blockers`, `--checklist-open`, `--sort`, `--limit`, `--all`, `--git`, `--json`.
135
+ Flags: `--status`, `--keyword`, `--module`, `--surface`, `--domain`, `--owner`, `--updated-since`, `--stale`, `--has-next-step`, `--has-blockers`, `--checklist-open`, `--sort`, `--limit`, `--all`, `--git`, `--json`, `--summarize`, `--summarize-limit`, `--model`.
125
136
 
126
137
  ### Scaffold with Templates
127
138
 
@@ -133,6 +144,7 @@ dotmd new my-proposal --template rfc # RFC: Summary, Motivation
133
144
  dotmd new my-audit --template audit # Audit: Scope, Findings, Recommendations
134
145
  dotmd new my-design --template design # Design: Goals, Non-Goals, Design
135
146
  dotmd new my-feature --status planned --title "Title" # custom status and title
147
+ dotmd new my-doc --root modules # create in a specific root
136
148
  dotmd new --list-templates # show all available templates
137
149
  ```
138
150
 
@@ -158,21 +170,24 @@ dotmd check --fix # auto-fix broken refs + lint + regen index
158
170
 
159
171
  Validates: required fields, status values, broken reference paths, broken body links (`[text](path.md)`), bidirectional reference symmetry, git date drift, taxonomy mismatches.
160
172
 
161
- ### Doctor
173
+ ### Stats
162
174
 
163
- One command to fix everything fixable:
175
+ ```bash
176
+ dotmd stats # health dashboard
177
+ dotmd stats --json # machine-readable
178
+ ```
179
+
180
+ Shows: status counts, staleness, errors/warnings, freshness (today/week/month), completeness (owner/surface/module/next_step), checklist progress, audit coverage.
181
+
182
+ ### Doctor
164
183
 
165
184
  ```bash
166
185
  dotmd doctor # fix refs → lint → sync git dates → regen index
167
186
  dotmd doctor --dry-run # preview all changes
168
187
  ```
169
188
 
170
- Runs in sequence: `fix-refs` → `lint --fix` → `touch --git` → `index --write` → shows remaining issues.
171
-
172
189
  ### Graph
173
190
 
174
- Visualize how documents reference each other:
175
-
176
191
  ```bash
177
192
  dotmd graph # text adjacency list
178
193
  dotmd graph --dot | dot -Tpng -o g.png # Graphviz PNG
@@ -181,6 +196,78 @@ dotmd graph --status active,ready # filter by status
181
196
  dotmd graph --module auth # filter by module
182
197
  ```
183
198
 
199
+ ### Deps
200
+
201
+ ```bash
202
+ dotmd deps # overview: most blocking, most blocked
203
+ dotmd deps docs/plan-a.md # tree: depends-on + depended-on-by
204
+ dotmd deps docs/plan-a.md --depth 2 # limit tree depth
205
+ dotmd deps --json # machine-readable
206
+ ```
207
+
208
+ ### AI Summaries
209
+
210
+ ```bash
211
+ dotmd summary docs/plan-a.md # AI summary of a single doc
212
+ dotmd summary docs/plan-a.md --json # JSON output
213
+ dotmd query --status active --summarize # AI summaries in query results
214
+ dotmd context --summarize # AI-enhanced briefing
215
+ ```
216
+
217
+ Uses local MLX model via `uv`. Override with `--model <name>` or the `summarizeDoc` hook.
218
+
219
+ ### Export
220
+
221
+ ```bash
222
+ dotmd export # all docs as concatenated markdown
223
+ dotmd export --format html --output site # static HTML site
224
+ dotmd export --format json > bundle.json # JSON bundle with bodies
225
+ dotmd export docs/plan-a.md # single doc + dependencies
226
+ dotmd export --status active # filtered export
227
+ ```
228
+
229
+ ### Notion Integration
230
+
231
+ ```bash
232
+ dotmd notion import <database-id> # pull Notion database → local .md files
233
+ dotmd notion export <database-id> # push local docs → Notion database
234
+ dotmd notion sync <database-id> # bidirectional sync (newer wins)
235
+ dotmd notion import <db-id> --force # overwrite existing files
236
+ dotmd notion sync <db-id> --dry-run # preview sync actions
237
+ ```
238
+
239
+ Requires `NOTION_TOKEN` env var or `notion.token` in config. Maps Notion properties (select, multi_select, date, status, people, etc.) to YAML frontmatter fields. Configure property mapping in config:
240
+
241
+ ```js
242
+ export const notion = {
243
+ token: process.env.NOTION_TOKEN,
244
+ database: 'your-database-id',
245
+ propertyMap: {
246
+ 'Status': 'status',
247
+ 'Last Updated': 'updated',
248
+ 'Tags': 'surfaces',
249
+ },
250
+ };
251
+ ```
252
+
253
+ ### Multi-Root
254
+
255
+ Manage docs across multiple directories:
256
+
257
+ ```js
258
+ export const root = ['docs/plans', 'docs/modules', 'docs/app'];
259
+ ```
260
+
261
+ All commands work across all roots. Filter with `--root`:
262
+
263
+ ```bash
264
+ dotmd list --root plans # only docs from docs/plans
265
+ dotmd stats --root modules # stats for modules only
266
+ dotmd new my-doc --root modules # create in docs/modules
267
+ ```
268
+
269
+ Archive stays within the source file's root. Cross-root references validate correctly.
270
+
184
271
  ### Archive
185
272
 
186
273
  ```bash
@@ -188,44 +275,33 @@ dotmd archive docs/old-plan.md # move + update refs + regen index
188
275
  dotmd archive docs/old-plan.md -n # preview
189
276
  ```
190
277
 
191
- Archives a document: sets status to `archived`, moves to archive directory via `git mv`, auto-updates references in other docs, and regenerates the index.
192
-
193
278
  ### Touch
194
279
 
195
280
  ```bash
196
281
  dotmd touch docs/my-doc.md # set updated to today
197
282
  dotmd touch --git # bulk-sync all docs from git history
198
- dotmd touch --git docs/my-doc.md # sync one file from git
199
283
  ```
200
284
 
201
285
  ### Fix References
202
286
 
203
287
  ```bash
204
- dotmd fix-refs # find and fix broken ref paths
288
+ dotmd fix-refs # fix broken frontmatter refs + body links
205
289
  dotmd fix-refs --dry-run # preview fixes
206
290
  ```
207
291
 
208
- Scans all reference fields for broken paths, resolves by basename matching, and rewrites frontmatter.
209
-
210
292
  ### Lint
211
293
 
212
294
  ```bash
213
295
  dotmd lint # report issues
214
296
  dotmd lint --fix # fix all issues
215
- dotmd lint --fix --dry-run # preview fixes without writing
216
297
  ```
217
298
 
218
- Detected issues: missing `updated`, status casing, camelCase keys, trailing whitespace, missing EOF newline.
219
-
220
299
  ### Rename
221
300
 
222
301
  ```bash
223
302
  dotmd rename old-name.md new-name # renames + updates refs
224
- dotmd rename old-name.md new-name -n # preview without writing
225
303
  ```
226
304
 
227
- Uses `git mv` and updates all frontmatter references. Body markdown links are warned about but not auto-fixed.
228
-
229
305
  ### Migrate
230
306
 
231
307
  ```bash
@@ -261,12 +337,16 @@ dotmd diff --stat # summary stats only
261
337
  dotmd diff --summarize # AI summary via local MLX model
262
338
  ```
263
339
 
340
+ ### Init Auto-Detect
341
+
342
+ When `dotmd init` runs in a directory with existing `.md` files, it scans them and pre-populates the config with discovered statuses, surfaces, modules, and reference fields.
343
+
264
344
  ## Configuration
265
345
 
266
346
  Create `dotmd.config.mjs` at your project root (or run `dotmd init`):
267
347
 
268
348
  ```js
269
- export const root = 'docs/plans'; // where your .md files live
349
+ export const root = 'docs/plans'; // string or array of paths
270
350
  export const archiveDir = 'archived'; // subdirectory for archived docs
271
351
 
272
352
  export const statuses = {
@@ -312,8 +392,7 @@ export function validate(doc, ctx) {
312
392
  const warnings = [];
313
393
  if (doc.status === 'active' && !doc.owner) {
314
394
  warnings.push({
315
- path: doc.path,
316
- level: 'warning',
395
+ path: doc.path, level: 'warning',
317
396
  message: 'Active docs should have an owner.',
318
397
  });
319
398
  }
@@ -332,7 +411,7 @@ export function renderContext(index, defaultRenderer) {
332
411
  }
333
412
  ```
334
413
 
335
- Available: `renderContext`, `renderCompactList`, `renderCheck`, `renderGraph`, `formatSnapshot`.
414
+ Available: `renderContext`, `renderCompactList`, `renderCheck`, `renderGraph`, `renderStats`, `formatSnapshot`.
336
415
 
337
416
  ### Lifecycle Hooks
338
417
 
@@ -340,33 +419,33 @@ Available: `renderContext`, `renderCompactList`, `renderCheck`, `renderGraph`, `
340
419
  export function onArchive(doc, { oldPath, newPath }) {
341
420
  console.log(`Archived: ${oldPath} → ${newPath}`);
342
421
  }
343
-
344
- export function onStatusChange(doc, { oldStatus, newStatus }) {
345
- // notify, log, trigger CI, etc.
346
- }
347
422
  ```
348
423
 
349
424
  Available: `onArchive`, `onStatusChange`, `onTouch`, `onNew`, `onRename`, `onLint`.
350
425
 
351
- ### Summarize Hook
352
-
353
- Override the diff summarizer (replaces the default MLX model call):
426
+ ### AI Hooks
354
427
 
355
428
  ```js
429
+ // Override doc summarization (replaces local MLX model)
430
+ export function summarizeDoc(body, meta) {
431
+ return 'Custom summary for ' + meta.title;
432
+ }
433
+
434
+ // Override diff summarization
356
435
  export function summarizeDiff(diffOutput, filePath) {
357
- // call your preferred LLM, return a string summary
358
436
  return `Changes in ${filePath}: ...`;
359
437
  }
360
438
  ```
361
439
 
362
440
  ## Features
363
441
 
364
- - **Zero dependencies** — pure Node.js builtins (`fs`, `path`, `child_process`)
365
- - **No build step** — ships as plain ESM, runs directly
366
442
  - **Git-aware** — detects frontmatter date drift vs git history, uses `git mv` for archives
367
443
  - **Dry-run everything** — preview any mutation with `--dry-run` / `-n`
368
- - **Configurable everything** — statuses, taxonomy, lifecycle, validation rules, display, templates
444
+ - **Multi-root** — manage docs across multiple directories with `--root` filtering
445
+ - **Configurable** — statuses, taxonomy, lifecycle, validation rules, display, templates
369
446
  - **Hook system** — extend with JS functions, no plugin framework to learn
447
+ - **AI-powered** — local MLX summaries for docs, queries, diffs, and context briefings
448
+ - **Notion sync** — import, export, and bidirectional sync with Notion databases
370
449
  - **LLM-friendly** — `dotmd context` generates compact briefings for AI assistants
371
450
  - **Shell completion** — bash and zsh via `dotmd completions`
372
451
 
package/bin/dotmd.mjs CHANGED
@@ -20,6 +20,11 @@ import { runMigrate } from '../src/migrate.mjs';
20
20
  import { runFixRefs, fixBrokenRefs } from '../src/fix-refs.mjs';
21
21
  import { buildGraph, renderGraphText, renderGraphDot, renderGraphJson } from '../src/graph.mjs';
22
22
  import { runDoctor } from '../src/doctor.mjs';
23
+ import { buildStats, renderStats, renderStatsJson } from '../src/stats.mjs';
24
+ import { runSummary } from '../src/summary.mjs';
25
+ import { runDeps } from '../src/deps.mjs';
26
+ import { runExport } from '../src/export.mjs';
27
+ import { runNotion } from '../src/notion.mjs';
23
28
  import { die, warn } from '../src/util.mjs';
24
29
 
25
30
  const __filename = fileURLToPath(import.meta.url);
@@ -34,7 +39,9 @@ Commands:
34
39
  json Full index as JSON
35
40
  check [flags] Validate frontmatter and references
36
41
  coverage [--json] Metadata coverage report
42
+ stats [--json] Doc health dashboard
37
43
  graph [--dot|--json] Visualize document relationships
44
+ deps [file] Dependency tree or overview
38
45
  context Compact briefing (LLM-oriented)
39
46
  focus [status] Detailed view for one status group
40
47
  query [filters] Filtered search
@@ -48,8 +55,11 @@ Commands:
48
55
  rename <old> <new> Rename doc and update references
49
56
  migrate <f> <old> <new> Batch update a frontmatter field
50
57
  watch [command] Re-run a command on file changes
58
+ notion <sub> [db-id] Notion import/export/sync
59
+ export [file] Export docs as md, html, or json
60
+ summary <file> AI summary of a document
51
61
  diff [file] Show changes since last updated date
52
- new <name> Create a new document with frontmatter
62
+ new <name> Create a new document from template
53
63
  init Create starter config + docs directory
54
64
  completions <shell> Output shell completion script (bash, zsh)
55
65
 
@@ -101,6 +111,14 @@ references in other docs, and regenerates the index.
101
111
 
102
112
  Use --dry-run (-n) to preview changes without writing anything.`,
103
113
 
114
+ stats: `dotmd stats — doc health dashboard
115
+
116
+ Shows aggregated metrics: status counts, staleness, errors/warnings,
117
+ freshness, completeness, checklist progress, and audit coverage.
118
+
119
+ Options:
120
+ --json Machine-readable JSON output`,
121
+
104
122
  graph: `dotmd graph — visualize document relationships
105
123
 
106
124
  Output formats:
@@ -113,6 +131,18 @@ Filters:
113
131
  --module <name> Show only docs with this module
114
132
  --surface <name> Show only docs with this surface`,
115
133
 
134
+ deps: `dotmd deps [file] — dependency tree or overview
135
+
136
+ Without a file, shows a flat overview: most blocking docs, most blocked
137
+ docs, docs with blockers, and orphans.
138
+
139
+ With a file, shows a tree: what the doc depends on (recursive) and what
140
+ depends on it.
141
+
142
+ Options:
143
+ --depth <n> Max tree depth (default: 5)
144
+ --json Machine-readable JSON output`,
145
+
116
146
  doctor: `dotmd doctor — auto-fix everything in one pass
117
147
 
118
148
  Runs in sequence: fix broken references, lint --fix, sync dates from
@@ -167,6 +197,40 @@ Examples:
167
197
  dotmd watch check # re-run check on changes
168
198
  dotmd watch context # live briefing`,
169
199
 
200
+ notion: `dotmd notion — Notion database integration
201
+
202
+ Subcommands:
203
+ import <database-id> Pull Notion database → local .md files
204
+ export <database-id> Push local docs → Notion database rows
205
+ sync <database-id> Bidirectional sync (merge by slug)
206
+
207
+ Options:
208
+ --force Overwrite existing files on import
209
+ --dry-run, -n Preview without changes
210
+
211
+ Requires NOTION_TOKEN env var or notion.token in config.`,
212
+
213
+ export: `dotmd export — export docs as markdown, HTML, or JSON
214
+
215
+ Without a file, exports all docs (with optional filters).
216
+ With a file, exports that doc plus all its dependencies.
217
+
218
+ Options:
219
+ --format <md|html|json> Output format (default: md)
220
+ --output <path> Write to file/directory (default: stdout for md/json)
221
+ --status <s1,s2> Filter by status
222
+ --module <name> Filter by module
223
+ --root <name> Filter by root`,
224
+
225
+ summary: `dotmd summary <file> — AI summary of a document
226
+
227
+ Generates an AI-powered summary using a local MLX model via uv.
228
+
229
+ Options:
230
+ --model <name> MLX model (default: mlx-community/Llama-3.2-3B-Instruct-4bit)
231
+ --max-tokens <n> Max tokens for generation (default: 200)
232
+ --json Output as JSON`,
233
+
170
234
  diff: `dotmd diff [file] — show changes since last updated date
171
235
 
172
236
  Shows git diffs for docs that changed after their frontmatter updated date.
@@ -270,7 +334,8 @@ async function main() {
270
334
 
271
335
  if (verbose) {
272
336
  process.stderr.write(`Config: ${config.configPath ?? 'none'}\n`);
273
- process.stderr.write(`Docs root: ${config.docsRoot}\n`);
337
+ const roots = config.docsRoots || [config.docsRoot];
338
+ process.stderr.write(`Docs root${roots.length > 1 ? 's' : ''}: ${roots.join(', ')}\n`);
274
339
  process.stderr.write(`Repo root: ${config.repoRoot}\n`);
275
340
  }
276
341
 
@@ -284,6 +349,10 @@ async function main() {
284
349
  // Watch and diff (handle their own index building)
285
350
  if (command === 'watch') { runWatch(restArgs, config); return; }
286
351
  if (command === 'diff') { runDiff(restArgs, config); return; }
352
+ if (command === 'summary') { runSummary(restArgs, config); return; }
353
+ if (command === 'deps') { runDeps(restArgs, config); return; }
354
+ if (command === 'export') { runExport(restArgs, config); return; }
355
+ if (command === 'notion') { await runNotion(restArgs, config, { dryRun }); return; }
287
356
 
288
357
  // Lifecycle commands
289
358
  if (command === 'status') { runStatus(restArgs, config, { dryRun }); return; }
@@ -298,6 +367,17 @@ async function main() {
298
367
 
299
368
  const index = buildIndex(config);
300
369
 
370
+ // Apply --root filter
371
+ const rootFilter = (() => { const i = args.indexOf('--root'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
372
+ if (rootFilter) {
373
+ index.docs = index.docs.filter(d => d.root === rootFilter || d.root.endsWith('/' + rootFilter) || d.root.split('/').pop() === rootFilter);
374
+ index.errors = index.errors.filter(e => index.docs.some(d => d.path === e.path));
375
+ index.warnings = index.warnings.filter(w => index.docs.some(d => d.path === w.path));
376
+ for (const status of config.statusOrder) {
377
+ index.countsByStatus[status] = index.docs.filter(d => d.status === status).length;
378
+ }
379
+ }
380
+
301
381
  if (verbose) {
302
382
  process.stderr.write(`Docs found: ${index.docs.length}\n`);
303
383
  }
@@ -353,6 +433,16 @@ async function main() {
353
433
  return;
354
434
  }
355
435
 
436
+ if (command === 'stats') {
437
+ const stats = buildStats(index, config);
438
+ if (args.includes('--json')) {
439
+ process.stdout.write(renderStatsJson(stats));
440
+ } else {
441
+ process.stdout.write(renderStats(stats, config));
442
+ }
443
+ return;
444
+ }
445
+
356
446
  if (command === 'index') {
357
447
  if (!config.indexPath) {
358
448
  die('Index generation is not configured. Add an `index` section to your dotmd.config.mjs.');
@@ -372,7 +462,13 @@ async function main() {
372
462
 
373
463
  if (command === 'focus') { runFocus(index, restArgs, config); return; }
374
464
  if (command === 'query') { runQuery(index, restArgs, config); return; }
375
- if (command === 'context') { process.stdout.write(renderContext(index, config)); return; }
465
+ if (command === 'context') {
466
+ const summarize = args.includes('--summarize');
467
+ const modelIdx = args.indexOf('--model');
468
+ const model = modelIdx !== -1 && args[modelIdx + 1] ? args[modelIdx + 1] : undefined;
469
+ process.stdout.write(renderContext(index, config, { summarize, model }));
470
+ return;
471
+ }
376
472
 
377
473
  if (command === 'graph') {
378
474
  const statusFilter = (() => { const i = args.indexOf('--status'); return i !== -1 && args[i + 1] ? args[i + 1] : null; })();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.7.0",
4
- "description": "Zero-dependency CLI for managing markdown documents with YAML frontmatter — index, query, validate, lifecycle.",
3
+ "version": "0.8.0",
4
+ "description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, Notion sync, AI summaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -24,17 +24,27 @@
24
24
  "adr",
25
25
  "rfc",
26
26
  "document-management",
27
- "yaml"
27
+ "yaml",
28
+ "notion",
29
+ "graph",
30
+ "export",
31
+ "ai-summary",
32
+ "validation",
33
+ "lifecycle"
28
34
  ],
29
35
  "repository": {
30
36
  "type": "git",
31
- "url": "git+https://github.com/beyond-dev/platform.git",
32
- "directory": "packages/dotmd"
37
+ "url": "git+https://github.com/reowens/dotmd.git"
33
38
  },
39
+ "homepage": "https://github.com/reowens/dotmd#readme",
34
40
  "scripts": {
35
41
  "test": "node --test test/*.test.mjs"
36
42
  },
37
43
  "engines": {
38
44
  "node": ">=18"
45
+ },
46
+ "dependencies": {
47
+ "@notionhq/client": "^5.13.0",
48
+ "notion-to-md": "^3.1.9"
39
49
  }
40
50
  }
package/src/ai.mjs ADDED
@@ -0,0 +1,50 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { warn } from './util.mjs';
3
+
4
+ let uvChecked = null;
5
+
6
+ export function checkUvAvailable() {
7
+ if (uvChecked !== null) return uvChecked;
8
+ const result = spawnSync('uv', ['--version'], { encoding: 'utf8' });
9
+ uvChecked = !result.error && result.status === 0;
10
+ if (!uvChecked) warn('uv is not installed. Install it to enable AI summaries: https://docs.astral.sh/uv/');
11
+ return uvChecked;
12
+ }
13
+
14
+ export function runMLX(prompt, opts = {}) {
15
+ const { model = 'mlx-community/Llama-3.2-3B-Instruct-4bit', maxTokens = 200, timeout = 120000 } = opts;
16
+ if (!checkUvAvailable()) return null;
17
+
18
+ const result = spawnSync('uv', [
19
+ 'run', '--with', 'mlx-lm',
20
+ 'python3', '-m', 'mlx_lm', 'generate',
21
+ '--model', model,
22
+ '--prompt', prompt,
23
+ '--max-tokens', String(maxTokens),
24
+ '--verbose', 'false',
25
+ ], { encoding: 'utf8', timeout });
26
+
27
+ if (result.status !== 0) return null;
28
+
29
+ const output = result.stdout.trim();
30
+ const lines = output.split('\n')
31
+ .filter(l => !l.includes('Fetching') && !l.includes('Warning:') && !l.includes('=========='));
32
+ return lines.join(' ').trim() || null;
33
+ }
34
+
35
+ export function summarizeDocBody(bodyText, meta, opts = {}) {
36
+ if (!bodyText?.trim()) return null;
37
+ const prompt = `Summarize this markdown document in 2-3 sentences. Focus on what the document is about, its current state, and any key next actions or open questions.
38
+
39
+ Title: ${meta.title}
40
+ Status: ${meta.status}
41
+ File: ${meta.path}
42
+
43
+ ${bodyText.slice(0, 6000)}`;
44
+ return runMLX(prompt, { maxTokens: 200, ...opts });
45
+ }
46
+
47
+ export function summarizeDiffText(diffText, filePath, model) {
48
+ const prompt = `Summarize this git diff in 1-2 sentences. Focus on what changed semantically, not line counts.\n\nFile: ${filePath}\n\n${diffText.slice(0, 4000)}`;
49
+ return runMLX(prompt, { model, maxTokens: 150 });
50
+ }
@@ -1,28 +1,35 @@
1
1
  import { die } from './util.mjs';
2
2
 
3
3
  const COMMANDS = [
4
- 'list', 'json', 'check', 'coverage', 'graph', 'context', 'focus', 'query',
4
+ 'list', 'json', 'check', 'coverage', 'stats', 'graph', 'deps', 'context', 'focus', 'query',
5
5
  'index', 'status', 'archive', 'touch', 'doctor', 'lint', 'rename', 'migrate',
6
- 'fix-refs', 'watch', 'diff', 'init', 'new', 'completions',
6
+ 'fix-refs', 'notion', 'export', 'summary', 'watch', 'diff', 'init', 'new', 'completions',
7
7
  ];
8
8
 
9
- const GLOBAL_FLAGS = ['--config', '--dry-run', '--verbose', '--help', '--version'];
9
+ const GLOBAL_FLAGS = ['--config', '--dry-run', '--verbose', '--root', '--help', '--version'];
10
10
 
11
11
  const COMMAND_FLAGS = {
12
12
  query: ['--status', '--keyword', '--module', '--surface', '--domain', '--owner',
13
13
  '--updated-since', '--stale', '--has-next-step', '--has-blockers',
14
- '--checklist-open', '--sort', '--limit', '--all', '--git', '--json'],
14
+ '--checklist-open', '--sort', '--limit', '--all', '--git', '--json',
15
+ '--summarize', '--summarize-limit', '--model'],
15
16
  index: ['--write'],
16
17
  list: ['--verbose'],
17
18
  coverage: ['--json'],
18
- new: ['--status', '--title', '--template', '--list-templates'],
19
+ new: ['--status', '--title', '--template', '--list-templates', '--root'],
19
20
  diff: ['--stat', '--since', '--summarize', '--model'],
20
21
  check: ['--errors-only', '--fix'],
22
+ stats: ['--json'],
21
23
  graph: ['--dot', '--json', '--status', '--module', '--surface'],
24
+ deps: ['--json', '--depth'],
25
+ notion: ['import', 'export', 'sync', '--force', '--dry-run'],
26
+ export: ['--format', '--output', '--status', '--module', '--root'],
22
27
  lint: ['--fix'],
23
28
  rename: [],
24
29
  migrate: [],
25
30
  'fix-refs': [],
31
+ summary: ['--model', '--max-tokens', '--json'],
32
+ context: ['--summarize', '--model'],
26
33
  touch: ['--git'],
27
34
  };
28
35
 
package/src/config.mjs CHANGED
@@ -57,6 +57,8 @@ const DEFAULTS = {
57
57
 
58
58
  templates: {},
59
59
 
60
+ notion: null,
61
+
60
62
  presets: {
61
63
  stale: ['--status', 'active,ready,planned,blocked,research', '--stale', '--sort', 'updated', '--all'],
62
64
  actionable: ['--status', 'active,ready', '--has-next-step', '--sort', 'updated', '--all'],
@@ -182,11 +184,15 @@ export async function resolveConfig(cwd, explicitConfigPath) {
182
184
 
183
185
  const config = deepMerge(DEFAULTS, userConfig);
184
186
 
185
- const docsRoot = path.resolve(configDir, config.root);
187
+ const rootPaths = Array.isArray(config.root) ? config.root : [config.root];
188
+ const docsRoots = rootPaths.map(r => path.resolve(configDir, r));
189
+ const docsRoot = docsRoots[0]; // primary root for backwards compat
186
190
 
187
191
  const earlyWarnings = [];
188
- if (!existsSync(docsRoot)) {
189
- earlyWarnings.push('Config: docs root does not exist: ' + docsRoot);
192
+ for (const dr of docsRoots) {
193
+ if (!existsSync(dr)) {
194
+ earlyWarnings.push('Config: docs root does not exist: ' + dr);
195
+ }
190
196
  }
191
197
 
192
198
  // Find repo root by walking up looking for .git
@@ -235,6 +241,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
235
241
  raw: config,
236
242
 
237
243
  docsRoot,
244
+ docsRoots,
238
245
  repoRoot,
239
246
  configDir,
240
247
  configPath: configPath ?? null,