dotmd-cli 0.49.5 → 0.50.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
@@ -879,9 +879,15 @@ export const index = {
879
879
  path: 'docs/docs.md',
880
880
  startMarker: '<!-- GENERATED:dotmd:start -->',
881
881
  endMarker: '<!-- GENERATED:dotmd:end -->',
882
+ snapshot: 'status', // default; use 'state' to include live current_state text
882
883
  };
883
884
  ```
884
885
 
886
+ Generated indexes default to status-only rows for live sections so README files
887
+ do not become stale mirrors of volatile `current_state` text. Set
888
+ `snapshot: 'state'` if you want the older `Status Snapshot` table for live
889
+ sections too. Archived highlights still include their historical snapshots.
890
+
885
891
  All exports are optional. Additional options: `context`, `display`, `presets`, `templates`, `excludeDirs`, `notion`. See [`dotmd.config.example.mjs`](dotmd.config.example.mjs) for the full reference.
886
892
 
887
893
  Config discovery walks up from cwd looking for `dotmd.config.mjs` or `.dotmd.config.mjs`.
@@ -144,6 +144,7 @@ export const index = {
144
144
  path: 'docs/docs.md',
145
145
  startMarker: '<!-- GENERATED:dotmd:start -->',
146
146
  endMarker: '<!-- GENERATED:dotmd:end -->',
147
+ snapshot: 'status', // default; use 'state' to include live current_state text
147
148
  archivedLimit: 8,
148
149
  };
149
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.49.5",
3
+ "version": "0.50.0",
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
@@ -316,6 +316,10 @@ function validateConfig(userConfig, config, validStatuses, indexPath) {
316
316
  warnings.push(`Config: index path does not exist: ${indexPath}`);
317
317
  }
318
318
 
319
+ if (config.index?.snapshot !== undefined && !['status', 'state'].includes(config.index.snapshot)) {
320
+ warnings.push("Config: index.snapshot must be 'status' or 'state'.");
321
+ }
322
+
319
323
  // Unknown top-level user config keys
320
324
  for (const key of Object.keys(userConfig)) {
321
325
  if (!VALID_CONFIG_KEYS.has(key)) {
@@ -512,6 +516,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
512
516
  indexPath,
513
517
  indexStartMarker: config.index?.startMarker ?? '<!-- GENERATED:dotmd:start -->',
514
518
  indexEndMarker: config.index?.endMarker ?? '<!-- GENERATED:dotmd:end -->',
519
+ indexSnapshot: config.index?.snapshot ?? 'status',
515
520
  archivedHighlightLimit: config.index?.archivedLimit ?? 8,
516
521
 
517
522
  context: config.context,
package/src/hud.mjs CHANGED
@@ -4,21 +4,11 @@ import { readLeases, findStaleLeases, currentSessionId, isLeaseStale, STALE_LEAS
4
4
  import { scrubStaleSilently } from './lease-scrub.mjs';
5
5
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
6
6
  import { asString, toRepoPath } from './util.mjs';
7
- import { dim, yellow } from './color.mjs';
7
+ import { dim } from './color.mjs';
8
8
  import { buildIndex } from './index.mjs';
9
9
  import { refreshStaleSlashCommands } from './claude-commands.mjs';
10
10
  import { readJournalEntries, journalFilePath } from './journal.mjs';
11
11
 
12
- const MAX_PREVIEW = 5;
13
-
14
- function slug(repoPath) { return path.basename(repoPath, '.md'); }
15
-
16
- function previewList(items, max = MAX_PREVIEW) {
17
- const slugs = items.slice(0, max).map(slug);
18
- const more = items.length > max ? `, +${items.length - max} more` : '';
19
- return slugs.join(', ') + more;
20
- }
21
-
22
12
  // Statuses that count as "actionable" for a prompt are derived from config:
23
13
  // types.prompt.context.expanded (the statuses the user wants prominently shown).
24
14
  // Falls back to ['pending'] when no prompt type is configured (defensive default
@@ -223,11 +213,12 @@ export function runHud(argv, config) {
223
213
  const hud = buildHud(config);
224
214
 
225
215
  // Self-heal stale slash-command files. Wrapped: a broken scaffolder must
226
- // never kill the SessionStart hook (would block every session). Skipped in
227
- // --json mode to keep the structured shape stable for programmatic callers.
228
- let refreshed = [];
216
+ // never kill the SessionStart hook (would block every session). Runs for its
217
+ // side effect only the refresh is no longer announced in stdout (see the
218
+ // primer-only contract below). Skipped in --json mode to keep the structured
219
+ // shape stable for programmatic callers.
229
220
  if (!json) {
230
- try { refreshed = refreshStaleSlashCommands(config); }
221
+ try { refreshStaleSlashCommands(config); }
231
222
  catch { /* swallow — see comment above */ }
232
223
  }
233
224
 
@@ -236,58 +227,15 @@ export function runHud(argv, config) {
236
227
  return;
237
228
  }
238
229
 
239
- const lines = [];
240
-
241
- // Always-on command primer. Replaces the prior plan-state / prompts /
242
- // stuck-leases / validation-errors lines those signals belong inside
243
- // their own commands (`plans`, `prompts`), not in the SessionStart hook.
244
- // hud's job is purely to remind the agent which verbs exist, since that's
245
- // the one thing the agent reaches for `--help` to recover. Keep it tight:
246
- // one line, the minimum verb set.
247
- lines.push(dim('dotmd: plans|briefing set <status> [<file>] new <type> <slug> use [<file>] archive <file> (use [no-arg] oldest pending prompt)'));
248
-
249
- const state = [];
250
- if (hud.owned?.length) state.push(`held: ${hud.owned.length} (${previewList(hud.owned)})`);
251
- if (hud.prompts?.length) state.push(`prompts: ${hud.prompts.length} (${previewList(hud.prompts)})`);
252
- if (hud.stale?.length) state.push(`stuck: ${hud.stale.length} (${previewList(hud.stale)})`);
253
- if (hud.errors > 0) state.push(`errors: ${hud.errors} (run dotmd check)`);
254
- if (state.length) lines.push(yellow(state.join(' · ')));
255
-
256
- if (refreshed.length > 0) {
257
- const from = refreshed[0].from;
258
- const to = refreshed[0].to;
259
- const names = refreshed.map(r => r.name).join(', ');
260
- lines.push(dim(`↻ slash commands refreshed (v${from} → v${to}): ${names}`));
261
- }
262
-
263
- // F17b: three journal-aware sections. Silent-when-clean: each block emits
264
- // only when it has entries.
265
- if (hud.previousSelf?.length) {
266
- lines.push(dim('— previous self —'));
267
- for (const e of hud.previousSelf) {
268
- const cmd = (e.argv ?? []).join(' ');
269
- const exitTag = e.exit === 0 ? '' : `, exit ${e.exit}`;
270
- lines.push(dim(` ${cmd} (${e.ago}${exitTag})`));
271
- }
272
- }
273
-
274
- if (hud.fleet?.length) {
275
- lines.push(dim('— fleet (last 24h) —'));
276
- for (const f of hud.fleet) {
277
- const heldTag = f.holding?.length
278
- ? ` · holding ${f.holding.map(p => path.basename(p, '.md')).join(', ')}`
279
- : '';
280
- const staleTag = f.stale ? yellow(' [stale]') : '';
281
- lines.push(dim(` session ${f.sid} · ${f.cmds} cmds · last ${f.lastAgo}${heldTag}`) + staleTag);
282
- }
283
- }
284
-
285
- if (hud.recentRejections?.length) {
286
- lines.push(dim('— recent rejections (last 1h) —'));
287
- for (const r of hud.recentRejections) {
288
- lines.push(dim(` ${r.count}× "${r.cls}" on \`${r.cmd}\``));
289
- }
290
- }
291
-
292
- process.stdout.write(lines.join('\n') + '\n');
230
+ // SessionStart contract: emit ONLY the command primer — the verb cheat-sheet
231
+ // that tells the agent which dotmd verbs exist. Everything else hud used to
232
+ // print (held/prompts/stuck/errors state, slash-command refresh notices, and
233
+ // the journal-aware previous-self / fleet / recent-rejections sections) is
234
+ // deliberately suppressed here: those signals nudged agents into phantom
235
+ // follow-up work e.g. "errors: 1 (run dotmd check)" prompting a check run
236
+ // for state that belongs inside its own command. Each of those signals lives
237
+ // in its proper command (`plans`, `prompts`, `check`) and stays available via
238
+ // `dotmd hud --json` for programmatic callers. The hook's job is purely to
239
+ // teach the verbs, never to report status.
240
+ process.stdout.write(dim('dotmd: plans|briefing set <status> [<file>] new <type> <slug> use [<file>] archive <file> (use [no-arg] → oldest pending prompt)') + '\n');
293
241
  }
@@ -21,6 +21,7 @@ export function renderIndexFile(index, config) {
21
21
  function renderGeneratedBlock(index, config) {
22
22
  const lines = [];
23
23
  const indexDir = config.indexPath ? path.dirname(path.relative(config.repoRoot, config.indexPath)).split(path.sep).join('/') : '';
24
+ const snapshotMode = config.indexSnapshot ?? 'status';
24
25
 
25
26
  for (const status of config.statusOrder) {
26
27
  const docs = index.docs.filter(doc => doc.status === status);
@@ -34,10 +35,9 @@ function renderGeneratedBlock(index, config) {
34
35
 
35
36
  lines.push(`## ${capitalize(status)}`);
36
37
  lines.push('');
37
- lines.push('| Doc | Status Snapshot |');
38
- lines.push('|-----|-----------------|');
38
+ lines.push(...snapshotHeader(snapshotMode));
39
39
  for (const doc of docs) {
40
- const snapshot = formatSnapshot(doc, config);
40
+ const snapshot = renderIndexSnapshot(doc, config, snapshotMode);
41
41
  const linkPath = indexDir ? path.relative(indexDir, doc.path).split(path.sep).join('/') : doc.path;
42
42
  lines.push(`| [${escapeTable(doc.title)}](${linkPath}) | ${escapeTable(snapshot)} |`);
43
43
  }
@@ -76,6 +76,16 @@ function renderArchivedSection(docs, config, status) {
76
76
  return lines;
77
77
  }
78
78
 
79
+ function snapshotHeader(snapshotMode) {
80
+ if (snapshotMode === 'state') return ['| Doc | Status Snapshot |', '|-----|-----------------|'];
81
+ return ['| Doc | Status |', '|-----|--------|'];
82
+ }
83
+
84
+ function renderIndexSnapshot(doc, config, snapshotMode) {
85
+ if (snapshotMode === 'state') return formatSnapshot(doc, config);
86
+ return capitalize(doc.status ?? 'unknown');
87
+ }
88
+
79
89
  export function writeIndex(content, config) {
80
90
  writeFileSync(config.indexPath, content, 'utf8');
81
91
  }
package/src/init.mjs CHANGED
@@ -189,6 +189,7 @@ function generateDetectedConfig(scan, rootPath) {
189
189
  lines.push(` path: '${rootPath}/docs.md',`);
190
190
  lines.push(` startMarker: '<!-- GENERATED:dotmd:start -->',`);
191
191
  lines.push(` endMarker: '<!-- GENERATED:dotmd:end -->',`);
192
+ lines.push(` snapshot: 'status',`);
192
193
  lines.push('};');
193
194
  lines.push('');
194
195