mdkg 0.0.3 → 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 (42) hide show
  1. package/README.md +171 -169
  2. package/dist/cli.js +980 -624
  3. package/dist/commands/checkpoint.js +17 -6
  4. package/dist/commands/event.js +46 -0
  5. package/dist/commands/event_support.js +146 -0
  6. package/dist/commands/format.js +6 -7
  7. package/dist/commands/index.js +10 -4
  8. package/dist/commands/init.js +198 -16
  9. package/dist/commands/list.js +18 -1
  10. package/dist/commands/new.js +30 -5
  11. package/dist/commands/pack.js +194 -2
  12. package/dist/commands/query_output.js +84 -0
  13. package/dist/commands/search.js +22 -5
  14. package/dist/commands/show.js +26 -11
  15. package/dist/commands/skill.js +359 -0
  16. package/dist/commands/skill_support.js +121 -0
  17. package/dist/commands/task.js +270 -0
  18. package/dist/commands/validate.js +104 -7
  19. package/dist/graph/edges.js +2 -2
  20. package/dist/graph/frontmatter.js +1 -0
  21. package/dist/graph/indexer.js +21 -0
  22. package/dist/graph/node.js +20 -4
  23. package/dist/graph/skills_index_cache.js +94 -0
  24. package/dist/graph/skills_indexer.js +160 -0
  25. package/dist/init/core/rule-1-mdkg-conventions.md +9 -2
  26. package/dist/init/core/rule-3-cli-contract.md +73 -14
  27. package/dist/init/core/rule-4-repo-safety-and-ignores.md +9 -3
  28. package/dist/init/core/rule-6-templates-and-schemas.md +6 -2
  29. package/dist/init/skills/SKILL.md.example +41 -0
  30. package/dist/init/templates/default/bug.md +1 -0
  31. package/dist/init/templates/default/chk.md +1 -0
  32. package/dist/init/templates/default/epic.md +1 -0
  33. package/dist/init/templates/default/feat.md +1 -0
  34. package/dist/init/templates/default/task.md +1 -0
  35. package/dist/init/templates/default/test.md +1 -0
  36. package/dist/pack/export_md.js +6 -0
  37. package/dist/pack/export_xml.js +6 -0
  38. package/dist/pack/pack.js +35 -0
  39. package/dist/util/argparse.js +25 -0
  40. package/dist/util/filter.js +18 -0
  41. package/dist/util/id.js +23 -0
  42. package/package.json +5 -2
@@ -14,10 +14,11 @@ const loader_1 = require("../templates/loader");
14
14
  const date_1 = require("../util/date");
15
15
  const errors_1 = require("../util/errors");
16
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]+$/;
17
+ const id_1 = require("../util/id");
18
+ const event_support_1 = require("./event_support");
19
19
  const DEC_ID_RE = /^dec-[0-9]+$/;
20
20
  const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
21
+ const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21
22
  const CORE_TYPES = new Set(["rule"]);
22
23
  const DESIGN_TYPES = new Set(["prd", "edd", "dec", "prop"]);
23
24
  function parseCsvList(raw) {
@@ -31,14 +32,14 @@ function parseCsvList(raw) {
31
32
  }
32
33
  function normalizeId(value, key) {
33
34
  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}`);
35
+ if (!(0, id_1.isCanonicalId)(normalized)) {
36
+ throw new errors_1.UsageError(`${key} entries must match <prefix>-<number> or reserved id: ${value}`);
36
37
  }
37
38
  return normalized;
38
39
  }
39
40
  function normalizeIdRef(value, key) {
40
41
  const normalized = value.toLowerCase();
41
- if (!ID_REF_RE.test(normalized)) {
42
+ if (!(0, id_1.isCanonicalIdRef)(normalized)) {
42
43
  throw new errors_1.UsageError(`${key} entries must match <id> or <ws>:<id>: ${value}`);
43
44
  }
44
45
  return normalized;
@@ -55,6 +56,15 @@ function normalizeIdList(raw, key) {
55
56
  function normalizeIdRefList(raw, key) {
56
57
  return parseCsvList(raw).map((value) => normalizeIdRef(value, key));
57
58
  }
59
+ function normalizeSkillList(raw) {
60
+ return parseCsvList(raw).map((value) => {
61
+ const normalized = value.toLowerCase();
62
+ if (!SKILL_SLUG_RE.test(normalized)) {
63
+ throw new errors_1.UsageError(`--skills entries must be kebab-case: ${value}`);
64
+ }
65
+ return normalized;
66
+ });
67
+ }
58
68
  function normalizeWorkspace(value) {
59
69
  if (!value) {
60
70
  return "root";
@@ -206,8 +216,12 @@ function runNewCommand(options) {
206
216
  const tags = normalizeLowercaseList(options.tags);
207
217
  const owners = normalizeLowercaseList(options.owners);
208
218
  const cases = normalizeLowercaseList(options.cases);
219
+ const skills = normalizeSkillList(options.skills);
209
220
  const links = normalizeList(options.links);
210
221
  const artifacts = normalizeList(options.artifacts);
222
+ if (skills.length > 0 && !node_1.WORK_TYPES.has(type)) {
223
+ throw new errors_1.UsageError("--skills is only valid for work items");
224
+ }
211
225
  if (type === "dec" && options.supersedes) {
212
226
  const supersedes = options.supersedes.toLowerCase();
213
227
  if (!DEC_ID_RE.test(supersedes)) {
@@ -263,6 +277,7 @@ function runNewCommand(options) {
263
277
  artifacts: artifacts.length > 0 ? artifacts : undefined,
264
278
  refs: refs.length > 0 ? refs : undefined,
265
279
  aliases: aliases.length > 0 ? aliases : undefined,
280
+ skills: skills.length > 0 ? skills : undefined,
266
281
  cases: cases.length > 0 ? cases : undefined,
267
282
  supersedes: options.supersedes ? options.supersedes.toLowerCase() : undefined,
268
283
  created: today,
@@ -275,5 +290,15 @@ function runNewCommand(options) {
275
290
  const outputPath = path_1.default.resolve(options.root, config.index.global_index_path);
276
291
  (0, index_cache_1.writeIndex)(outputPath, updatedIndex);
277
292
  }
293
+ (0, event_support_1.appendAutomaticEvent)({
294
+ root: options.root,
295
+ ws,
296
+ kind: "NODE_CREATED",
297
+ status: "ok",
298
+ refs: [id],
299
+ notes: `node created via mdkg new`,
300
+ runId: options.runId,
301
+ now: options.now,
302
+ });
278
303
  console.log(`node created: ${ws}:${id} (${path_1.default.relative(options.root, filePath)})`);
279
304
  }
@@ -8,6 +8,8 @@ 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 frontmatter_1 = require("../graph/frontmatter");
12
+ const skills_index_cache_1 = require("../graph/skills_index_cache");
11
13
  const node_1 = require("../graph/node");
12
14
  const budget_1 = require("../pack/budget");
13
15
  const export_json_1 = require("../pack/export_json");
@@ -24,6 +26,7 @@ const output_1 = require("../util/output");
24
26
  const qid_1 = require("../util/qid");
25
27
  const EDGE_KEYS = new Set(["parent", "epic", "relates", "blocked_by", "blocks", "prev", "next"]);
26
28
  const FORMAT_KEYS = new Set(["md", "json", "toon", "xml"]);
29
+ const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
27
30
  function normalizeWorkspace(value) {
28
31
  if (!value || value === "all") {
29
32
  return undefined;
@@ -48,6 +51,158 @@ function normalizeFormat(format) {
48
51
  }
49
52
  return normalized;
50
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
+ }
51
206
  function writeJsonFile(outPath, payload) {
52
207
  fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
53
208
  fs_1.default.writeFileSync(outPath, JSON.stringify(payload, null, 2), "utf8");
@@ -76,6 +231,12 @@ function printDryRunSummary(pack, stats, format) {
76
231
  console.log(`body_mode: ${pack.meta.body_mode ?? "full"}`);
77
232
  console.log(`format: ${format}`);
78
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
+ }
79
240
  if (pack.meta.truncated.dropped.length > 0) {
80
241
  console.log(`dropped: ${pack.meta.truncated.dropped.join(", ")}`);
81
242
  }
@@ -114,6 +275,8 @@ function runPackCommand(options) {
114
275
  }
115
276
  const extraEdges = options.edges ? normalizeEdges(options.edges) : [];
116
277
  const edges = normalizeEdges([...config.pack.default_edges, ...extraEdges]);
278
+ const skillsPolicy = resolveSkillsPolicy(options.skills);
279
+ const skillsDepth = resolveSkillsDepth(options.skillsDepth);
117
280
  let resolvedProfile;
118
281
  try {
119
282
  resolvedProfile = (0, profile_1.resolvePackProfile)({
@@ -128,7 +291,7 @@ function runPackCommand(options) {
128
291
  throw new errors_1.UsageError(message);
129
292
  }
130
293
  if (options.verbose && resolvedProfile.profile !== "standard") {
131
- throw new errors_1.UsageError("--verbose is only supported with --pack-profile standard");
294
+ throw new errors_1.UsageError("--verbose is only supported with --profile standard");
132
295
  }
133
296
  const buildResult = (0, pack_1.buildPack)({
134
297
  root: options.root,
@@ -140,14 +303,43 @@ function runPackCommand(options) {
140
303
  maxNodes: config.pack.limits.max_nodes,
141
304
  verboseCoreListPath: path_1.default.resolve(options.root, config.pack.verbose_core_list_path),
142
305
  wsHint: ws,
306
+ includeLatestCheckpoint: true,
143
307
  });
144
308
  for (const warning of buildResult.warnings) {
145
309
  console.error(`warning: ${warning}`);
146
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
+ }
147
339
  const templateHeadingMap = resolvedProfile.bodyMode === "summary"
148
340
  ? (0, headings_1.loadTemplateHeadingMap)(options.root, config, Array.from(node_1.ALLOWED_TYPES))
149
341
  : {};
150
- const shaped = (0, profile_1.shapePackBodies)(buildResult.pack, {
342
+ const shaped = (0, profile_1.shapePackBodies)(packWithSkills, {
151
343
  resolved: resolvedProfile,
152
344
  templateHeadingMap,
153
345
  });
@@ -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
  }