dotmd-cli 0.49.2 → 0.49.4

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.49.2",
3
+ "version": "0.49.4",
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/lifecycle.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
4
- import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter } from './util.mjs';
4
+ import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter, isArchivedPath } from './util.mjs';
5
5
  import { gitMv, getGitLastModified, getGitLastModifiedBatch } from './git.mjs';
6
6
  import { buildIndex, collectDocFiles } from './index.mjs';
7
7
  import { renderIndexFile, writeIndex } from './index-file.mjs';
@@ -169,7 +169,7 @@ export async function runStatus(argv, config, opts = {}) {
169
169
  const archiveDir = path.join(fileRoot, config.archiveDir);
170
170
  const relFromRoot = path.relative(fileRoot, filePath);
171
171
  const relSegments = relFromRoot.split(path.sep);
172
- const inArchive = relFromRoot.startsWith(config.archiveDir + '/') || relFromRoot.startsWith(config.archiveDir + path.sep);
172
+ const inArchive = isArchivedPath(toRepoPath(filePath, config.repoRoot), config);
173
173
  const isArchiving = config.lifecycle.archiveStatuses.has(newStatus) && !inArchive;
174
174
  const isUnarchiving = !config.lifecycle.archiveStatuses.has(newStatus) && inArchive;
175
175
 
@@ -620,8 +620,15 @@ export function runArchive(argv, config, opts = {}) {
620
620
  appendVersionHistory(filePath, `Archived (frontmatter healed in place from \`${oldStatus}\`).`);
621
621
  if (!noIndex) regenIndex(config);
622
622
  out.write(`${green('✓ Healed')}: ${repoPathHeal} (${oldStatus} → archived; file already under \`${config.archiveDir}/\`)\n`);
623
- if (showFiles) emitFilesFooter([repoPathHeal, config.indexPath], config);
624
- return;
623
+ const touched = [repoPathHeal];
624
+ if (config.indexPath && !noIndex) touched.push(config.indexPath);
625
+ if (showFiles) emitFilesFooter(touched, config);
626
+ return {
627
+ action: 'healed',
628
+ oldRepoPath: repoPathHeal,
629
+ newRepoPath: repoPathHeal,
630
+ touched,
631
+ };
625
632
  }
626
633
 
627
634
  const closeoutAction = closeoutTemplate ? planCloseoutInjection(body) : null;
@@ -701,7 +708,12 @@ export function runArchive(argv, config, opts = {}) {
701
708
 
702
709
  try { config.hooks.onArchive?.({ path: newRepoPath, oldStatus }, { oldPath: oldRepoPath, newPath: newRepoPath }); } catch (err) { warn(`Hook 'onArchive' threw: ${err.message}`); }
703
710
 
704
- return { touched };
711
+ return {
712
+ action: 'archived',
713
+ oldRepoPath,
714
+ newRepoPath,
715
+ touched,
716
+ };
705
717
  }
706
718
 
707
719
  // Unified status-transition verb. Collapses status/archive/release into one
@@ -763,9 +775,7 @@ export async function runSet(argv, config, opts = {}) {
763
775
  const parsedFm = parseSimpleFrontmatter(fmRaw);
764
776
  const oldStatus = asString(parsedFm.status);
765
777
 
766
- const fileRoot = findFileRoot(filePath, config);
767
- const relFromRoot = path.relative(fileRoot, filePath);
768
- const inArchive = relFromRoot.startsWith(config.archiveDir + '/') || relFromRoot.startsWith(config.archiveDir + path.sep);
778
+ const inArchive = isArchivedPath(toRepoPath(filePath, config.repoRoot), config);
769
779
 
770
780
  if (config.lifecycle.archiveStatuses.has(newStatus) && !inArchive) {
771
781
  const archiveArgs = [filePath];
@@ -807,11 +817,7 @@ export function runBulkArchive(argv, config, opts = {}) {
807
817
  }
808
818
  }
809
819
 
810
- const unique = [...new Set(matched)].filter(f => {
811
- const root = findFileRoot(f, config);
812
- const rel = path.relative(root, f);
813
- return !rel.startsWith(config.archiveDir + '/') && !rel.startsWith(config.archiveDir + path.sep);
814
- });
820
+ const unique = [...new Set(matched)].filter(f => !isArchivedPath(toRepoPath(f, config.repoRoot), config));
815
821
  if (unique.length === 0) die('No matching files found (already-archived files are excluded).');
816
822
 
817
823
  process.stdout.write(`${unique.length} file(s) to archive:\n`);
package/src/new.mjs CHANGED
@@ -366,14 +366,16 @@ export async function runNew(argv, config, opts = {}) {
366
366
  else if (bodyArg !== null) {
367
367
  bodyInput = readBodyInput(bodyArg);
368
368
  bodyInputSource = bodyArg === '-' ? 'stdin (`-`)' : (bodyArg.startsWith('@') ? `file (\`${bodyArg}\`)` : 'inline body argument');
369
- } else if (template.acceptsBody || template.requiresBody) {
369
+ } else {
370
370
  // Auto-consume piped or redirected stdin so agents don't need the `-`
371
371
  // placeholder for the most common pattern (`cat draft.md | dotmd new …`,
372
372
  // `dotmd new … < draft.md`, or a `<<'EOF'` heredoc). We probe stdin via
373
373
  // fstatSync rather than `!isTTY` so a closed/inherited fd doesn't trigger
374
374
  // a blocking read of an empty stream. We accept FIFO (shell pipes), regular
375
375
  // file (shell redirection / heredoc), and socket (Node spawnSync `input:`
376
- // delivers stdin as an AF_UNIX socket).
376
+ // delivers stdin as an AF_UNIX socket). Probe this even for templates that
377
+ // don't accept bodies so the fail-fast guard below can reject accidental
378
+ // heredoc/input instead of silently scaffolding without it.
377
379
  try {
378
380
  const stat = fstatSync(0);
379
381
  if (stat.isFIFO() || stat.isFile() || stat.isSocket()) {
package/src/prompts.mjs CHANGED
@@ -243,7 +243,7 @@ export function consumePrompt(filePath, config, opts) {
243
243
  if (docType !== 'prompt') {
244
244
  die(`Not a prompt (type: ${docType ?? 'unknown'}): ${repoPath}`);
245
245
  }
246
- if (status === 'archived') {
246
+ if (status === 'archived' || isArchivedPath(repoPath, config)) {
247
247
  die(`Already consumed: ${repoPath}`);
248
248
  }
249
249
 
@@ -267,12 +267,13 @@ export function consumePrompt(filePath, config, opts) {
267
267
  // ever being archived, and the next session sees the same prompt as pending.
268
268
  // Body is already in memory from extractFrontmatter, so the source file
269
269
  // can move out from under us safely.
270
- runArchive([filePath], config, { noIndex, showFiles, out: process.stderr });
270
+ const archiveResult = runArchive([filePath], config, { noIndex, showFiles, out: process.stderr });
271
271
 
272
272
  process.stdout.write(body);
273
273
  if (!body.endsWith('\n')) process.stdout.write('\n');
274
274
 
275
- process.stderr.write(`${green('✓ Consumed')}: ${repoPath}\n`);
275
+ const consumedPath = archiveResult?.newRepoPath ?? repoPath;
276
+ process.stderr.write(`${green('✓ Consumed')}: ${consumedPath}\n`);
276
277
  }
277
278
 
278
279
  function runPromptsArchive(argv, config, opts = {}) {