dotmd-cli 0.68.0 → 0.70.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 +144 -964
- package/bin/dotmd.mjs +241 -197
- package/dotmd.config.example.mjs +5 -8
- package/package.json +6 -10
- package/src/agent-context.mjs +132 -0
- package/src/atomic-mutation.mjs +1505 -0
- package/src/baton.mjs +109 -87
- package/src/bulk-tag.mjs +7 -7
- package/src/commands.mjs +326 -12
- package/src/completions.mjs +38 -98
- package/src/config.mjs +18 -3
- package/src/diff.mjs +7 -3
- package/src/doctor.mjs +12 -5
- package/src/export.mjs +154 -25
- package/src/fix-refs.mjs +2 -0
- package/src/frontmatter-fix.mjs +2 -0
- package/src/frontmatter.mjs +3 -2
- package/src/git.mjs +531 -14
- package/src/graph.mjs +53 -25
- package/src/guard.mjs +163 -60
- package/src/hud.mjs +65 -76
- package/src/index-file.mjs +28 -16
- package/src/index.mjs +17 -12
- package/src/init.mjs +1 -1
- package/src/journal.mjs +145 -12
- package/src/lifecycle.mjs +554 -282
- package/src/lint.mjs +57 -9
- package/src/managed-path.mjs +192 -0
- package/src/migrate-prompts.mjs +2 -0
- package/src/migrate-template.mjs +2 -0
- package/src/migrate.mjs +7 -1
- package/src/new.mjs +135 -54
- package/src/output-identity.mjs +106 -0
- package/src/pickup-card.mjs +24 -10
- package/src/pickup.mjs +457 -0
- package/src/prompts.mjs +138 -32
- package/src/query.mjs +22 -10
- package/src/reference-planner.mjs +292 -0
- package/src/rename.mjs +65 -73
- package/src/render.mjs +17 -8
- package/src/runlist.mjs +109 -71
- package/src/section.mjs +2 -1
- package/src/ship.mjs +39 -20
- package/src/stats.mjs +1 -1
- package/src/status-metadata.mjs +87 -0
- package/src/statuses.mjs +11 -26
- package/src/summary.mjs +14 -3
- package/src/update.mjs +38 -10
- package/src/use.mjs +4 -1
- package/src/util.mjs +1 -0
- package/src/validate.mjs +14 -6
- package/src/watch.mjs +6 -1
- package/src/notion.mjs +0 -528
package/dotmd.config.example.mjs
CHANGED
|
@@ -250,13 +250,6 @@ export const presets = {
|
|
|
250
250
|
// // },
|
|
251
251
|
// };
|
|
252
252
|
|
|
253
|
-
// ─── Notion ──────────────────────────────────────────────────────────────────
|
|
254
|
-
// IMPORTANT: Use environment variables for tokens — never hardcode secrets in config files.
|
|
255
|
-
// export const notion = {
|
|
256
|
-
// token: process.env.NOTION_TOKEN,
|
|
257
|
-
// database: process.env.NOTION_DATABASE_ID,
|
|
258
|
-
// };
|
|
259
|
-
|
|
260
253
|
// ─── Function Hooks ──────────────────────────────────────────────────────────
|
|
261
254
|
// Hooks are optional. Each receives a default implementation it can wrap or replace.
|
|
262
255
|
|
|
@@ -290,7 +283,11 @@ export const presets = {
|
|
|
290
283
|
// export function onNew({ path, status, title, template }) {}
|
|
291
284
|
// export function onRename({ oldPath, newPath, referencesUpdated }) {}
|
|
292
285
|
// export function onLint({ path, fixes }) {}
|
|
293
|
-
//
|
|
286
|
+
// Pickup hooks are delivered at least once. A retry reuses operationId, so any
|
|
287
|
+
// external side effect must deduplicate by that stable ID. Delivery uses a
|
|
288
|
+
// persisted process-identity lease; live/unverifiable owners are not reclaimed
|
|
289
|
+
// by age, and an expired lease retries only when its owner is demonstrably dead.
|
|
290
|
+
// export function onPickup({ path, oldStatus, newStatus, operationId }) {}
|
|
294
291
|
|
|
295
292
|
// AI hooks — override summarization (replaces local MLX model).
|
|
296
293
|
// export function summarizeDoc(body, meta) { return 'Custom summary'; }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotmd-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export,
|
|
3
|
+
"version": "0.70.0",
|
|
4
|
+
"description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, lifecycle, and AI summaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"rfc",
|
|
27
27
|
"document-management",
|
|
28
28
|
"yaml",
|
|
29
|
-
"notion",
|
|
30
29
|
"graph",
|
|
31
30
|
"export",
|
|
32
31
|
"ai-summary",
|
|
@@ -41,15 +40,12 @@
|
|
|
41
40
|
"scripts": {
|
|
42
41
|
"test": "node --test test/*.test.mjs",
|
|
43
42
|
"postinstall": "node scripts/postinstall.mjs",
|
|
44
|
-
"preversion": "npm test",
|
|
45
|
-
"version": "node
|
|
46
|
-
"postversion": "bash scripts/postversion.sh"
|
|
43
|
+
"preversion": "node scripts/release-preflight.mjs && npm test",
|
|
44
|
+
"version": "node scripts/prepare-version-commit.mjs",
|
|
45
|
+
"postversion": "bash scripts/postversion.sh",
|
|
46
|
+
"release:resume": "node scripts/recover-local-release.mjs && bash scripts/postversion.sh"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=20"
|
|
50
|
-
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"@notionhq/client": "^5.13.0",
|
|
53
|
-
"notion-to-md": "^3.1.9"
|
|
54
50
|
}
|
|
55
51
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { isArchivedPath, truncate } from './util.mjs';
|
|
2
|
+
import { isCoordinationHub } from './runlist.mjs';
|
|
3
|
+
import {
|
|
4
|
+
actionablePromptStatuses,
|
|
5
|
+
comparePromptDocs,
|
|
6
|
+
compareStrings,
|
|
7
|
+
resolveStatusMetadata,
|
|
8
|
+
statusMetadataFor,
|
|
9
|
+
} from './status-metadata.mjs';
|
|
10
|
+
|
|
11
|
+
export const AGENT_CONTEXT_SCHEMA = Object.freeze({ name: 'dotmd.agent-context', version: 1 });
|
|
12
|
+
|
|
13
|
+
const LIMITS = Object.freeze({ vocabulary: 32, prompts: 8, plans: 12, hubs: 8, issues: 10 });
|
|
14
|
+
|
|
15
|
+
export function boundedCollection(items, limit) {
|
|
16
|
+
const selected = items.slice(0, limit);
|
|
17
|
+
return {
|
|
18
|
+
total: items.length,
|
|
19
|
+
shown: selected.length,
|
|
20
|
+
truncated: selected.length < items.length,
|
|
21
|
+
items: selected,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function compactDoc(doc) {
|
|
26
|
+
const blockers = (doc.blockers ?? []).map(item => truncate(String(item), 160));
|
|
27
|
+
return {
|
|
28
|
+
path: doc.path,
|
|
29
|
+
title: truncate(doc.title ?? '', 160),
|
|
30
|
+
status: doc.status,
|
|
31
|
+
type: doc.type,
|
|
32
|
+
nextStep: doc.nextStep ? truncate(doc.nextStep, 300) : null,
|
|
33
|
+
blockers: boundedCollection(blockers, 3),
|
|
34
|
+
daysSinceUpdate: doc.daysSinceUpdate ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function rankMap(metadata, type) {
|
|
39
|
+
return new Map((metadata.byType[type] ?? []).map(item => [item.name, item.rank]));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compareByStatusAndPath(ranks) {
|
|
43
|
+
return (a, b) => (ranks.get(a.status) ?? Number.MAX_SAFE_INTEGER) - (ranks.get(b.status) ?? Number.MAX_SAFE_INTEGER)
|
|
44
|
+
|| compareStrings(a.path, b.path);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function issueItem(issue) {
|
|
48
|
+
return { path: issue.path ?? null, message: truncate(issue.message ?? String(issue), 300) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildAgentContext(index, config, options = {}) {
|
|
52
|
+
const metadata = resolveStatusMetadata(config);
|
|
53
|
+
const planRanks = rankMap(metadata, 'plan');
|
|
54
|
+
const closed = (doc) => isArchivedPath(doc.path, config)
|
|
55
|
+
|| config.lifecycle.archiveStatuses.has(doc.status)
|
|
56
|
+
|| config.lifecycle.terminalStatuses.has(doc.status);
|
|
57
|
+
const livePlans = index.docs.filter(doc => doc.type === 'plan' && !closed(doc));
|
|
58
|
+
const hubs = livePlans.filter(isCoordinationHub).sort((a, b) => compareStrings(a.path, b.path));
|
|
59
|
+
const leaves = livePlans.filter(doc => !isCoordinationHub(doc));
|
|
60
|
+
const focus = leaves
|
|
61
|
+
.filter(doc => statusMetadataFor(config, 'plan', doc.status)?.context === 'expanded')
|
|
62
|
+
.sort(compareByStatusAndPath(planRanks));
|
|
63
|
+
const listed = leaves
|
|
64
|
+
.filter(doc => statusMetadataFor(config, 'plan', doc.status)?.context === 'listed')
|
|
65
|
+
.sort(compareByStatusAndPath(planRanks));
|
|
66
|
+
const stale = leaves
|
|
67
|
+
.filter(doc => doc.isStale && !statusMetadataFor(config, 'plan', doc.status)?.skipStale)
|
|
68
|
+
.sort((a, b) => (b.daysSinceUpdate ?? -1) - (a.daysSinceUpdate ?? -1)
|
|
69
|
+
|| compareByStatusAndPath(planRanks)(a, b));
|
|
70
|
+
|
|
71
|
+
const promptStatuses = actionablePromptStatuses(config);
|
|
72
|
+
const prompts = index.docs
|
|
73
|
+
.filter(doc => doc.type === 'prompt' && promptStatuses.has(doc.status) && !closed(doc))
|
|
74
|
+
.sort(comparePromptDocs);
|
|
75
|
+
|
|
76
|
+
const vocabulary = {};
|
|
77
|
+
const vocabularyTypes = options.types?.length
|
|
78
|
+
? metadata.typeOrder.filter(type => options.types.includes(type))
|
|
79
|
+
: metadata.typeOrder;
|
|
80
|
+
for (const type of vocabularyTypes) {
|
|
81
|
+
vocabulary[type] = boundedCollection(
|
|
82
|
+
(metadata.byType[type] ?? []).map(item => ({
|
|
83
|
+
name: item.name,
|
|
84
|
+
context: item.context,
|
|
85
|
+
staleDays: item.staleDays,
|
|
86
|
+
startable: item.startable,
|
|
87
|
+
terminal: item.terminal,
|
|
88
|
+
archive: item.archive,
|
|
89
|
+
})),
|
|
90
|
+
LIMITS.vocabulary,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const issueSort = (a, b) => compareStrings(a.path ?? '', b.path ?? '')
|
|
95
|
+
|| compareStrings(a.message ?? '', b.message ?? '');
|
|
96
|
+
const errors = [...index.errors].sort(issueSort).map(issueItem);
|
|
97
|
+
const warnings = [...index.warnings].sort(issueSort).map(issueItem);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
schema: AGENT_CONTEXT_SCHEMA,
|
|
101
|
+
generatedAt: options.generatedAt ?? new Date().toISOString(),
|
|
102
|
+
scope: {
|
|
103
|
+
roots: options.roots?.length ? [...options.roots] : null,
|
|
104
|
+
types: options.types?.length ? [...options.types] : null,
|
|
105
|
+
},
|
|
106
|
+
statusVocabulary: vocabulary,
|
|
107
|
+
counts: {
|
|
108
|
+
documents: index.docs.length,
|
|
109
|
+
byStatus: { ...index.countsByStatus },
|
|
110
|
+
byType: Object.fromEntries(Object.entries(index.countsByType).map(([type, counts]) => [type, { ...counts }])),
|
|
111
|
+
errors: index.errors.length,
|
|
112
|
+
warnings: index.warnings.length,
|
|
113
|
+
},
|
|
114
|
+
prompts: {
|
|
115
|
+
actionable: boundedCollection(prompts.map(compactDoc), LIMITS.prompts),
|
|
116
|
+
next: prompts[0] ? compactDoc(prompts[0]) : null,
|
|
117
|
+
},
|
|
118
|
+
plans: {
|
|
119
|
+
focus: boundedCollection(focus.map(compactDoc), LIMITS.plans),
|
|
120
|
+
listed: boundedCollection(listed.map(compactDoc), LIMITS.plans),
|
|
121
|
+
stale: boundedCollection(stale.map(compactDoc), LIMITS.plans),
|
|
122
|
+
hubs: boundedCollection(hubs.map(compactDoc), LIMITS.hubs),
|
|
123
|
+
},
|
|
124
|
+
issues: {
|
|
125
|
+
errors: boundedCollection(errors, LIMITS.issues),
|
|
126
|
+
warnings: boundedCollection(warnings, LIMITS.issues),
|
|
127
|
+
},
|
|
128
|
+
...(options.skippedHooks?.length ? {
|
|
129
|
+
validationPreview: { status: 'built-in-only', skippedHooks: [...options.skippedHooks] },
|
|
130
|
+
} : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|