dotmd-cli 0.14.1 → 0.14.2

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.14.1",
3
+ "version": "0.14.2",
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/config.mjs CHANGED
@@ -5,6 +5,16 @@ import { die, warn } from './util.mjs';
5
5
 
6
6
  const CONFIG_FILENAMES = ['dotmd.config.mjs', '.dotmd.config.mjs', 'dotmd.config.js'];
7
7
 
8
+ // Keys where user config replaces defaults entirely (not deep-merged).
9
+ // These are flat maps or config sections where the user's version is authoritative —
10
+ // default keys that the user omitted should NOT survive the merge.
11
+ const REPLACE_KEYS = new Set([
12
+ 'statuses.staleDays',
13
+ 'statuses.rootStatuses',
14
+ 'presets',
15
+ 'context',
16
+ ]);
17
+
8
18
  const DEFAULTS = {
9
19
  root: '.',
10
20
  archiveDir: 'archived',
@@ -155,12 +165,14 @@ function validateConfig(userConfig, config, validStatuses, indexPath) {
155
165
  return warnings;
156
166
  }
157
167
 
158
- function deepMerge(defaults, overrides) {
168
+ function deepMerge(defaults, overrides, parentPath = '') {
159
169
  const result = { ...defaults };
160
170
  for (const [key, value] of Object.entries(overrides)) {
171
+ const keyPath = parentPath ? `${parentPath}.${key}` : key;
161
172
  if (value != null && typeof value === 'object' && !Array.isArray(value) &&
162
- result[key] != null && typeof result[key] === 'object' && !Array.isArray(result[key])) {
163
- result[key] = deepMerge(result[key], value);
173
+ result[key] != null && typeof result[key] === 'object' && !Array.isArray(result[key]) &&
174
+ !REPLACE_KEYS.has(keyPath)) {
175
+ result[key] = deepMerge(result[key], value, keyPath);
164
176
  } else {
165
177
  result[key] = value;
166
178
  }
package/src/index.mjs CHANGED
@@ -99,7 +99,8 @@ function walkMarkdownFiles(directory, files, excludedDirs, skipPaths, seen = new
99
99
  let entries;
100
100
  try {
101
101
  entries = readdirSync(directory, { withFileTypes: true });
102
- } catch {
102
+ } catch (err) {
103
+ warn(`Could not read directory ${directory}: ${err.message}`);
103
104
  return;
104
105
  }
105
106
  for (const entry of entries) {
package/src/init.mjs CHANGED
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from
2
2
  import path from 'node:path';
3
3
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
4
4
  import { green, dim } from './color.mjs';
5
+ import { warn } from './util.mjs';
5
6
  import { scaffoldClaudeCommands } from './claude-commands.mjs';
6
7
 
7
8
  const STARTER_CONFIG = `// dotmd.config.mjs — document management configuration
@@ -35,12 +36,12 @@ function scanExistingDocs(dir) {
35
36
 
36
37
  function walk(d) {
37
38
  let entries;
38
- try { entries = readdirSync(d, { withFileTypes: true }); } catch { return; }
39
+ try { entries = readdirSync(d, { withFileTypes: true }); } catch (err) { warn(`Could not read ${d}: ${err.message}`); return; }
39
40
  for (const entry of entries) {
40
41
  if (entry.isDirectory()) { walk(path.join(d, entry.name)); continue; }
41
42
  if (!entry.name.endsWith('.md')) continue;
42
43
  let raw;
43
- try { raw = readFileSync(path.join(d, entry.name), 'utf8'); } catch { continue; }
44
+ try { raw = readFileSync(path.join(d, entry.name), 'utf8'); } catch (err) { warn(`Could not read ${entry.name}: ${err.message}`); continue; }
44
45
  const { frontmatter } = extractFrontmatter(raw);
45
46
  if (!frontmatter) continue;
46
47
  const parsed = parseSimpleFrontmatter(frontmatter);
package/src/notion.mjs CHANGED
@@ -402,7 +402,7 @@ export async function runNotionSync(argv, config, opts = {}) {
402
402
  try {
403
403
  const mdBlocks = await n2m.pageToMarkdown(remote.page.id);
404
404
  body = n2m.toMarkdownString(mdBlocks).parent ?? '';
405
- } catch { /* skip body on error */ }
405
+ } catch (err) { warn(`Could not convert Notion page body for "${title}": ${err.message}`); }
406
406
 
407
407
  const content = `---\n${serializeFrontmatter(fm)}\n---\n\n# ${title}\n\n${body}`;
408
408
  const filePath = path.join(config.docsRoot, slug + '.md');
@@ -456,7 +456,7 @@ export async function runNotionSync(argv, config, opts = {}) {
456
456
  try {
457
457
  const mdBlocks = await n2m.pageToMarkdown(remote.page.id);
458
458
  body = n2m.toMarkdownString(mdBlocks).parent ?? '';
459
- } catch { /* skip body on error */ }
459
+ } catch (err) { warn(`Could not convert Notion page body for "${title}": ${err.message}`); }
460
460
 
461
461
  const content = `---\n${serializeFrontmatter(fm)}\n---\n\n# ${title}\n\n${body}`;
462
462
  const filePath = path.join(config.repoRoot, local.path);
package/src/query.mjs CHANGED
@@ -157,7 +157,7 @@ function getDocSummary(doc, config) {
157
157
  return config.hooks.summarizeDoc
158
158
  ? config.hooks.summarizeDoc(body, meta)
159
159
  : summarizeDocBody(body, meta);
160
- } catch { return null; }
160
+ } catch (err) { warn(`Could not summarize ${doc.path}: ${err.message}`); return null; }
161
161
  }
162
162
 
163
163
  function renderQueryResults(docs, filters, config) {
package/src/render.mjs CHANGED
@@ -151,7 +151,7 @@ function _renderContextSection(docs, ctx, opts, config, lines) {
151
151
  if (summary) {
152
152
  lines.push(` ${''.padEnd(maxSlug)} ${dim('ai: ' + truncate(summary, 120))}`);
153
153
  }
154
- } catch { /* skip on failure */ }
154
+ } catch (err) { warn(`AI summary failed for ${doc.path}: ${err.message}`); }
155
155
  }
156
156
  }
157
157
  lines.push('');