dotmd-cli 0.31.0 → 0.31.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
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",
package/src/export.mjs CHANGED
@@ -151,8 +151,8 @@ function exportMarkdown(docs, config) {
151
151
  lines.push(`### ${doc.title}`);
152
152
  const meta = [`Status: ${doc.status}`];
153
153
  if (doc.updated) meta.push(`Updated: ${doc.updated}`);
154
- if (doc.module) meta.push(`Module: ${doc.module}`);
155
- if (doc.surface) meta.push(`Surface: ${doc.surface}`);
154
+ if (doc.modules?.length) meta.push(`Module: ${doc.modules.join(', ')}`);
155
+ if (doc.surfaces?.length) meta.push(`Surface: ${doc.surfaces.join(', ')}`);
156
156
  if (doc.owner) meta.push(`Owner: ${doc.owner}`);
157
157
  lines.push(`> ${meta.join(' | ')}`, '');
158
158
  if (doc.body.trim()) lines.push(doc.body.trim());
@@ -292,8 +292,8 @@ function buildDocPage(doc) {
292
292
  let meta = `<table class="meta">`;
293
293
  meta += `<tr><td>Status</td><td><span class="badge ${badgeClass}">${doc.status ?? 'unknown'}</span></td></tr>`;
294
294
  if (doc.updated) meta += `<tr><td>Updated</td><td>${escHtml(doc.updated)}</td></tr>`;
295
- if (doc.module) meta += `<tr><td>Module</td><td>${escHtml(doc.module)}</td></tr>`;
296
- if (doc.surface) meta += `<tr><td>Surface</td><td>${escHtml(doc.surface)}</td></tr>`;
295
+ if (doc.modules?.length) meta += `<tr><td>Module</td><td>${escHtml(doc.modules.join(', '))}</td></tr>`;
296
+ if (doc.surfaces?.length) meta += `<tr><td>Surface</td><td>${escHtml(doc.surfaces.join(', '))}</td></tr>`;
297
297
  if (doc.owner) meta += `<tr><td>Owner</td><td>${escHtml(doc.owner)}</td></tr>`;
298
298
  meta += `<tr><td>Path</td><td><code>${escHtml(doc.path)}</code></td></tr>`;
299
299
  meta += `</table>`;
package/src/git.mjs CHANGED
@@ -1,4 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ import { renameSync } from 'node:fs';
3
+ import path from 'node:path';
2
4
 
3
5
  let gitChecked = false;
4
6
  function ensureGit() {
@@ -49,6 +51,20 @@ export function getGitLastModifiedBatch(repoRoot) {
49
51
 
50
52
  export function gitMv(source, target, repoRoot) {
51
53
  ensureGit();
54
+ // Source is untracked (scaffolded this session, never committed; or repoRoot
55
+ // is not a git repo at all): a plain rename is the only correct move. `git mv`
56
+ // would error with `fatal: not under version control` and the user can't act
57
+ // on that — the file is genuinely a doc, just not yet staged.
58
+ if (!isTracked(source, repoRoot)) {
59
+ const absSource = path.isAbsolute(source) ? source : path.join(repoRoot, source);
60
+ const absTarget = path.isAbsolute(target) ? target : path.join(repoRoot, target);
61
+ try {
62
+ renameSync(absSource, absTarget);
63
+ return { status: 0, stderr: '' };
64
+ } catch (err) {
65
+ return { status: 1, stderr: err.message };
66
+ }
67
+ }
52
68
  const result = spawnSync('git', ['mv', source, target], {
53
69
  cwd: repoRoot,
54
70
  encoding: 'utf8',
@@ -56,6 +72,15 @@ export function gitMv(source, target, repoRoot) {
56
72
  return { status: result.status, stderr: result.stderr };
57
73
  }
58
74
 
75
+ function isTracked(source, repoRoot) {
76
+ const relSource = path.isAbsolute(source) ? path.relative(repoRoot, source) : source;
77
+ const result = spawnSync('git', ['ls-files', '--error-unmatch', '--', relSource], {
78
+ cwd: repoRoot,
79
+ encoding: 'utf8',
80
+ });
81
+ return result.status === 0;
82
+ }
83
+
59
84
  export function gitDiffSince(relPath, sinceDate, repoRoot, opts = {}) {
60
85
  ensureGit();
61
86
  // Find the last commit at or before sinceDate
package/src/graph.mjs CHANGED
@@ -43,7 +43,9 @@ export function buildGraph(index, config, filters = {}) {
43
43
  title: d.title,
44
44
  status: d.status,
45
45
  module: d.module,
46
+ modules: d.modules,
46
47
  surface: d.surface,
48
+ surfaces: d.surfaces,
47
49
  edgeCount: 0,
48
50
  }));
49
51
  const nodeMap = new Map(nodes.map(n => [n.id, n]));
package/src/render.mjs CHANGED
@@ -379,10 +379,10 @@ export function renderCoverage(index, config) {
379
379
  export function buildCoverage(index, config) {
380
380
  const scope = [...new Set(index.docs.map(d => d.status).filter(s => s && !config.lifecycle.terminalStatuses.has(s)))];
381
381
  const scoped = index.docs.filter(doc => doc.status && !config.lifecycle.terminalStatuses.has(doc.status));
382
- const missingSurface = scoped.filter(doc => !doc.surface);
383
- const missingModule = scoped.filter(doc => !doc.module);
384
- const modulePlatform = scoped.filter(doc => doc.module === 'platform');
385
- const moduleNone = scoped.filter(doc => doc.module === 'none');
382
+ const missingSurface = scoped.filter(doc => !doc.surfaces?.length);
383
+ const missingModule = scoped.filter(doc => !doc.modules?.length);
384
+ const modulePlatform = scoped.filter(doc => doc.modules?.includes('platform'));
385
+ const moduleNone = scoped.filter(doc => doc.modules?.includes('none'));
386
386
  const auditLevelNone = scoped.filter(doc => doc.auditLevel === 'none');
387
387
  const audited = scoped.filter(doc => ['pass1', 'pass2', 'deep'].includes(doc.auditLevel));
388
388
 
package/src/stats.mjs CHANGED
@@ -64,8 +64,8 @@ export function buildStats(index, config) {
64
64
  completeness: {
65
65
  scoped: scoped.length,
66
66
  hasOwner: scoped.filter(d => d.owner).length,
67
- hasSurface: scoped.filter(d => d.surface).length,
68
- hasModule: scoped.filter(d => d.module).length,
67
+ hasSurface: scoped.filter(d => d.surfaces?.length).length,
68
+ hasModule: scoped.filter(d => d.modules?.length).length,
69
69
  hasNextStep: scoped.filter(d => d.hasNextStep).length,
70
70
  },
71
71
  checklists: {
package/src/validate.mjs CHANGED
@@ -102,8 +102,8 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
102
102
  doc.errors.push({ path: doc.path, level: 'error', message: '`modules` must be a YAML list when present.' });
103
103
  }
104
104
 
105
- if (config.moduleRequiredStatuses.has(doc.status) && !doc.module) {
106
- doc.errors.push({ path: doc.path, level: 'error', message: '`module` is required for active/ready/planned/blocked docs; use a real module, `platform`, or `none`.' });
105
+ if (config.moduleRequiredStatuses.has(doc.status) && !doc.modules?.length) {
106
+ doc.errors.push({ path: doc.path, level: 'error', message: '`module` is required for active/ready/planned/blocked docs; use a real module, `platform`, or `none`. Accepts singular `module:` or plural `modules:` list.' });
107
107
  }
108
108
 
109
109
  if (config.validSurfaces) {