dotmd-cli 0.67.0 → 0.68.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/package.json +1 -1
- package/src/baton.mjs +3 -1
- package/src/lifecycle.mjs +61 -12
package/package.json
CHANGED
package/src/baton.mjs
CHANGED
|
@@ -203,7 +203,9 @@ export async function runBaton(argv, config, opts = {}) {
|
|
|
203
203
|
} else {
|
|
204
204
|
const setArgs = [status, planPath];
|
|
205
205
|
if (note) setArgs.push('--note', note);
|
|
206
|
-
|
|
206
|
+
// viaBaton: this release IS the handoff — don't let runSet nudge "leave a
|
|
207
|
+
// baton" on top of the baton we're in the middle of.
|
|
208
|
+
archiveResult = await runSet(setArgs, config, { dryRun, viaBaton: true });
|
|
207
209
|
statusChanged = true;
|
|
208
210
|
}
|
|
209
211
|
}
|
package/src/lifecycle.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { extractFrontmatter, parseSimpleFrontmatter, normalizeEol } from './frontmatter.mjs';
|
|
4
|
-
import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter, isArchivedPath } from './util.mjs';
|
|
4
|
+
import { asString, toRepoPath, die, warn, resolveDocPath, resolveRefPath, escapeRegex, nowIso, suggestCandidates, emitFilesFooter, isArchivedPath, currentSessionId } from './util.mjs';
|
|
5
|
+
import { readJournalEntries } from './journal.mjs';
|
|
5
6
|
import { gitMv, getGitLastModifiedBatch } from './git.mjs';
|
|
6
7
|
import { buildIndex, collectDocFiles, resolveDocArg } from './index.mjs';
|
|
7
8
|
import { renderIndexFile, writeIndex } from './index-file.mjs';
|
|
@@ -598,6 +599,24 @@ export function runArchive(argv, config, opts = {}) {
|
|
|
598
599
|
// `dotmd set in-session <path>` is refused — acquiring a lease is asymmetric
|
|
599
600
|
// enough to deserve its own verb (`dotmd pickup`), and silently routing here
|
|
600
601
|
// would skip the lease-acquisition path entirely.
|
|
602
|
+
|
|
603
|
+
// Did THIS session already hand off via `dotmd baton`? The journal records the
|
|
604
|
+
// top-level argv of every invocation; a successful `baton …` means a resume
|
|
605
|
+
// prompt was saved this session, so the closure nudge would be redundant. Best
|
|
606
|
+
// effort — a disabled journal degrades to "might nudge," which is harmless for
|
|
607
|
+
// an advisory line. (baton's own internal `runSet` is suppressed by `viaBaton`,
|
|
608
|
+
// since its journal entry isn't written until the process exits.)
|
|
609
|
+
function batonSavedThisSession(config) {
|
|
610
|
+
const sid = currentSessionId();
|
|
611
|
+
let entries = [];
|
|
612
|
+
try { entries = readJournalEntries(config); } catch { return false; }
|
|
613
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
614
|
+
const e = entries[i];
|
|
615
|
+
if (e?.sid === sid && (e.exit ?? 0) === 0 && Array.isArray(e.argv) && e.argv[0] === 'baton') return true;
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
|
|
601
620
|
export async function runSet(argv, config, opts = {}) {
|
|
602
621
|
const { dryRun } = opts;
|
|
603
622
|
const noIndex = argv.includes('--no-index');
|
|
@@ -631,26 +650,56 @@ export async function runSet(argv, config, opts = {}) {
|
|
|
631
650
|
return runArchive(archiveArgs, config, { dryRun, note, archiveStatus: newStatus });
|
|
632
651
|
}
|
|
633
652
|
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
// only
|
|
653
|
+
// Two advisory reminders, both computed from one pre-transition frontmatter
|
|
654
|
+
// read (a non-archive `set` never moves the file, so the path stays valid).
|
|
655
|
+
// Advisory only — never block the transition.
|
|
637
656
|
let partialReminder = false;
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
657
|
+
let batonNudge = false;
|
|
658
|
+
try {
|
|
659
|
+
const { frontmatter: fmRaw, body } = extractFrontmatter(readFileSync(filePath, 'utf8'));
|
|
660
|
+
const fm = parseSimpleFrontmatter(fmRaw);
|
|
661
|
+
|
|
662
|
+
// `partial` promises a successor tracking the deferred tail. When neither a
|
|
663
|
+
// --note nor any doc reference exists to point at it, remind.
|
|
664
|
+
if (newStatus === 'partial' && !note) {
|
|
665
|
+
const related = fm.related_plans;
|
|
642
666
|
const hasRelated = Array.isArray(related) ? related.length > 0 : Boolean(asString(related)?.trim());
|
|
643
667
|
partialReminder = !hasRelated && !/[\w./-]+\.md\b/.test(body);
|
|
644
|
-
}
|
|
645
|
-
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Baton-on-exit backstop (the one core-loop step with no other mechanical
|
|
671
|
+
// catch). Fire ONLY on the baton-less in-session release: a plan you were
|
|
672
|
+
// actively working (old status `in-session`) is being parked at a
|
|
673
|
+
// non-terminal stop status while a known next pickup (`next_step`) remains,
|
|
674
|
+
// and no baton was saved this session. `archived` never reaches here (routed
|
|
675
|
+
// to runArchive above), so this can't fire on a fully-done close. Gating on
|
|
676
|
+
// `in-session` keeps it off pure triage of never-started plans — where no
|
|
677
|
+
// session work is owed a handoff — which is how the nag-fatigue risk is
|
|
678
|
+
// managed. Suppressed for baton's own internal release via `opts.viaBaton`.
|
|
679
|
+
if (!opts.viaBaton) {
|
|
680
|
+
const docType = asString(fm.type) ?? 'plan';
|
|
681
|
+
const oldStatus = asString(fm.status);
|
|
682
|
+
const nextStep = asString(fm.next_step)?.trim();
|
|
683
|
+
batonNudge = docType === 'plan'
|
|
684
|
+
&& oldStatus === 'in-session'
|
|
685
|
+
&& newStatus !== 'in-session'
|
|
686
|
+
&& Boolean(nextStep)
|
|
687
|
+
&& !batonSavedThisSession(config);
|
|
688
|
+
}
|
|
689
|
+
} catch { /* advisory only */ }
|
|
646
690
|
|
|
647
691
|
const statusArgs = [filePath, newStatus];
|
|
648
692
|
if (noIndex) statusArgs.push('--no-index');
|
|
649
693
|
if (showFiles) statusArgs.push('--show-files');
|
|
650
694
|
await runStatus(statusArgs, config, { dryRun, suppressDeprecation: true, note });
|
|
651
695
|
|
|
652
|
-
if (
|
|
653
|
-
|
|
696
|
+
if (!dryRun) {
|
|
697
|
+
if (partialReminder) {
|
|
698
|
+
warn('partial usually references the successor plan tracking the tail — add a link to the body, or rerun with --note "tail tracked in <plan>".');
|
|
699
|
+
}
|
|
700
|
+
if (batonNudge) {
|
|
701
|
+
warn(`wrapping up? leave a baton so the next session picks up cleanly — \`dotmd baton ${path.basename(filePath, '.md')} @draft\` saves a resume prompt (no copy-paste into chat).`);
|
|
702
|
+
}
|
|
654
703
|
}
|
|
655
704
|
}
|
|
656
705
|
|