mdkg 0.0.2 → 0.0.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.
Files changed (50) hide show
  1. package/README.md +171 -151
  2. package/dist/cli.js +920 -422
  3. package/dist/commands/checkpoint.js +17 -6
  4. package/dist/commands/doctor.js +156 -0
  5. package/dist/commands/event.js +46 -0
  6. package/dist/commands/event_support.js +146 -0
  7. package/dist/commands/format.js +6 -7
  8. package/dist/commands/index.js +10 -4
  9. package/dist/commands/init.js +202 -11
  10. package/dist/commands/list.js +18 -1
  11. package/dist/commands/new.js +30 -5
  12. package/dist/commands/pack.js +332 -10
  13. package/dist/commands/query_output.js +84 -0
  14. package/dist/commands/search.js +22 -5
  15. package/dist/commands/show.js +26 -11
  16. package/dist/commands/skill.js +359 -0
  17. package/dist/commands/skill_support.js +121 -0
  18. package/dist/commands/task.js +270 -0
  19. package/dist/commands/validate.js +104 -7
  20. package/dist/graph/edges.js +2 -2
  21. package/dist/graph/frontmatter.js +1 -0
  22. package/dist/graph/indexer.js +21 -0
  23. package/dist/graph/node.js +20 -4
  24. package/dist/graph/skills_index_cache.js +94 -0
  25. package/dist/graph/skills_indexer.js +160 -0
  26. package/dist/init/README.md +43 -0
  27. package/dist/init/core/rule-1-mdkg-conventions.md +9 -2
  28. package/dist/init/core/rule-3-cli-contract.md +73 -14
  29. package/dist/init/core/rule-4-repo-safety-and-ignores.md +9 -3
  30. package/dist/init/core/rule-6-templates-and-schemas.md +6 -2
  31. package/dist/init/skills/SKILL.md.example +41 -0
  32. package/dist/init/templates/default/bug.md +1 -0
  33. package/dist/init/templates/default/chk.md +1 -0
  34. package/dist/init/templates/default/epic.md +1 -0
  35. package/dist/init/templates/default/feat.md +1 -0
  36. package/dist/init/templates/default/task.md +1 -0
  37. package/dist/init/templates/default/test.md +1 -0
  38. package/dist/pack/budget.js +186 -0
  39. package/dist/pack/export_md.js +17 -1
  40. package/dist/pack/export_xml.js +15 -0
  41. package/dist/pack/metrics.js +66 -0
  42. package/dist/pack/pack.js +35 -0
  43. package/dist/pack/profile.js +222 -0
  44. package/dist/pack/stats.js +37 -0
  45. package/dist/templates/headings.js +34 -0
  46. package/dist/util/argparse.js +47 -1
  47. package/dist/util/filter.js +18 -0
  48. package/dist/util/id.js +23 -0
  49. package/dist/util/output.js +2 -2
  50. package/package.json +6 -2
@@ -8,16 +8,25 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const config_1 = require("../core/config");
10
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");
11
+ const frontmatter_1 = require("../graph/frontmatter");
12
+ const skills_index_cache_1 = require("../graph/skills_index_cache");
13
+ const node_1 = require("../graph/node");
14
+ const budget_1 = require("../pack/budget");
16
15
  const export_json_1 = require("../pack/export_json");
16
+ const export_md_1 = require("../pack/export_md");
17
17
  const export_toon_1 = require("../pack/export_toon");
18
18
  const export_xml_1 = require("../pack/export_xml");
19
+ const metrics_1 = require("../pack/metrics");
20
+ const pack_1 = require("../pack/pack");
21
+ const profile_1 = require("../pack/profile");
22
+ const stats_1 = require("../pack/stats");
23
+ const headings_1 = require("../templates/headings");
24
+ const errors_1 = require("../util/errors");
25
+ const output_1 = require("../util/output");
26
+ const qid_1 = require("../util/qid");
19
27
  const EDGE_KEYS = new Set(["parent", "epic", "relates", "blocked_by", "blocks", "prev", "next"]);
20
28
  const FORMAT_KEYS = new Set(["md", "json", "toon", "xml"]);
29
+ const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21
30
  function normalizeWorkspace(value) {
22
31
  if (!value || value === "all") {
23
32
  return undefined;
@@ -42,6 +51,205 @@ function normalizeFormat(format) {
42
51
  }
43
52
  return normalized;
44
53
  }
54
+ function normalizeSkillSlug(slug) {
55
+ const normalized = slug.toLowerCase().trim();
56
+ if (!SKILL_SLUG_RE.test(normalized)) {
57
+ throw new errors_1.UsageError(`invalid skill slug: ${slug}`);
58
+ }
59
+ return normalized;
60
+ }
61
+ function resolveSkillsPolicy(raw) {
62
+ if (!raw) {
63
+ return { mode: "auto" };
64
+ }
65
+ const normalized = raw.toLowerCase().trim();
66
+ if (!normalized || normalized === "auto") {
67
+ return { mode: "auto" };
68
+ }
69
+ if (normalized === "none") {
70
+ return { mode: "none" };
71
+ }
72
+ const slugs = normalized
73
+ .split(",")
74
+ .map((value) => value.trim())
75
+ .filter(Boolean)
76
+ .map((value) => normalizeSkillSlug(value));
77
+ if (slugs.length === 0) {
78
+ return { mode: "none" };
79
+ }
80
+ return { mode: "list", slugs: Array.from(new Set(slugs)) };
81
+ }
82
+ function resolveSkillsDepth(raw) {
83
+ if (!raw) {
84
+ return "meta";
85
+ }
86
+ const normalized = raw.toLowerCase().trim();
87
+ if (normalized === "meta" || normalized === "full") {
88
+ return normalized;
89
+ }
90
+ throw new errors_1.UsageError("--skills-depth must be meta or full");
91
+ }
92
+ function renderSkillMetaBody(skill) {
93
+ const lines = [];
94
+ lines.push(`description: ${skill.description}`);
95
+ if (skill.tags.length > 0) {
96
+ lines.push(`tags: ${skill.tags.join(", ")}`);
97
+ }
98
+ if (skill.version) {
99
+ lines.push(`version: ${skill.version}`);
100
+ }
101
+ if (skill.authors.length > 0) {
102
+ lines.push(`authors: ${skill.authors.join(", ")}`);
103
+ }
104
+ if (skill.links.length > 0) {
105
+ lines.push(`links: ${skill.links.join(", ")}`);
106
+ }
107
+ lines.push(`has_scripts: ${skill.has_scripts ? "true" : "false"}`);
108
+ lines.push(`has_references: ${skill.has_references ? "true" : "false"}`);
109
+ for (const [key, value] of Object.entries(skill.ochatr).sort(([a], [b]) => a.localeCompare(b))) {
110
+ if (Array.isArray(value)) {
111
+ lines.push(`${key}: ${value.join(", ")}`);
112
+ }
113
+ else if (typeof value === "boolean") {
114
+ lines.push(`${key}: ${value ? "true" : "false"}`);
115
+ }
116
+ else {
117
+ lines.push(`${key}: ${value}`);
118
+ }
119
+ }
120
+ return lines.join("\n").trimEnd();
121
+ }
122
+ function loadSkillFullBody(root, skill) {
123
+ const skillPath = path_1.default.resolve(root, skill.path);
124
+ if (!fs_1.default.existsSync(skillPath)) {
125
+ return renderSkillMetaBody(skill);
126
+ }
127
+ const content = fs_1.default.readFileSync(skillPath, "utf8");
128
+ return (0, frontmatter_1.parseFrontmatter)(content, skillPath).body.trimEnd();
129
+ }
130
+ function uniqueSkillOrder(slugs) {
131
+ const seen = new Set();
132
+ const ordered = [];
133
+ for (const slug of slugs) {
134
+ if (seen.has(slug)) {
135
+ continue;
136
+ }
137
+ seen.add(slug);
138
+ ordered.push(slug);
139
+ }
140
+ return ordered;
141
+ }
142
+ function collectReferencedSkills(indexQids, indexNodes) {
143
+ const slugs = [];
144
+ for (const qid of indexQids) {
145
+ const node = indexNodes[qid];
146
+ if (!node) {
147
+ continue;
148
+ }
149
+ if (node.type === "skill") {
150
+ continue;
151
+ }
152
+ slugs.push(...node.skills);
153
+ }
154
+ return uniqueSkillOrder(slugs);
155
+ }
156
+ function appendSkillsToPack(pack, skillEntries, depth, root) {
157
+ const existing = new Set(pack.nodes.map((node) => node.qid));
158
+ const newNodes = skillEntries
159
+ .filter((skill) => !existing.has(skill.qid))
160
+ .map((skill) => ({
161
+ qid: skill.qid,
162
+ id: skill.id,
163
+ workspace: skill.ws,
164
+ type: "skill",
165
+ title: skill.name,
166
+ status: undefined,
167
+ priority: undefined,
168
+ path: skill.path,
169
+ links: skill.links,
170
+ artifacts: [],
171
+ refs: [],
172
+ aliases: [skill.slug, ...skill.tags],
173
+ body: depth === "full" ? loadSkillFullBody(root, skill) : renderSkillMetaBody(skill),
174
+ }));
175
+ if (newNodes.length === 0) {
176
+ return pack;
177
+ }
178
+ const mergedNodes = [...pack.nodes, ...newNodes];
179
+ return {
180
+ meta: {
181
+ ...pack.meta,
182
+ node_count: mergedNodes.length,
183
+ },
184
+ nodes: mergedNodes,
185
+ };
186
+ }
187
+ function applyNodeCountLimit(pack, maxNodes) {
188
+ if (maxNodes <= 0 || pack.nodes.length <= maxNodes) {
189
+ return pack;
190
+ }
191
+ const included = pack.nodes.slice(0, maxNodes);
192
+ const dropped = pack.nodes.slice(maxNodes).map((node) => node.qid);
193
+ return {
194
+ meta: {
195
+ ...pack.meta,
196
+ node_count: included.length,
197
+ truncated: {
198
+ ...pack.meta.truncated,
199
+ max_nodes: true,
200
+ dropped: [...pack.meta.truncated.dropped, ...dropped],
201
+ },
202
+ },
203
+ nodes: included,
204
+ };
205
+ }
206
+ function writeJsonFile(outPath, payload) {
207
+ fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
208
+ fs_1.default.writeFileSync(outPath, JSON.stringify(payload, null, 2), "utf8");
209
+ }
210
+ function resolveStatsPath(root, outPath, statsOut) {
211
+ if (statsOut) {
212
+ return path_1.default.resolve(root, statsOut);
213
+ }
214
+ return `${outPath}.stats.json`;
215
+ }
216
+ function shouldWriteTruncationReport(pack, options) {
217
+ if (options.truncationReport) {
218
+ return true;
219
+ }
220
+ if (!options.maxChars && !options.maxLines && !options.maxTokens) {
221
+ return false;
222
+ }
223
+ const hasDrops = pack.meta.truncated.dropped.length > 0;
224
+ const hasBodyTruncation = (pack.meta.truncated.body_truncated ?? []).length > 0;
225
+ return hasDrops || hasBodyTruncation;
226
+ }
227
+ function printDryRunSummary(pack, stats, format) {
228
+ console.log("dry-run: no files written");
229
+ console.log(`root: ${pack.meta.root}`);
230
+ console.log(`profile: ${pack.meta.profile ?? "standard"}`);
231
+ console.log(`body_mode: ${pack.meta.body_mode ?? "full"}`);
232
+ console.log(`format: ${format}`);
233
+ console.log(`nodes: ${pack.nodes.length}`);
234
+ if (pack.meta.latest_checkpoint_qid) {
235
+ console.log(`latest_checkpoint_qid: ${pack.meta.latest_checkpoint_qid}`);
236
+ }
237
+ if (pack.meta.latest_checkpoint_qid_hint) {
238
+ console.log(`latest_checkpoint_qid_hint: ${pack.meta.latest_checkpoint_qid_hint}`);
239
+ }
240
+ if (pack.meta.truncated.dropped.length > 0) {
241
+ console.log(`dropped: ${pack.meta.truncated.dropped.join(", ")}`);
242
+ }
243
+ if ((pack.meta.truncated.body_truncated ?? []).length > 0) {
244
+ console.log(`body_truncated: ${(pack.meta.truncated.body_truncated ?? []).join(", ")}`);
245
+ }
246
+ console.log("included_nodes:");
247
+ for (const node of pack.nodes) {
248
+ console.log(`- ${node.qid}`);
249
+ }
250
+ console.log("");
251
+ console.log((0, stats_1.renderPackStats)(stats));
252
+ }
45
253
  function runPackCommand(options) {
46
254
  const config = (0, config_1.loadConfig)(options.root);
47
255
  const ws = normalizeWorkspace(options.ws);
@@ -67,6 +275,24 @@ function runPackCommand(options) {
67
275
  }
68
276
  const extraEdges = options.edges ? normalizeEdges(options.edges) : [];
69
277
  const edges = normalizeEdges([...config.pack.default_edges, ...extraEdges]);
278
+ const skillsPolicy = resolveSkillsPolicy(options.skills);
279
+ const skillsDepth = resolveSkillsDepth(options.skillsDepth);
280
+ let resolvedProfile;
281
+ try {
282
+ resolvedProfile = (0, profile_1.resolvePackProfile)({
283
+ profile: options.packProfile,
284
+ concise: options.concise,
285
+ stripCode: options.stripCode,
286
+ maxCodeLines: options.maxCodeLines,
287
+ });
288
+ }
289
+ catch (err) {
290
+ const message = err instanceof Error ? err.message : String(err);
291
+ throw new errors_1.UsageError(message);
292
+ }
293
+ if (options.verbose && resolvedProfile.profile !== "standard") {
294
+ throw new errors_1.UsageError("--verbose is only supported with --profile standard");
295
+ }
70
296
  const buildResult = (0, pack_1.buildPack)({
71
297
  root: options.root,
72
298
  index,
@@ -77,29 +303,125 @@ function runPackCommand(options) {
77
303
  maxNodes: config.pack.limits.max_nodes,
78
304
  verboseCoreListPath: path_1.default.resolve(options.root, config.pack.verbose_core_list_path),
79
305
  wsHint: ws,
306
+ includeLatestCheckpoint: true,
80
307
  });
81
308
  for (const warning of buildResult.warnings) {
82
309
  console.error(`warning: ${warning}`);
83
310
  }
311
+ let packWithSkills = buildResult.pack;
312
+ if (skillsPolicy.mode !== "none") {
313
+ const skillsLoad = (0, skills_index_cache_1.loadSkillsIndex)({
314
+ root: options.root,
315
+ config,
316
+ useCache: !options.noCache,
317
+ allowReindex: !options.noReindex,
318
+ });
319
+ if (skillsLoad.stale && !skillsLoad.rebuilt && !options.noCache) {
320
+ console.error("warning: skills index is stale; run mdkg index to refresh");
321
+ }
322
+ const indexQids = buildResult.pack.nodes.map((node) => node.qid);
323
+ const autoSlugs = collectReferencedSkills(indexQids, index.nodes);
324
+ const selectedSlugs = skillsPolicy.mode === "list"
325
+ ? skillsPolicy.slugs
326
+ : autoSlugs;
327
+ const selectedEntries = [];
328
+ for (const slug of selectedSlugs) {
329
+ const entry = skillsLoad.index.skills[slug];
330
+ if (!entry) {
331
+ console.error(`warning: requested skill missing: ${slug}`);
332
+ continue;
333
+ }
334
+ selectedEntries.push(entry);
335
+ }
336
+ packWithSkills = appendSkillsToPack(packWithSkills, selectedEntries, skillsDepth, options.root);
337
+ packWithSkills = applyNodeCountLimit(packWithSkills, config.pack.limits.max_nodes);
338
+ }
339
+ const templateHeadingMap = resolvedProfile.bodyMode === "summary"
340
+ ? (0, headings_1.loadTemplateHeadingMap)(options.root, config, Array.from(node_1.ALLOWED_TYPES))
341
+ : {};
342
+ const shaped = (0, profile_1.shapePackBodies)(packWithSkills, {
343
+ resolved: resolvedProfile,
344
+ templateHeadingMap,
345
+ });
346
+ let budgeted;
347
+ try {
348
+ budgeted = (0, budget_1.applyPackBudgets)(shaped, {
349
+ maxChars: options.maxChars,
350
+ maxLines: options.maxLines,
351
+ maxTokens: options.maxTokens,
352
+ }, resolvedProfile.profile);
353
+ }
354
+ catch (err) {
355
+ const message = err instanceof Error ? err.message : String(err);
356
+ throw new errors_1.UsageError(message);
357
+ }
358
+ let finalPack = budgeted.pack;
84
359
  const format = normalizeFormat(options.format);
85
360
  let output = "";
86
361
  if (format === "md") {
87
- output = (0, export_md_1.exportMarkdown)(buildResult.pack, config.pack.limits.max_bytes).content;
362
+ const markdown = (0, export_md_1.exportMarkdown)(finalPack, config.pack.limits.max_bytes);
363
+ output = markdown.content;
364
+ finalPack = {
365
+ meta: markdown.meta,
366
+ nodes: markdown.nodes,
367
+ };
88
368
  }
89
369
  else if (format === "json") {
90
- output = (0, export_json_1.exportJson)(buildResult.pack);
370
+ output = (0, export_json_1.exportJson)(finalPack);
91
371
  }
92
372
  else if (format === "toon") {
93
- output = (0, export_toon_1.exportToon)(buildResult.pack);
373
+ output = (0, export_toon_1.exportToon)(finalPack);
94
374
  }
95
375
  else if (format === "xml") {
96
- output = (0, export_xml_1.exportXml)(buildResult.pack);
376
+ output = (0, export_xml_1.exportXml)(finalPack);
377
+ }
378
+ const finalStats = (0, metrics_1.measurePack)(finalPack);
379
+ if (options.dryRun) {
380
+ if (options.out || options.statsOut || options.truncationReport) {
381
+ console.error("warning: --out/--stats-out/--truncation-report are ignored with --dry-run");
382
+ }
383
+ printDryRunSummary(finalPack, finalStats, format);
384
+ return;
97
385
  }
98
386
  const rootId = resolved.qid.split(":")[1] ?? resolved.qid;
99
387
  const outPath = options.out
100
388
  ? path_1.default.resolve(options.root, options.out)
101
- : (0, output_1.buildDefaultPackPath)(options.root, rootId, format, Boolean(options.verbose), new Date());
389
+ : (0, output_1.buildDefaultPackPath)(options.root, rootId, format, Boolean(options.verbose), new Date(), resolvedProfile.profile);
102
390
  fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
103
391
  fs_1.default.writeFileSync(outPath, output, "utf8");
104
392
  console.log(`pack written: ${outPath}`);
393
+ const statsEnabled = Boolean(options.stats || options.statsOut);
394
+ if (statsEnabled) {
395
+ const statsPayload = {
396
+ root: finalPack.meta.root,
397
+ profile: finalPack.meta.profile ?? resolvedProfile.profile,
398
+ body_mode: finalPack.meta.body_mode ?? resolvedProfile.bodyMode,
399
+ ...finalStats,
400
+ };
401
+ const statsPath = resolveStatsPath(options.root, outPath, options.statsOut);
402
+ writeJsonFile(statsPath, statsPayload);
403
+ console.log(`pack stats written: ${statsPath}`);
404
+ if (options.stats) {
405
+ console.log((0, stats_1.renderPackStats)(finalStats));
406
+ }
407
+ }
408
+ const truncationReport = {
409
+ ...budgeted.report,
410
+ after: {
411
+ node_count: finalPack.nodes.length,
412
+ chars: finalStats.totals.chars,
413
+ lines: finalStats.totals.lines,
414
+ bytes: finalStats.totals.bytes,
415
+ tokens_estimate: finalStats.totals.tokens_estimate,
416
+ },
417
+ dropped_nodes: [...finalPack.meta.truncated.dropped],
418
+ body_truncated_nodes: [...(finalPack.meta.truncated.body_truncated ?? [])],
419
+ };
420
+ if (shouldWriteTruncationReport(finalPack, options)) {
421
+ const reportPath = options.truncationReport
422
+ ? path_1.default.resolve(options.root, options.truncationReport)
423
+ : `${outPath}.truncation.json`;
424
+ writeJsonFile(reportPath, truncationReport);
425
+ console.log(`pack truncation report written: ${reportPath}`);
426
+ }
105
427
  }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toNodeSummaryJson = toNodeSummaryJson;
4
+ exports.toNodeDetailJson = toNodeDetailJson;
5
+ exports.toSkillSummaryJson = toSkillSummaryJson;
6
+ exports.toSkillDetailJson = toSkillDetailJson;
7
+ exports.writeJson = writeJson;
8
+ exports.writeCount = writeCount;
9
+ function toNodeSummaryJson(node) {
10
+ return {
11
+ id: node.id,
12
+ qid: node.qid,
13
+ ws: node.ws,
14
+ type: node.type,
15
+ title: node.title,
16
+ status: node.status,
17
+ priority: node.priority,
18
+ created: node.created,
19
+ updated: node.updated,
20
+ tags: [...node.tags],
21
+ owners: [...node.owners],
22
+ links: [...node.links],
23
+ artifacts: [...node.artifacts],
24
+ refs: [...node.refs],
25
+ aliases: [...node.aliases],
26
+ skills: [...node.skills],
27
+ path: node.path,
28
+ edges: {
29
+ epic: node.edges.epic,
30
+ parent: node.edges.parent,
31
+ prev: node.edges.prev,
32
+ next: node.edges.next,
33
+ relates: [...node.edges.relates],
34
+ blocked_by: [...node.edges.blocked_by],
35
+ blocks: [...node.edges.blocks],
36
+ },
37
+ };
38
+ }
39
+ function toNodeDetailJson(node, body) {
40
+ const item = {
41
+ ...toNodeSummaryJson(node),
42
+ };
43
+ if (body !== undefined) {
44
+ item.body = body;
45
+ }
46
+ return item;
47
+ }
48
+ function toSkillSummaryJson(skill) {
49
+ return {
50
+ slug: skill.slug,
51
+ id: skill.id,
52
+ qid: skill.qid,
53
+ ws: skill.ws,
54
+ type: skill.type,
55
+ name: skill.name,
56
+ description: skill.description,
57
+ tags: [...skill.tags],
58
+ version: skill.version,
59
+ authors: [...skill.authors],
60
+ links: [...skill.links],
61
+ path: skill.path,
62
+ has_scripts: skill.has_scripts,
63
+ has_references: skill.has_references,
64
+ ochatr: { ...skill.ochatr },
65
+ };
66
+ }
67
+ function toSkillDetailJson(skill, body) {
68
+ const item = {
69
+ ...toSkillSummaryJson(skill),
70
+ };
71
+ if (body !== undefined) {
72
+ item.body = body;
73
+ }
74
+ return item;
75
+ }
76
+ function writeJson(payload) {
77
+ console.log(JSON.stringify(payload, null, 2));
78
+ }
79
+ function writeCount(count, note) {
80
+ console.error(`count: ${count}`);
81
+ if (note) {
82
+ console.error(`note: ${note}`);
83
+ }
84
+ }
@@ -7,6 +7,7 @@ const filter_1 = require("../util/filter");
7
7
  const errors_1 = require("../util/errors");
8
8
  const sort_1 = require("../util/sort");
9
9
  const node_card_1 = require("./node_card");
10
+ const query_output_1 = require("./query_output");
10
11
  function normalizeWorkspace(value) {
11
12
  if (!value || value === "all") {
12
13
  return undefined;
@@ -25,6 +26,7 @@ function buildSearchText(node) {
25
26
  ...node.artifacts,
26
27
  ...node.refs,
27
28
  ...node.aliases,
29
+ ...node.skills,
28
30
  ];
29
31
  return tokens.join(" ").toLowerCase();
30
32
  }
@@ -47,6 +49,10 @@ function runSearchCommand(options) {
47
49
  if (ws && !config.workspaces[ws]) {
48
50
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
49
51
  }
52
+ const normalizedType = options.type?.toLowerCase();
53
+ if (normalizedType === "skill") {
54
+ throw new errors_1.UsageError("--type skill is no longer supported here; use `mdkg skill search`");
55
+ }
50
56
  const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
51
57
  root: options.root,
52
58
  config,
@@ -57,13 +63,24 @@ function runSearchCommand(options) {
57
63
  console.error("warning: index is stale; run mdkg index to refresh");
58
64
  }
59
65
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
60
- const filtered = (0, filter_1.filterNodes)(Object.values(index.nodes), {
66
+ const nodeResults = (0, filter_1.filterNodes)(Object.values(index.nodes), {
61
67
  ws,
62
- type: options.type,
68
+ type: normalizedType,
63
69
  status: options.status,
64
- });
65
- const matches = filtered.filter((node) => matchesQuery(node, terms));
66
- const sorted = (0, sort_1.sortNodesByQid)(matches);
70
+ tags: options.tags,
71
+ tagsMode: options.tagsMode,
72
+ }).filter((node) => matchesQuery(node, terms));
73
+ const sorted = (0, sort_1.sortNodesByQid)(nodeResults);
74
+ if (options.json) {
75
+ (0, query_output_1.writeJson)({
76
+ command: "search",
77
+ kind: "node",
78
+ count: sorted.length,
79
+ items: sorted.map(query_output_1.toNodeSummaryJson),
80
+ });
81
+ return;
82
+ }
83
+ (0, query_output_1.writeCount)(sorted.length, sorted.length === 0 ? `no nodes matched query "${query}"` : undefined);
67
84
  for (const node of sorted) {
68
85
  console.log((0, node_card_1.formatNodeCard)(node));
69
86
  }
@@ -12,6 +12,7 @@ const frontmatter_1 = require("../graph/frontmatter");
12
12
  const errors_1 = require("../util/errors");
13
13
  const qid_1 = require("../util/qid");
14
14
  const node_card_1 = require("./node_card");
15
+ const query_output_1 = require("./query_output");
15
16
  function normalizeWorkspace(value) {
16
17
  if (!value || value === "all") {
17
18
  return undefined;
@@ -30,6 +31,10 @@ function runShowCommand(options) {
30
31
  if (ws && !config.workspaces[ws]) {
31
32
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
32
33
  }
34
+ const normalizedId = options.id.toLowerCase();
35
+ if (normalizedId.startsWith("skill:") || normalizedId.startsWith("root:skill:")) {
36
+ throw new errors_1.UsageError(`generic skill show is no longer supported; use \`mdkg skill show ${options.id.replace(/^root:skill:|^skill:/i, "")}\``);
37
+ }
33
38
  const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
34
39
  root: options.root,
35
40
  config,
@@ -44,6 +49,23 @@ function runShowCommand(options) {
44
49
  throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
45
50
  }
46
51
  const node = index.nodes[resolved.qid];
52
+ const filePath = path_1.default.resolve(options.root, node.path);
53
+ let body = "";
54
+ if (!options.metaOnly) {
55
+ if (!fs_1.default.existsSync(filePath)) {
56
+ throw new errors_1.NotFoundError(`file not found for ${node.qid}: ${node.path}`);
57
+ }
58
+ const content = fs_1.default.readFileSync(filePath, "utf8");
59
+ body = (0, frontmatter_1.parseFrontmatter)(content, filePath).body.trimEnd();
60
+ }
61
+ if (options.json) {
62
+ (0, query_output_1.writeJson)({
63
+ command: "show",
64
+ kind: "node",
65
+ item: (0, query_output_1.toNodeDetailJson)(node, options.metaOnly ? undefined : body),
66
+ });
67
+ return;
68
+ }
47
69
  const lines = [];
48
70
  lines.push((0, node_card_1.formatNodeCard)(node));
49
71
  const metaLines = [
@@ -53,6 +75,7 @@ function runShowCommand(options) {
53
75
  maybeLine("artifacts", node.artifacts),
54
76
  maybeLine("refs", node.refs),
55
77
  maybeLine("aliases", node.aliases),
78
+ maybeLine("skills", node.skills),
56
79
  ].filter((line) => Boolean(line));
57
80
  lines.push(...metaLines);
58
81
  if (node.edges.epic) {
@@ -79,17 +102,9 @@ function runShowCommand(options) {
79
102
  if (blocksLine) {
80
103
  lines.push(blocksLine);
81
104
  }
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
- }
105
+ if (!options.metaOnly && body.length > 0) {
106
+ lines.push("");
107
+ lines.push(body);
93
108
  }
94
109
  console.log(lines.join("\n"));
95
110
  }