mdkg 0.0.1 → 0.0.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/README.md +20 -6
- package/dist/cli.js +667 -11
- package/dist/commands/checkpoint.js +133 -0
- package/dist/commands/format.js +297 -0
- package/dist/commands/guide.js +22 -0
- package/dist/commands/index.js +17 -0
- package/dist/commands/init.js +111 -0
- package/dist/commands/list.js +52 -0
- package/dist/commands/new.js +279 -0
- package/dist/commands/next.js +75 -0
- package/dist/commands/node_card.js +17 -0
- package/dist/commands/pack.js +105 -0
- package/dist/commands/search.js +70 -0
- package/dist/commands/show.js +95 -0
- package/dist/commands/validate.js +229 -0
- package/dist/commands/workspace.js +101 -0
- package/dist/core/config.js +162 -0
- package/dist/core/migrate.js +30 -0
- package/dist/core/paths.js +14 -0
- package/dist/graph/edges.js +64 -0
- package/dist/graph/frontmatter.js +132 -0
- package/dist/graph/index_cache.js +50 -0
- package/dist/graph/indexer.js +144 -0
- package/dist/graph/node.js +225 -0
- package/dist/graph/staleness.js +31 -0
- package/dist/graph/template_schema.js +86 -0
- package/dist/graph/validate_graph.js +115 -0
- package/dist/graph/workspace_files.js +64 -0
- package/dist/init/AGENTS.md +43 -0
- package/dist/init/CLAUDE.md +37 -0
- package/dist/init/config.json +67 -0
- package/dist/init/core/core.md +12 -0
- package/dist/init/core/guide.md +99 -0
- package/dist/init/core/rule-1-mdkg-conventions.md +232 -0
- package/dist/init/core/rule-2-context-pack-rules.md +186 -0
- package/dist/init/core/rule-3-cli-contract.md +177 -0
- package/dist/init/core/rule-4-repo-safety-and-ignores.md +97 -0
- package/dist/init/core/rule-5-release-and-versioning.md +82 -0
- package/dist/init/core/rule-6-templates-and-schemas.md +186 -0
- package/dist/init/templates/default/bug.md +54 -0
- package/dist/init/templates/default/chk.md +55 -0
- package/dist/init/templates/default/dec.md +38 -0
- package/dist/init/templates/default/edd.md +50 -0
- package/dist/init/templates/default/epic.md +46 -0
- package/dist/init/templates/default/feat.md +35 -0
- package/dist/init/templates/default/prd.md +59 -0
- package/dist/init/templates/default/prop.md +45 -0
- package/dist/init/templates/default/rule.md +33 -0
- package/dist/init/templates/default/task.md +53 -0
- package/dist/init/templates/default/test.md +49 -0
- package/dist/pack/export_json.js +38 -0
- package/dist/pack/export_md.js +93 -0
- package/dist/pack/export_toon.js +7 -0
- package/dist/pack/export_xml.js +73 -0
- package/dist/pack/order.js +162 -0
- package/dist/pack/pack.js +181 -0
- package/dist/pack/types.js +2 -0
- package/dist/pack/verbose_core.js +23 -0
- package/dist/templates/loader.js +82 -0
- package/dist/util/argparse.js +154 -0
- package/dist/util/date.js +9 -0
- package/dist/util/errors.js +12 -0
- package/dist/util/filter.js +26 -0
- package/dist/util/output.js +50 -0
- package/dist/util/qid.js +54 -0
- package/dist/util/sort.js +40 -0
- package/package.json +18 -2
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runNewCommand = runNewCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("../core/config");
|
|
10
|
+
const index_cache_1 = require("../graph/index_cache");
|
|
11
|
+
const node_1 = require("../graph/node");
|
|
12
|
+
const indexer_1 = require("../graph/indexer");
|
|
13
|
+
const loader_1 = require("../templates/loader");
|
|
14
|
+
const date_1 = require("../util/date");
|
|
15
|
+
const errors_1 = require("../util/errors");
|
|
16
|
+
const qid_1 = require("../util/qid");
|
|
17
|
+
const ID_RE = /^[a-z]+-[0-9]+$/;
|
|
18
|
+
const ID_REF_RE = /^([a-z][a-z0-9_]*:)?[a-z]+-[0-9]+$/;
|
|
19
|
+
const DEC_ID_RE = /^dec-[0-9]+$/;
|
|
20
|
+
const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
|
|
21
|
+
const CORE_TYPES = new Set(["rule"]);
|
|
22
|
+
const DESIGN_TYPES = new Set(["prd", "edd", "dec", "prop"]);
|
|
23
|
+
function parseCsvList(raw) {
|
|
24
|
+
if (!raw) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
return raw
|
|
28
|
+
.split(",")
|
|
29
|
+
.map((value) => value.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
function normalizeId(value, key) {
|
|
33
|
+
const normalized = value.toLowerCase();
|
|
34
|
+
if (!ID_RE.test(normalized) && normalized !== "rule-guide") {
|
|
35
|
+
throw new errors_1.UsageError(`${key} entries must match <prefix>-<number>: ${value}`);
|
|
36
|
+
}
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
function normalizeIdRef(value, key) {
|
|
40
|
+
const normalized = value.toLowerCase();
|
|
41
|
+
if (!ID_REF_RE.test(normalized)) {
|
|
42
|
+
throw new errors_1.UsageError(`${key} entries must match <id> or <ws>:<id>: ${value}`);
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
function normalizeList(raw) {
|
|
47
|
+
return parseCsvList(raw);
|
|
48
|
+
}
|
|
49
|
+
function normalizeLowercaseList(raw) {
|
|
50
|
+
return parseCsvList(raw).map((value) => value.toLowerCase());
|
|
51
|
+
}
|
|
52
|
+
function normalizeIdList(raw, key) {
|
|
53
|
+
return parseCsvList(raw).map((value) => normalizeId(value, key));
|
|
54
|
+
}
|
|
55
|
+
function normalizeIdRefList(raw, key) {
|
|
56
|
+
return parseCsvList(raw).map((value) => normalizeIdRef(value, key));
|
|
57
|
+
}
|
|
58
|
+
function normalizeWorkspace(value) {
|
|
59
|
+
if (!value) {
|
|
60
|
+
return "root";
|
|
61
|
+
}
|
|
62
|
+
const normalized = value.toLowerCase();
|
|
63
|
+
if (normalized === "all") {
|
|
64
|
+
throw new errors_1.UsageError("--ws all is not valid for creation");
|
|
65
|
+
}
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
68
|
+
function slugifyTitle(title) {
|
|
69
|
+
const slug = title
|
|
70
|
+
.trim()
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
73
|
+
.replace(/^-+|-+$/g, "")
|
|
74
|
+
.replace(/-+/g, "-");
|
|
75
|
+
if (!slug) {
|
|
76
|
+
return "node";
|
|
77
|
+
}
|
|
78
|
+
const maxLen = 80;
|
|
79
|
+
return slug.length > maxLen ? slug.slice(0, maxLen).replace(/-+$/g, "") : slug;
|
|
80
|
+
}
|
|
81
|
+
function nextIdForPrefix(index, ws, prefix) {
|
|
82
|
+
let max = 0;
|
|
83
|
+
const pattern = new RegExp(`^${prefix}-(\\d+)$`);
|
|
84
|
+
for (const node of Object.values(index)) {
|
|
85
|
+
if (node.ws !== ws) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const match = pattern.exec(node.id);
|
|
89
|
+
if (!match) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const parsed = Number.parseInt(match[1] ?? "", 10);
|
|
93
|
+
if (Number.isInteger(parsed) && parsed > max) {
|
|
94
|
+
max = parsed;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return `${prefix}-${max + 1}`;
|
|
98
|
+
}
|
|
99
|
+
function idPrefixForType(type) {
|
|
100
|
+
if (type === "checkpoint") {
|
|
101
|
+
return "chk";
|
|
102
|
+
}
|
|
103
|
+
return type;
|
|
104
|
+
}
|
|
105
|
+
function folderForType(type) {
|
|
106
|
+
if (CORE_TYPES.has(type)) {
|
|
107
|
+
return "core";
|
|
108
|
+
}
|
|
109
|
+
if (DESIGN_TYPES.has(type)) {
|
|
110
|
+
return "design";
|
|
111
|
+
}
|
|
112
|
+
if (node_1.WORK_TYPES.has(type)) {
|
|
113
|
+
return "work";
|
|
114
|
+
}
|
|
115
|
+
throw new errors_1.UsageError(`unsupported type: ${type}`);
|
|
116
|
+
}
|
|
117
|
+
function ensureExists(index, value, ws, label) {
|
|
118
|
+
const resolved = (0, qid_1.resolveQid)(index, value, ws);
|
|
119
|
+
if (resolved.status !== "ok") {
|
|
120
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)(label, value, resolved, ws));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function runNewCommand(options) {
|
|
124
|
+
const title = options.title.trim();
|
|
125
|
+
if (!title) {
|
|
126
|
+
throw new errors_1.UsageError("title cannot be empty");
|
|
127
|
+
}
|
|
128
|
+
const type = options.type.toLowerCase();
|
|
129
|
+
if (!node_1.ALLOWED_TYPES.has(type)) {
|
|
130
|
+
throw new errors_1.UsageError(`type must be one of ${Array.from(node_1.ALLOWED_TYPES).join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
133
|
+
const ws = normalizeWorkspace(options.ws);
|
|
134
|
+
if (!config.workspaces[ws]) {
|
|
135
|
+
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
136
|
+
}
|
|
137
|
+
const noCache = Boolean(options.noCache);
|
|
138
|
+
const noReindex = Boolean(options.noReindex);
|
|
139
|
+
const { index } = (0, index_cache_1.loadIndex)({
|
|
140
|
+
root: options.root,
|
|
141
|
+
config,
|
|
142
|
+
useCache: !noCache,
|
|
143
|
+
allowReindex: !noReindex,
|
|
144
|
+
});
|
|
145
|
+
const prefix = idPrefixForType(type);
|
|
146
|
+
const id = nextIdForPrefix(index.nodes, ws, prefix);
|
|
147
|
+
const slug = slugifyTitle(title);
|
|
148
|
+
const fileName = `${id}-${slug}.md`;
|
|
149
|
+
const wsEntry = config.workspaces[ws];
|
|
150
|
+
const folder = folderForType(type);
|
|
151
|
+
const targetDir = path_1.default.resolve(options.root, wsEntry.path, wsEntry.mdkg_dir, folder);
|
|
152
|
+
const filePath = path_1.default.join(targetDir, fileName);
|
|
153
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
154
|
+
throw new errors_1.UsageError(`node already exists: ${path_1.default.relative(options.root, filePath)}`);
|
|
155
|
+
}
|
|
156
|
+
const statusInput = options.status?.toLowerCase();
|
|
157
|
+
let status;
|
|
158
|
+
if (node_1.WORK_TYPES.has(type)) {
|
|
159
|
+
const allowed = new Set(config.work.status_enum.map((value) => value.toLowerCase()));
|
|
160
|
+
status = statusInput ?? config.work.status_enum[0]?.toLowerCase();
|
|
161
|
+
if (!status || !allowed.has(status)) {
|
|
162
|
+
throw new errors_1.UsageError(`--status must be one of ${Array.from(allowed).join(", ")}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (type === "dec") {
|
|
166
|
+
status = statusInput ?? "proposed";
|
|
167
|
+
if (!DEC_STATUS.has(status)) {
|
|
168
|
+
throw new errors_1.UsageError(`--status must be one of ${Array.from(DEC_STATUS).join(", ")}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else if (statusInput) {
|
|
172
|
+
throw new errors_1.UsageError("--status is only valid for work items and decisions");
|
|
173
|
+
}
|
|
174
|
+
let priority;
|
|
175
|
+
if (node_1.WORK_TYPES.has(type)) {
|
|
176
|
+
if (options.priority !== undefined) {
|
|
177
|
+
priority = options.priority;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
priority = config.work.priority_max;
|
|
181
|
+
}
|
|
182
|
+
if (!Number.isInteger(priority) || priority < config.work.priority_min || priority > config.work.priority_max) {
|
|
183
|
+
throw new errors_1.UsageError(`--priority must be between ${config.work.priority_min} and ${config.work.priority_max}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (options.priority !== undefined) {
|
|
187
|
+
throw new errors_1.UsageError("--priority is only valid for work items");
|
|
188
|
+
}
|
|
189
|
+
if (!node_1.WORK_TYPES.has(type)) {
|
|
190
|
+
if (options.epic || options.parent || options.prev || options.next || options.blockedBy || options.blocks) {
|
|
191
|
+
throw new errors_1.UsageError("epic/parent/prev/next/blocked-by/blocks are only valid for work items");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (options.cases && type !== "test") {
|
|
195
|
+
throw new errors_1.UsageError("--cases is only valid for test nodes");
|
|
196
|
+
}
|
|
197
|
+
const epic = options.epic ? normalizeIdRef(options.epic, "--epic") : undefined;
|
|
198
|
+
const parent = options.parent ? normalizeIdRef(options.parent, "--parent") : undefined;
|
|
199
|
+
const prev = options.prev ? normalizeIdRef(options.prev, "--prev") : undefined;
|
|
200
|
+
const next = options.next ? normalizeIdRef(options.next, "--next") : undefined;
|
|
201
|
+
const relates = normalizeIdRefList(options.relates, "--relates");
|
|
202
|
+
const blockedBy = normalizeIdRefList(options.blockedBy, "--blocked-by");
|
|
203
|
+
const blocks = normalizeIdRefList(options.blocks, "--blocks");
|
|
204
|
+
const refs = normalizeIdList(options.refs, "--refs");
|
|
205
|
+
const aliases = normalizeLowercaseList(options.aliases);
|
|
206
|
+
const tags = normalizeLowercaseList(options.tags);
|
|
207
|
+
const owners = normalizeLowercaseList(options.owners);
|
|
208
|
+
const cases = normalizeLowercaseList(options.cases);
|
|
209
|
+
const links = normalizeList(options.links);
|
|
210
|
+
const artifacts = normalizeList(options.artifacts);
|
|
211
|
+
if (type === "dec" && options.supersedes) {
|
|
212
|
+
const supersedes = options.supersedes.toLowerCase();
|
|
213
|
+
if (!DEC_ID_RE.test(supersedes)) {
|
|
214
|
+
throw new errors_1.UsageError("--supersedes must be a dec-# id");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (options.supersedes && type !== "dec") {
|
|
218
|
+
throw new errors_1.UsageError("--supersedes is only valid for dec records");
|
|
219
|
+
}
|
|
220
|
+
const idRefsToCheck = [];
|
|
221
|
+
if (epic) {
|
|
222
|
+
idRefsToCheck.push(["epic", epic]);
|
|
223
|
+
}
|
|
224
|
+
if (parent) {
|
|
225
|
+
idRefsToCheck.push(["parent", parent]);
|
|
226
|
+
}
|
|
227
|
+
if (prev) {
|
|
228
|
+
idRefsToCheck.push(["prev", prev]);
|
|
229
|
+
}
|
|
230
|
+
if (next) {
|
|
231
|
+
idRefsToCheck.push(["next", next]);
|
|
232
|
+
}
|
|
233
|
+
for (const value of relates) {
|
|
234
|
+
idRefsToCheck.push(["relates", value]);
|
|
235
|
+
}
|
|
236
|
+
for (const value of blockedBy) {
|
|
237
|
+
idRefsToCheck.push(["blocked_by", value]);
|
|
238
|
+
}
|
|
239
|
+
for (const value of blocks) {
|
|
240
|
+
idRefsToCheck.push(["blocks", value]);
|
|
241
|
+
}
|
|
242
|
+
for (const [label, value] of idRefsToCheck) {
|
|
243
|
+
ensureExists(index, value, ws, label);
|
|
244
|
+
}
|
|
245
|
+
const today = (0, date_1.formatDate)(options.now ?? new Date());
|
|
246
|
+
const template = (0, loader_1.loadTemplate)(options.root, config, type, options.template);
|
|
247
|
+
const content = (0, loader_1.renderTemplate)(template, {
|
|
248
|
+
id,
|
|
249
|
+
type,
|
|
250
|
+
title,
|
|
251
|
+
status,
|
|
252
|
+
priority,
|
|
253
|
+
epic,
|
|
254
|
+
parent,
|
|
255
|
+
prev,
|
|
256
|
+
next,
|
|
257
|
+
relates: relates.length > 0 ? relates : undefined,
|
|
258
|
+
blocked_by: blockedBy.length > 0 ? blockedBy : undefined,
|
|
259
|
+
blocks: blocks.length > 0 ? blocks : undefined,
|
|
260
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
261
|
+
owners: owners.length > 0 ? owners : undefined,
|
|
262
|
+
links: links.length > 0 ? links : undefined,
|
|
263
|
+
artifacts: artifacts.length > 0 ? artifacts : undefined,
|
|
264
|
+
refs: refs.length > 0 ? refs : undefined,
|
|
265
|
+
aliases: aliases.length > 0 ? aliases : undefined,
|
|
266
|
+
cases: cases.length > 0 ? cases : undefined,
|
|
267
|
+
supersedes: options.supersedes ? options.supersedes.toLowerCase() : undefined,
|
|
268
|
+
created: today,
|
|
269
|
+
updated: today,
|
|
270
|
+
});
|
|
271
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
272
|
+
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
273
|
+
if (config.index.auto_reindex && !noReindex) {
|
|
274
|
+
const updatedIndex = (0, indexer_1.buildIndex)(options.root, config, { tolerant: config.index.tolerant });
|
|
275
|
+
const outputPath = path_1.default.resolve(options.root, config.index.global_index_path);
|
|
276
|
+
(0, index_cache_1.writeIndex)(outputPath, updatedIndex);
|
|
277
|
+
}
|
|
278
|
+
console.log(`node created: ${ws}:${id} (${path_1.default.relative(options.root, filePath)})`);
|
|
279
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runNextCommand = runNextCommand;
|
|
4
|
+
const config_1 = require("../core/config");
|
|
5
|
+
const index_cache_1 = require("../graph/index_cache");
|
|
6
|
+
const errors_1 = require("../util/errors");
|
|
7
|
+
const qid_1 = require("../util/qid");
|
|
8
|
+
const sort_1 = require("../util/sort");
|
|
9
|
+
const node_card_1 = require("./node_card");
|
|
10
|
+
const NEXT_TYPES = new Set(["feat", "task", "bug", "test"]);
|
|
11
|
+
const NO_MATCH_MESSAGE = 'no matching work items found; consider `mdkg new task "..."` or `mdkg new test "..."`';
|
|
12
|
+
function normalizeWorkspace(value) {
|
|
13
|
+
if (!value || value === "all") {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function selectNextByPriority(index, ws, statusPreference, priorityMax) {
|
|
19
|
+
const statusRanks = new Set(statusPreference);
|
|
20
|
+
const candidates = Object.values(index.nodes).filter((node) => {
|
|
21
|
+
if (ws && node.ws !== ws) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (!NEXT_TYPES.has(node.type)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (!node.status) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (!statusRanks.has(node.status)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
const sorted = (0, sort_1.sortNodesForNext)(candidates, {
|
|
36
|
+
statusPreference,
|
|
37
|
+
priorityMax,
|
|
38
|
+
});
|
|
39
|
+
return sorted[0];
|
|
40
|
+
}
|
|
41
|
+
function runNextCommand(options) {
|
|
42
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
43
|
+
const ws = normalizeWorkspace(options.ws);
|
|
44
|
+
if (ws && !config.workspaces[ws]) {
|
|
45
|
+
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
46
|
+
}
|
|
47
|
+
const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
|
|
48
|
+
root: options.root,
|
|
49
|
+
config,
|
|
50
|
+
useCache: !options.noCache,
|
|
51
|
+
allowReindex: !options.noReindex,
|
|
52
|
+
});
|
|
53
|
+
if (stale && !rebuilt && !options.noCache) {
|
|
54
|
+
console.error("warning: index is stale; run mdkg index to refresh");
|
|
55
|
+
}
|
|
56
|
+
if (options.id) {
|
|
57
|
+
const resolved = (0, qid_1.resolveQid)(index, options.id, ws);
|
|
58
|
+
if (resolved.status !== "ok") {
|
|
59
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
|
|
60
|
+
}
|
|
61
|
+
const node = index.nodes[resolved.qid];
|
|
62
|
+
const nextQid = node.edges.next;
|
|
63
|
+
if (nextQid && index.nodes[nextQid]) {
|
|
64
|
+
console.log((0, node_card_1.formatNodeCard)(index.nodes[nextQid]));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const statusPreference = config.work.next.status_preference.map((status) => status.toLowerCase());
|
|
69
|
+
const selected = selectNextByPriority(index, ws, statusPreference, config.work.priority_max);
|
|
70
|
+
if (!selected) {
|
|
71
|
+
console.error(NO_MATCH_MESSAGE);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log((0, node_card_1.formatNodeCard)(selected));
|
|
75
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatNodeCard = formatNodeCard;
|
|
4
|
+
function formatStatusPriority(node) {
|
|
5
|
+
const status = node.status ?? "-";
|
|
6
|
+
const priority = node.priority === undefined ? "-" : `p${node.priority}`;
|
|
7
|
+
return `${status}/${priority}`;
|
|
8
|
+
}
|
|
9
|
+
function formatNodeCard(node) {
|
|
10
|
+
return [
|
|
11
|
+
node.qid,
|
|
12
|
+
node.type,
|
|
13
|
+
formatStatusPriority(node),
|
|
14
|
+
node.title,
|
|
15
|
+
node.path,
|
|
16
|
+
].join(" | ");
|
|
17
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runPackCommand = runPackCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("../core/config");
|
|
10
|
+
const index_cache_1 = require("../graph/index_cache");
|
|
11
|
+
const errors_1 = require("../util/errors");
|
|
12
|
+
const qid_1 = require("../util/qid");
|
|
13
|
+
const output_1 = require("../util/output");
|
|
14
|
+
const pack_1 = require("../pack/pack");
|
|
15
|
+
const export_md_1 = require("../pack/export_md");
|
|
16
|
+
const export_json_1 = require("../pack/export_json");
|
|
17
|
+
const export_toon_1 = require("../pack/export_toon");
|
|
18
|
+
const export_xml_1 = require("../pack/export_xml");
|
|
19
|
+
const EDGE_KEYS = new Set(["parent", "epic", "relates", "blocked_by", "blocks", "prev", "next"]);
|
|
20
|
+
const FORMAT_KEYS = new Set(["md", "json", "toon", "xml"]);
|
|
21
|
+
function normalizeWorkspace(value) {
|
|
22
|
+
if (!value || value === "all") {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function normalizeEdges(edges) {
|
|
28
|
+
const normalized = edges.map((edge) => edge.toLowerCase().replace(/-/g, "_"));
|
|
29
|
+
const invalid = normalized.filter((edge) => !EDGE_KEYS.has(edge));
|
|
30
|
+
if (invalid.length > 0) {
|
|
31
|
+
throw new errors_1.UsageError(`invalid edge name(s): ${invalid.join(", ")}`);
|
|
32
|
+
}
|
|
33
|
+
return Array.from(new Set(normalized));
|
|
34
|
+
}
|
|
35
|
+
function normalizeFormat(format) {
|
|
36
|
+
if (!format) {
|
|
37
|
+
return "md";
|
|
38
|
+
}
|
|
39
|
+
const normalized = format.toLowerCase();
|
|
40
|
+
if (!FORMAT_KEYS.has(normalized)) {
|
|
41
|
+
throw new errors_1.UsageError(`invalid format: ${format}`);
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
function runPackCommand(options) {
|
|
46
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
47
|
+
const ws = normalizeWorkspace(options.ws);
|
|
48
|
+
if (ws && !config.workspaces[ws]) {
|
|
49
|
+
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
50
|
+
}
|
|
51
|
+
const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
|
|
52
|
+
root: options.root,
|
|
53
|
+
config,
|
|
54
|
+
useCache: !options.noCache,
|
|
55
|
+
allowReindex: !options.noReindex,
|
|
56
|
+
});
|
|
57
|
+
if (stale && !rebuilt && !options.noCache) {
|
|
58
|
+
console.error("warning: index is stale; run mdkg index to refresh");
|
|
59
|
+
}
|
|
60
|
+
const resolved = (0, qid_1.resolveQid)(index, options.id, ws);
|
|
61
|
+
if (resolved.status !== "ok") {
|
|
62
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
|
|
63
|
+
}
|
|
64
|
+
const depth = options.depth ?? config.pack.default_depth;
|
|
65
|
+
if (!Number.isInteger(depth) || depth < 0) {
|
|
66
|
+
throw new errors_1.UsageError("--depth must be a non-negative integer");
|
|
67
|
+
}
|
|
68
|
+
const extraEdges = options.edges ? normalizeEdges(options.edges) : [];
|
|
69
|
+
const edges = normalizeEdges([...config.pack.default_edges, ...extraEdges]);
|
|
70
|
+
const buildResult = (0, pack_1.buildPack)({
|
|
71
|
+
root: options.root,
|
|
72
|
+
index,
|
|
73
|
+
rootQid: resolved.qid,
|
|
74
|
+
depth,
|
|
75
|
+
edges,
|
|
76
|
+
verbose: Boolean(options.verbose),
|
|
77
|
+
maxNodes: config.pack.limits.max_nodes,
|
|
78
|
+
verboseCoreListPath: path_1.default.resolve(options.root, config.pack.verbose_core_list_path),
|
|
79
|
+
wsHint: ws,
|
|
80
|
+
});
|
|
81
|
+
for (const warning of buildResult.warnings) {
|
|
82
|
+
console.error(`warning: ${warning}`);
|
|
83
|
+
}
|
|
84
|
+
const format = normalizeFormat(options.format);
|
|
85
|
+
let output = "";
|
|
86
|
+
if (format === "md") {
|
|
87
|
+
output = (0, export_md_1.exportMarkdown)(buildResult.pack, config.pack.limits.max_bytes).content;
|
|
88
|
+
}
|
|
89
|
+
else if (format === "json") {
|
|
90
|
+
output = (0, export_json_1.exportJson)(buildResult.pack);
|
|
91
|
+
}
|
|
92
|
+
else if (format === "toon") {
|
|
93
|
+
output = (0, export_toon_1.exportToon)(buildResult.pack);
|
|
94
|
+
}
|
|
95
|
+
else if (format === "xml") {
|
|
96
|
+
output = (0, export_xml_1.exportXml)(buildResult.pack);
|
|
97
|
+
}
|
|
98
|
+
const rootId = resolved.qid.split(":")[1] ?? resolved.qid;
|
|
99
|
+
const outPath = options.out
|
|
100
|
+
? path_1.default.resolve(options.root, options.out)
|
|
101
|
+
: (0, output_1.buildDefaultPackPath)(options.root, rootId, format, Boolean(options.verbose), new Date());
|
|
102
|
+
fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
|
|
103
|
+
fs_1.default.writeFileSync(outPath, output, "utf8");
|
|
104
|
+
console.log(`pack written: ${outPath}`);
|
|
105
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSearchCommand = runSearchCommand;
|
|
4
|
+
const config_1 = require("../core/config");
|
|
5
|
+
const index_cache_1 = require("../graph/index_cache");
|
|
6
|
+
const filter_1 = require("../util/filter");
|
|
7
|
+
const errors_1 = require("../util/errors");
|
|
8
|
+
const sort_1 = require("../util/sort");
|
|
9
|
+
const node_card_1 = require("./node_card");
|
|
10
|
+
function normalizeWorkspace(value) {
|
|
11
|
+
if (!value || value === "all") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function buildSearchText(node) {
|
|
17
|
+
const tokens = [
|
|
18
|
+
node.id,
|
|
19
|
+
node.qid,
|
|
20
|
+
node.title,
|
|
21
|
+
node.path,
|
|
22
|
+
...node.tags,
|
|
23
|
+
...node.owners,
|
|
24
|
+
...node.links,
|
|
25
|
+
...node.artifacts,
|
|
26
|
+
...node.refs,
|
|
27
|
+
...node.aliases,
|
|
28
|
+
];
|
|
29
|
+
return tokens.join(" ").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function matchesQuery(node, terms) {
|
|
32
|
+
const text = buildSearchText(node);
|
|
33
|
+
for (const term of terms) {
|
|
34
|
+
if (!text.includes(term)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
function runSearchCommand(options) {
|
|
41
|
+
const query = options.query.trim();
|
|
42
|
+
if (!query) {
|
|
43
|
+
throw new errors_1.UsageError("search query cannot be empty");
|
|
44
|
+
}
|
|
45
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
46
|
+
const ws = normalizeWorkspace(options.ws);
|
|
47
|
+
if (ws && !config.workspaces[ws]) {
|
|
48
|
+
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
49
|
+
}
|
|
50
|
+
const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
|
|
51
|
+
root: options.root,
|
|
52
|
+
config,
|
|
53
|
+
useCache: !options.noCache,
|
|
54
|
+
allowReindex: !options.noReindex,
|
|
55
|
+
});
|
|
56
|
+
if (stale && !rebuilt && !options.noCache) {
|
|
57
|
+
console.error("warning: index is stale; run mdkg index to refresh");
|
|
58
|
+
}
|
|
59
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
60
|
+
const filtered = (0, filter_1.filterNodes)(Object.values(index.nodes), {
|
|
61
|
+
ws,
|
|
62
|
+
type: options.type,
|
|
63
|
+
status: options.status,
|
|
64
|
+
});
|
|
65
|
+
const matches = filtered.filter((node) => matchesQuery(node, terms));
|
|
66
|
+
const sorted = (0, sort_1.sortNodesByQid)(matches);
|
|
67
|
+
for (const node of sorted) {
|
|
68
|
+
console.log((0, node_card_1.formatNodeCard)(node));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runShowCommand = runShowCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("../core/config");
|
|
10
|
+
const index_cache_1 = require("../graph/index_cache");
|
|
11
|
+
const frontmatter_1 = require("../graph/frontmatter");
|
|
12
|
+
const errors_1 = require("../util/errors");
|
|
13
|
+
const qid_1 = require("../util/qid");
|
|
14
|
+
const node_card_1 = require("./node_card");
|
|
15
|
+
function normalizeWorkspace(value) {
|
|
16
|
+
if (!value || value === "all") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function maybeLine(label, values) {
|
|
22
|
+
if (values.length === 0) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return `${label}: ${values.join(", ")}`;
|
|
26
|
+
}
|
|
27
|
+
function runShowCommand(options) {
|
|
28
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
29
|
+
const ws = normalizeWorkspace(options.ws);
|
|
30
|
+
if (ws && !config.workspaces[ws]) {
|
|
31
|
+
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
32
|
+
}
|
|
33
|
+
const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
|
|
34
|
+
root: options.root,
|
|
35
|
+
config,
|
|
36
|
+
useCache: !options.noCache,
|
|
37
|
+
allowReindex: !options.noReindex,
|
|
38
|
+
});
|
|
39
|
+
if (stale && !rebuilt && !options.noCache) {
|
|
40
|
+
console.error("warning: index is stale; run mdkg index to refresh");
|
|
41
|
+
}
|
|
42
|
+
const resolved = (0, qid_1.resolveQid)(index, options.id, ws);
|
|
43
|
+
if (resolved.status !== "ok") {
|
|
44
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
|
|
45
|
+
}
|
|
46
|
+
const node = index.nodes[resolved.qid];
|
|
47
|
+
const lines = [];
|
|
48
|
+
lines.push((0, node_card_1.formatNodeCard)(node));
|
|
49
|
+
const metaLines = [
|
|
50
|
+
maybeLine("tags", node.tags),
|
|
51
|
+
maybeLine("owners", node.owners),
|
|
52
|
+
maybeLine("links", node.links),
|
|
53
|
+
maybeLine("artifacts", node.artifacts),
|
|
54
|
+
maybeLine("refs", node.refs),
|
|
55
|
+
maybeLine("aliases", node.aliases),
|
|
56
|
+
].filter((line) => Boolean(line));
|
|
57
|
+
lines.push(...metaLines);
|
|
58
|
+
if (node.edges.epic) {
|
|
59
|
+
lines.push(`epic: ${node.edges.epic}`);
|
|
60
|
+
}
|
|
61
|
+
if (node.edges.parent) {
|
|
62
|
+
lines.push(`parent: ${node.edges.parent}`);
|
|
63
|
+
}
|
|
64
|
+
if (node.edges.prev) {
|
|
65
|
+
lines.push(`prev: ${node.edges.prev}`);
|
|
66
|
+
}
|
|
67
|
+
if (node.edges.next) {
|
|
68
|
+
lines.push(`next: ${node.edges.next}`);
|
|
69
|
+
}
|
|
70
|
+
const relatesLine = maybeLine("relates", node.edges.relates);
|
|
71
|
+
if (relatesLine) {
|
|
72
|
+
lines.push(relatesLine);
|
|
73
|
+
}
|
|
74
|
+
const blockedByLine = maybeLine("blocked_by", node.edges.blocked_by);
|
|
75
|
+
if (blockedByLine) {
|
|
76
|
+
lines.push(blockedByLine);
|
|
77
|
+
}
|
|
78
|
+
const blocksLine = maybeLine("blocks", node.edges.blocks);
|
|
79
|
+
if (blocksLine) {
|
|
80
|
+
lines.push(blocksLine);
|
|
81
|
+
}
|
|
82
|
+
if (options.includeBody) {
|
|
83
|
+
const filePath = path_1.default.resolve(options.root, node.path);
|
|
84
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
85
|
+
throw new errors_1.NotFoundError(`file not found for ${node.qid}: ${node.path}`);
|
|
86
|
+
}
|
|
87
|
+
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
88
|
+
const body = (0, frontmatter_1.parseFrontmatter)(content, filePath).body.trimEnd();
|
|
89
|
+
if (body.length > 0) {
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push(body);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log(lines.join("\n"));
|
|
95
|
+
}
|