dotmd-cli 0.8.0 → 0.8.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.8.0",
3
+ "version": "0.8.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",
@@ -41,8 +41,8 @@ export function extractNextStep(body) {
41
41
 
42
42
  export function extractBodyLinks(body) {
43
43
  if (!body) return [];
44
- // Strip fenced code blocks to avoid false positives
45
- const stripped = body.replace(/^```[\s\S]*?^```/gm, '');
44
+ // Strip fenced code blocks and inline code to avoid false positives
45
+ const stripped = body.replace(/^```[\s\S]*?^```/gm, '').replace(/`[^`]+`/g, '');
46
46
  const links = [];
47
47
  // Match [text](path.md) or [text](path.md#anchor), skip images (preceded by !)
48
48
  const regex = /(?<!!)\[([^\]]+)\]\(([^)]+\.md(?:#[^)]*)?)\)/g;
package/src/lifecycle.mjs CHANGED
@@ -188,10 +188,8 @@ export function runTouch(argv, config, opts = {}) {
188
188
  const gitDay = gitDate.slice(0, 10);
189
189
  if (fmUpdated === gitDay) continue;
190
190
 
191
- // Only sync if git is newer than frontmatter
192
- const gitMs = new Date(gitDate).getTime();
193
- const fmMs = fmUpdated ? new Date(fmUpdated).getTime() : 0;
194
- if (fmMs >= gitMs) continue;
191
+ // Only sync if git is newer than frontmatter (compare date strings)
192
+ if (fmUpdated && fmUpdated >= gitDay) continue;
195
193
 
196
194
  if (!dryRun) {
197
195
  updateFrontmatter(filePath, { updated: gitDay });
package/src/lint.mjs CHANGED
@@ -47,6 +47,13 @@ export function runLint(argv, config, opts = {}) {
47
47
  }
48
48
  }
49
49
 
50
+ // Comma-separated surface → surfaces array
51
+ const surfaceVal = asString(parsed.surface);
52
+ if (surfaceVal && surfaceVal.includes(',')) {
53
+ const values = surfaceVal.split(',').map(s => s.trim()).filter(Boolean);
54
+ fixes.push({ field: 'surface', oldValue: surfaceVal, newValue: values, type: 'split-to-array' });
55
+ }
56
+
50
57
  // Trailing whitespace in values
51
58
  for (const line of frontmatter.split('\n')) {
52
59
  const m = line.match(/^([A-Za-z0-9_-]+):(.+\S)\s+$/);
@@ -78,6 +85,8 @@ export function runLint(argv, config, opts = {}) {
78
85
  for (const f of fixes) {
79
86
  if (f.type === 'rename-key') {
80
87
  process.stdout.write(dim(` ${f.oldValue} → ${f.newValue}\n`));
88
+ } else if (f.type === 'split-to-array') {
89
+ process.stdout.write(dim(` ${f.field}: "${f.oldValue}" → surfaces: [${f.newValue.join(', ')}]\n`));
81
90
  } else if (f.type === 'eof') {
82
91
  process.stdout.write(dim(` missing newline at end of file\n`));
83
92
  } else if (f.type === 'add') {
@@ -112,6 +121,7 @@ export function runLint(argv, config, opts = {}) {
112
121
  const keyRenames = [];
113
122
  let needsEofFix = false;
114
123
  const trimFixes = [];
124
+ const splitToArray = [];
115
125
 
116
126
  for (const f of fixes) {
117
127
  if (f.type === 'rename-key') {
@@ -120,12 +130,36 @@ export function runLint(argv, config, opts = {}) {
120
130
  needsEofFix = true;
121
131
  } else if (f.type === 'trim') {
122
132
  trimFixes.push(f);
133
+ } else if (f.type === 'split-to-array') {
134
+ splitToArray.push(f);
123
135
  } else {
124
136
  updates[f.field] = f.newValue;
125
137
  }
126
138
  }
127
139
 
128
140
  if (!dryRun) {
141
+ // Apply split-to-array fixes (surface: a, b → surfaces: array)
142
+ for (const sa of splitToArray) {
143
+ let raw = readFileSync(filePath, 'utf8');
144
+ const { frontmatter: fm } = extractFrontmatter(raw);
145
+ // Remove the scalar surface line
146
+ let newFm = fm.replace(new RegExp(`^${escapeRegex(sa.field)}:.*$`, 'm'), '').replace(/\n{2,}/g, '\n');
147
+ // Check if surfaces: array already exists
148
+ if (newFm.includes('surfaces:')) {
149
+ // Append new values to existing array
150
+ for (const val of sa.newValue) {
151
+ if (!newFm.includes(`- ${val}`)) {
152
+ newFm = newFm.replace(/^(surfaces:)$/m, `$1\n - ${val}`);
153
+ }
154
+ }
155
+ } else {
156
+ // Create new surfaces: array
157
+ newFm += `\nsurfaces:\n${sa.newValue.map(v => ` - ${v}`).join('\n')}`;
158
+ }
159
+ raw = replaceFrontmatter(raw, newFm.trim());
160
+ writeFileSync(filePath, raw, 'utf8');
161
+ }
162
+
129
163
  // Apply key renames and trim fixes via raw string manipulation
130
164
  if (keyRenames.length > 0 || trimFixes.length > 0) {
131
165
  let raw = readFileSync(filePath, 'utf8');
@@ -165,6 +199,8 @@ export function runLint(argv, config, opts = {}) {
165
199
  process.stdout.write(`${prefix} ${dim(`${f.oldValue} → ${f.newValue}`)}\n`);
166
200
  } else if (f.type === 'eof') {
167
201
  process.stdout.write(`${prefix} ${dim('added newline at EOF')}\n`);
202
+ } else if (f.type === 'split-to-array') {
203
+ process.stdout.write(`${prefix} ${dim(`${f.field}: "${f.oldValue}" → surfaces: [${f.newValue.join(', ')}]`)}\n`);
168
204
  } else if (f.type === 'add') {
169
205
  process.stdout.write(`${prefix} ${dim(`add ${f.field}: ${f.newValue}`)}\n`);
170
206
  } else {
package/src/validate.mjs CHANGED
@@ -10,18 +10,21 @@ export function validateDoc(doc, frontmatter, headingTitle, config) {
10
10
  if (!doc.status) {
11
11
  doc.errors.push({ path: doc.path, level: 'error', message: 'Missing frontmatter `status`.' });
12
12
  } else if (!config.validStatuses.has(doc.status)) {
13
- doc.errors.push({ path: doc.path, level: 'error', message: `Invalid status \`${doc.status}\`.` });
13
+ doc.warnings.push({ path: doc.path, level: 'warning', message: `Unknown status \`${doc.status}\`; not in statuses.order.` });
14
14
  }
15
15
 
16
- if (!config.lifecycle.skipWarningsFor.has(doc.status) && !doc.updated) {
16
+ // Only enforce lifecycle fields for known statuses (skip for unknown like implemented, partial, etc.)
17
+ const knownStatus = config.validStatuses.has(doc.status);
18
+
19
+ if (knownStatus && !config.lifecycle.skipWarningsFor.has(doc.status) && !doc.updated) {
17
20
  doc.errors.push({ path: doc.path, level: 'error', message: 'Missing frontmatter `updated` for non-archived doc.' });
18
21
  }
19
22
 
20
- if (doc.auditLevel && doc.auditLevel !== 'none' && !doc.audited) {
23
+ if (knownStatus && doc.auditLevel && doc.auditLevel !== 'none' && !doc.audited) {
21
24
  doc.errors.push({ path: doc.path, level: 'error', message: '`audit_level` is set without `audited`.' });
22
25
  }
23
26
 
24
- if (doc.auditLevel === 'none' && doc.audited) {
27
+ if (knownStatus && doc.auditLevel === 'none' && doc.audited) {
25
28
  doc.errors.push({ path: doc.path, level: 'error', message: '`audit_level: none` cannot be combined with `audited`.' });
26
29
  }
27
30
 
@@ -126,8 +129,8 @@ export function checkGitStaleness(docs, config) {
126
129
  const gitDate = getGitLastModified(doc.path, config.repoRoot);
127
130
  if (!gitDate) continue;
128
131
 
129
- const gitDay = Math.floor(new Date(gitDate).getTime() / 86400000);
130
- const fmDay = Math.floor(new Date(doc.updated).getTime() / 86400000);
132
+ const gitDay = gitDate.slice(0, 10);
133
+ const fmDay = doc.updated.slice(0, 10);
131
134
 
132
135
  if (gitDay > fmDay) {
133
136
  warnings.push({