mdkg 0.0.8 → 0.1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/CONTRIBUTING.md +124 -0
  3. package/README.md +49 -14
  4. package/dist/cli.js +113 -32
  5. package/dist/commands/checkpoint.js +19 -2
  6. package/dist/commands/event.js +12 -0
  7. package/dist/commands/init.js +4 -0
  8. package/dist/commands/init_manifest.js +131 -0
  9. package/dist/commands/new.js +57 -21
  10. package/dist/commands/pack.js +14 -0
  11. package/dist/commands/query_output.js +2 -0
  12. package/dist/commands/search.js +8 -0
  13. package/dist/commands/show.js +7 -0
  14. package/dist/commands/skill.js +80 -12
  15. package/dist/commands/task.js +42 -12
  16. package/dist/commands/upgrade.js +286 -0
  17. package/dist/commands/validate.js +31 -3
  18. package/dist/commands/workspace.js +105 -13
  19. package/dist/core/config.js +217 -22
  20. package/dist/core/migrate.js +39 -5
  21. package/dist/core/version.js +31 -0
  22. package/dist/core/workspace_path.js +41 -0
  23. package/dist/graph/agent_file_types.js +392 -0
  24. package/dist/graph/edges.js +13 -10
  25. package/dist/graph/frontmatter.js +33 -0
  26. package/dist/graph/indexer.js +1 -0
  27. package/dist/graph/node.js +43 -16
  28. package/dist/graph/skills_indexer.js +14 -1
  29. package/dist/graph/template_schema.js +13 -126
  30. package/dist/graph/validate_graph.js +302 -2
  31. package/dist/init/AGENT_START.md +14 -2
  32. package/dist/init/CLI_COMMAND_MATRIX.md +49 -1
  33. package/dist/init/README.md +14 -0
  34. package/dist/init/core/rule-6-templates-and-schemas.md +1 -3
  35. package/dist/init/init-manifest.json +197 -0
  36. package/dist/init/legacy/v0.0.9-init-manifest.json +197 -0
  37. package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +12 -11
  38. package/dist/init/templates/default/dispute.md +31 -0
  39. package/dist/init/templates/default/feedback.md +27 -0
  40. package/dist/init/templates/default/proposal.md +35 -0
  41. package/dist/init/templates/default/receipt.md +31 -0
  42. package/dist/init/templates/default/spec.md +43 -0
  43. package/dist/init/templates/default/work.md +44 -0
  44. package/dist/init/templates/default/work_order.md +32 -0
  45. package/dist/pack/export_json.js +3 -0
  46. package/dist/pack/export_md.js +9 -0
  47. package/dist/pack/export_xml.js +9 -0
  48. package/dist/pack/order.js +7 -0
  49. package/dist/pack/pack.js +1 -0
  50. package/dist/templates/loader.js +2 -2
  51. package/dist/util/argparse.js +2 -0
  52. package/dist/util/id.js +19 -0
  53. package/package.json +10 -2
  54. package/scripts/postinstall.js +89 -0
@@ -9,8 +9,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
11
  const node_1 = require("../graph/node");
12
+ const agent_file_types_1 = require("../graph/agent_file_types");
12
13
  const indexer_1 = require("../graph/indexer");
13
- const template_schema_1 = require("../graph/template_schema");
14
14
  const loader_1 = require("../templates/loader");
15
15
  const date_1 = require("../util/date");
16
16
  const errors_1 = require("../util/errors");
@@ -45,6 +45,13 @@ function normalizeIdRef(value, key) {
45
45
  }
46
46
  return normalized;
47
47
  }
48
+ function normalizeAgentFileId(value) {
49
+ const normalized = value.toLowerCase();
50
+ if (!(0, id_1.isPortableId)(normalized)) {
51
+ throw new errors_1.UsageError("--id must be a lowercase portable id for agent workflow file types");
52
+ }
53
+ return normalized;
54
+ }
48
55
  function normalizeList(raw) {
49
56
  return parseCsvList(raw);
50
57
  }
@@ -111,6 +118,9 @@ function idPrefixForType(type) {
111
118
  if (type === "checkpoint") {
112
119
  return "chk";
113
120
  }
121
+ if (type === "work_order") {
122
+ return "order";
123
+ }
114
124
  return type;
115
125
  }
116
126
  function folderForType(type) {
@@ -123,8 +133,17 @@ function folderForType(type) {
123
133
  if (node_1.WORK_TYPES.has(type)) {
124
134
  return "work";
125
135
  }
136
+ if ((0, agent_file_types_1.isAgentFileType)(type)) {
137
+ return "work";
138
+ }
126
139
  throw new errors_1.UsageError(`unsupported type: ${type}`);
127
140
  }
141
+ function fileNameForType(type, id, slug) {
142
+ if ((0, agent_file_types_1.isAgentFileType)(type)) {
143
+ return path_1.default.join(`${id}-${slug}`, agent_file_types_1.AGENT_FILE_BASENAMES[type]);
144
+ }
145
+ return `${id}-${slug}.md`;
146
+ }
128
147
  function ensureExists(index, value, ws, label) {
129
148
  const resolved = (0, qid_1.resolveQid)(index, value, ws);
130
149
  if (resolved.status !== "ok") {
@@ -153,10 +172,18 @@ function runNewCommand(options) {
153
172
  useCache: !noCache,
154
173
  allowReindex: !noReindex,
155
174
  });
175
+ if (options.id !== undefined && !(0, agent_file_types_1.isAgentFileType)(type)) {
176
+ throw new errors_1.UsageError("--id is only valid for agent workflow file types");
177
+ }
156
178
  const prefix = idPrefixForType(type);
157
- const id = nextIdForPrefix(index.nodes, ws, prefix);
179
+ const id = options.id !== undefined
180
+ ? normalizeAgentFileId(options.id)
181
+ : nextIdForPrefix(index.nodes, ws, prefix);
182
+ if (index.nodes[`${ws}:${id}`]) {
183
+ throw new errors_1.UsageError(`node already exists: ${ws}:${id}`);
184
+ }
158
185
  const slug = slugifyTitle(title);
159
- const fileName = `${id}-${slug}.md`;
186
+ const fileName = fileNameForType(type, id, slug);
160
187
  const wsEntry = config.workspaces[ws];
161
188
  const folder = folderForType(type);
162
189
  const targetDir = path_1.default.resolve(options.root, wsEntry.path, wsEntry.mdkg_dir, folder);
@@ -173,7 +200,7 @@ function runNewCommand(options) {
173
200
  throw new errors_1.UsageError(`--status must be one of ${Array.from(allowed).join(", ")}`);
174
201
  }
175
202
  }
176
- else if (node_1.DEC_TYPES.has(type)) {
203
+ else if (type === "dec") {
177
204
  status = statusInput ?? "proposed";
178
205
  if (!DEC_STATUS.has(status)) {
179
206
  throw new errors_1.UsageError(`--status must be one of ${Array.from(DEC_STATUS).join(", ")}`);
@@ -197,20 +224,12 @@ function runNewCommand(options) {
197
224
  else if (options.priority !== undefined) {
198
225
  throw new errors_1.UsageError("--priority is only valid for work items");
199
226
  }
200
- const graphFields = [
201
- ["epic", options.epic],
202
- ["parent", options.parent],
203
- ["prev", options.prev],
204
- ["next", options.next],
205
- ["blocked_by", options.blockedBy],
206
- ["blocks", options.blocks],
207
- ];
208
- for (const [key, raw] of graphFields) {
209
- if (raw && !(0, template_schema_1.schemaAllowsKey)(type, key)) {
210
- throw new errors_1.UsageError(`--${key.replace("_", "-")} is not valid for ${type} nodes`);
227
+ if (!node_1.WORK_TYPES.has(type)) {
228
+ if (options.epic || options.parent || options.prev || options.next || options.blockedBy || options.blocks) {
229
+ throw new errors_1.UsageError("epic/parent/prev/next/blocked-by/blocks are only valid for work items");
211
230
  }
212
231
  }
213
- if (options.cases && !(0, template_schema_1.schemaAllowsKey)(type, "cases")) {
232
+ if (options.cases && type !== "test") {
214
233
  throw new errors_1.UsageError("--cases is only valid for test nodes");
215
234
  }
216
235
  const epic = options.epic ? normalizeIdRef(options.epic, "--epic") : undefined;
@@ -228,7 +247,7 @@ function runNewCommand(options) {
228
247
  const skills = normalizeSkillList(options.skills);
229
248
  const links = normalizeList(options.links);
230
249
  const artifacts = normalizeList(options.artifacts);
231
- if (skills.length > 0 && !(0, template_schema_1.schemaAllowsKey)(type, "skills")) {
250
+ if (skills.length > 0 && !node_1.WORK_TYPES.has(type)) {
232
251
  throw new errors_1.UsageError("--skills is only valid for work items");
233
252
  }
234
253
  if (type === "dec" && options.supersedes) {
@@ -267,7 +286,6 @@ function runNewCommand(options) {
267
286
  }
268
287
  const today = (0, date_1.formatDate)(options.now ?? new Date());
269
288
  const template = (0, loader_1.loadTemplate)(options.root, config, type, options.template);
270
- const schema = (0, template_schema_1.getNodeSchema)(type);
271
289
  const content = (0, loader_1.renderTemplate)(template, {
272
290
  id,
273
291
  type,
@@ -292,8 +310,8 @@ function runNewCommand(options) {
292
310
  supersedes: options.supersedes ? options.supersedes.toLowerCase() : undefined,
293
311
  created: today,
294
312
  updated: today,
295
- }, schema?.allowedKeys);
296
- fs_1.default.mkdirSync(targetDir, { recursive: true });
313
+ });
314
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
297
315
  fs_1.default.writeFileSync(filePath, content, "utf8");
298
316
  if (config.index.auto_reindex && !noReindex) {
299
317
  const updatedIndex = (0, indexer_1.buildIndex)(options.root, config, { tolerant: config.index.tolerant });
@@ -310,5 +328,23 @@ function runNewCommand(options) {
310
328
  runId: options.runId,
311
329
  now: options.now,
312
330
  });
313
- console.log(`node created: ${ws}:${id} (${path_1.default.relative(options.root, filePath)})`);
331
+ const relativePath = path_1.default.relative(options.root, filePath);
332
+ const receipt = {
333
+ workspace: ws,
334
+ id,
335
+ qid: `${ws}:${id}`,
336
+ path: relativePath,
337
+ type,
338
+ title,
339
+ ...(status ? { status } : {}),
340
+ ...(priority !== undefined ? { priority } : {}),
341
+ };
342
+ if (options.json) {
343
+ console.log(JSON.stringify({
344
+ action: "created",
345
+ node: receipt,
346
+ }, null, 2));
347
+ return;
348
+ }
349
+ console.log(`node created: ${receipt.qid} (${receipt.path})`);
314
350
  }
@@ -106,6 +106,19 @@ function renderSkillMetaBody(skill) {
106
106
  }
107
107
  lines.push(`has_scripts: ${skill.has_scripts ? "true" : "false"}`);
108
108
  lines.push(`has_references: ${skill.has_references ? "true" : "false"}`);
109
+ for (const [namespace, values] of Object.entries(skill.extensions).sort(([a], [b]) => a.localeCompare(b))) {
110
+ for (const [key, value] of Object.entries(values).sort(([a], [b]) => a.localeCompare(b))) {
111
+ if (Array.isArray(value)) {
112
+ lines.push(`extensions.${namespace}.${key}: ${value.join(", ")}`);
113
+ }
114
+ else if (typeof value === "boolean") {
115
+ lines.push(`extensions.${namespace}.${key}: ${value ? "true" : "false"}`);
116
+ }
117
+ else {
118
+ lines.push(`extensions.${namespace}.${key}: ${value}`);
119
+ }
120
+ }
121
+ }
109
122
  for (const [key, value] of Object.entries(skill.ochatr).sort(([a], [b]) => a.localeCompare(b))) {
110
123
  if (Array.isArray(value)) {
111
124
  lines.push(`${key}: ${value.join(", ")}`);
@@ -170,6 +183,7 @@ function appendSkillsToPack(pack, skillEntries, depth, root) {
170
183
  artifacts: [],
171
184
  refs: [],
172
185
  aliases: [skill.slug, ...skill.tags],
186
+ attributes: {},
173
187
  body: depth === "full" ? loadSkillFullBody(root, skill) : renderSkillMetaBody(skill),
174
188
  }));
175
189
  if (newNodes.length === 0) {
@@ -25,6 +25,7 @@ function toNodeSummaryJson(node) {
25
25
  refs: [...node.refs],
26
26
  aliases: [...node.aliases],
27
27
  skills: [...node.skills],
28
+ attributes: { ...(node.attributes ?? {}) },
28
29
  path: node.path,
29
30
  edges: {
30
31
  epic: node.edges.epic,
@@ -62,6 +63,7 @@ function toSkillSummaryJson(skill) {
62
63
  path: skill.path,
63
64
  has_scripts: skill.has_scripts,
64
65
  has_references: skill.has_references,
66
+ extensions: JSON.parse(JSON.stringify(skill.extensions)),
65
67
  ochatr: { ...skill.ochatr },
66
68
  };
67
69
  }
@@ -15,9 +15,16 @@ function normalizeWorkspace(value) {
15
15
  return value;
16
16
  }
17
17
  function buildSearchText(node) {
18
+ const attributeTokens = Object.entries(node.attributes ?? {}).flatMap(([key, value]) => {
19
+ if (Array.isArray(value)) {
20
+ return [key, ...value];
21
+ }
22
+ return [key, String(value)];
23
+ });
18
24
  const tokens = [
19
25
  node.id,
20
26
  node.qid,
27
+ node.type,
21
28
  node.title,
22
29
  node.path,
23
30
  ...node.tags,
@@ -27,6 +34,7 @@ function buildSearchText(node) {
27
34
  ...node.refs,
28
35
  ...node.aliases,
29
36
  ...node.skills,
37
+ ...attributeTokens,
30
38
  ];
31
39
  return tokens.join(" ").toLowerCase();
32
40
  }
@@ -25,6 +25,12 @@ function maybeLine(label, values) {
25
25
  }
26
26
  return `${label}: ${values.join(", ")}`;
27
27
  }
28
+ function formatAttributeLine(key, value) {
29
+ if (Array.isArray(value)) {
30
+ return `${key}: ${value.join(", ")}`;
31
+ }
32
+ return `${key}: ${String(value)}`;
33
+ }
28
34
  function runShowCommand(options) {
29
35
  const config = (0, config_1.loadConfig)(options.root);
30
36
  const ws = normalizeWorkspace(options.ws);
@@ -79,6 +85,7 @@ function runShowCommand(options) {
79
85
  maybeLine("skills", node.skills),
80
86
  ].filter((line) => Boolean(line));
81
87
  lines.push(...metaLines);
88
+ lines.push(...Object.entries(node.attributes ?? {}).map(([key, value]) => formatAttributeLine(key, value)));
82
89
  if (node.edges.epic) {
83
90
  lines.push(`epic: ${node.edges.epic}`);
84
91
  }
@@ -99,6 +99,16 @@ function filterSkills(skills, tags, tagsMode = "any") {
99
99
  });
100
100
  }
101
101
  function buildSkillSearchText(skill) {
102
+ const extensionTokens = Object.entries(skill.extensions).flatMap(([namespace, values]) => Object.entries(values).flatMap(([key, value]) => {
103
+ const field = `extensions.${namespace}.${key}`;
104
+ if (Array.isArray(value)) {
105
+ return [field, ...value];
106
+ }
107
+ if (typeof value === "boolean") {
108
+ return [field, value ? "true" : "false"];
109
+ }
110
+ return [field, value];
111
+ }));
102
112
  const ochatrTokens = Object.entries(skill.ochatr).flatMap(([key, value]) => {
103
113
  if (Array.isArray(value)) {
104
114
  return [key, ...value];
@@ -118,6 +128,7 @@ function buildSkillSearchText(skill) {
118
128
  ...skill.tags,
119
129
  ...skill.authors,
120
130
  ...skill.links,
131
+ ...extensionTokens,
121
132
  ...ochatrTokens,
122
133
  ];
123
134
  return tokens.join(" ").toLowerCase();
@@ -191,7 +202,23 @@ function runSkillNewCommand(options) {
191
202
  skill: slug,
192
203
  now: options.now,
193
204
  });
194
- console.log(`skill created: root:skill:${slug} (${path_1.default.relative(root, canonicalPath)})`);
205
+ const receipt = {
206
+ workspace: "root",
207
+ id: `skill:${slug}`,
208
+ qid: `root:skill:${slug}`,
209
+ slug,
210
+ name,
211
+ path: path_1.default.relative(root, canonicalPath),
212
+ with_scripts: Boolean(options.withScripts),
213
+ };
214
+ if (options.json) {
215
+ console.log(JSON.stringify({
216
+ action: "created",
217
+ skill: receipt,
218
+ }, null, 2));
219
+ return;
220
+ }
221
+ console.log(`skill created: ${receipt.qid} (${receipt.path})`);
195
222
  }
196
223
  function runSkillListCommand(options) {
197
224
  const config = (0, config_1.loadConfig)(options.root);
@@ -274,6 +301,19 @@ function runSkillShowCommand(options) {
274
301
  }
275
302
  lines.push(`has_scripts: ${skill.has_scripts ? "true" : "false"}`);
276
303
  lines.push(`has_references: ${skill.has_references ? "true" : "false"}`);
304
+ for (const [namespace, values] of Object.entries(skill.extensions).sort(([a], [b]) => a.localeCompare(b))) {
305
+ for (const [key, value] of Object.entries(values).sort(([a], [b]) => a.localeCompare(b))) {
306
+ if (Array.isArray(value)) {
307
+ lines.push(`extensions.${namespace}.${key}: ${value.join(", ")}`);
308
+ continue;
309
+ }
310
+ if (typeof value === "boolean") {
311
+ lines.push(`extensions.${namespace}.${key}: ${value ? "true" : "false"}`);
312
+ continue;
313
+ }
314
+ lines.push(`extensions.${namespace}.${key}: ${value}`);
315
+ }
316
+ }
277
317
  for (const [key, value] of Object.entries(skill.ochatr).sort(([a], [b]) => a.localeCompare(b))) {
278
318
  if (Array.isArray(value)) {
279
319
  lines.push(`${key}: ${value.join(", ")}`);
@@ -329,8 +369,11 @@ function runSkillValidateCommand(options) {
329
369
  const targetSlug = options.slug?.trim().toLowerCase();
330
370
  const warnings = [];
331
371
  const errors = [];
372
+ let checkedCount = 0;
332
373
  if (targetSlug) {
333
- const result = validateSingleSkill(options.root, normalizeSlug(targetSlug));
374
+ const normalizedSlug = normalizeSlug(targetSlug);
375
+ const result = validateSingleSkill(options.root, normalizedSlug);
376
+ checkedCount = 1;
334
377
  warnings.push(...result.warnings);
335
378
  errors.push(...result.errors);
336
379
  }
@@ -338,31 +381,49 @@ function runSkillValidateCommand(options) {
338
381
  const skillsRoot = (0, skills_indexer_1.resolveSkillsRoot)(options.root, config);
339
382
  if (fs_1.default.existsSync(skillsRoot)) {
340
383
  const entries = fs_1.default.readdirSync(skillsRoot, { withFileTypes: true });
341
- for (const entry of entries.filter((value) => value.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
384
+ const skillDirs = entries
385
+ .filter((value) => value.isDirectory())
386
+ .sort((a, b) => a.name.localeCompare(b.name));
387
+ checkedCount = skillDirs.length;
388
+ for (const entry of skillDirs) {
342
389
  const result = validateSingleSkill(options.root, entry.name.toLowerCase());
343
390
  warnings.push(...result.warnings);
344
391
  errors.push(...result.errors);
345
392
  }
346
393
  }
347
394
  }
348
- for (const warning of Array.from(new Set(warnings))) {
395
+ const uniqueWarnings = Array.from(new Set(warnings));
396
+ const uniqueErrors = Array.from(new Set(errors));
397
+ const receipt = {
398
+ action: "validated",
399
+ ok: uniqueErrors.length === 0,
400
+ checked_count: checkedCount,
401
+ warning_count: uniqueWarnings.length,
402
+ error_count: uniqueErrors.length,
403
+ warnings: uniqueWarnings,
404
+ errors: uniqueErrors,
405
+ ...(targetSlug ? { target: normalizeSlug(targetSlug) } : {}),
406
+ };
407
+ if (options.json) {
408
+ console.log(JSON.stringify(receipt, null, 2));
409
+ if (uniqueErrors.length > 0) {
410
+ throw new errors_1.ValidationError(`skill validation failed with ${uniqueErrors.length} error(s)`);
411
+ }
412
+ return;
413
+ }
414
+ for (const warning of uniqueWarnings) {
349
415
  console.error(`warning: ${warning}`);
350
416
  }
351
- if (errors.length > 0) {
352
- for (const error of Array.from(new Set(errors))) {
417
+ if (uniqueErrors.length > 0) {
418
+ for (const error of uniqueErrors) {
353
419
  console.error(error);
354
420
  }
355
- throw new errors_1.ValidationError(`skill validation failed with ${Array.from(new Set(errors)).length} error(s)`);
421
+ throw new errors_1.ValidationError(`skill validation failed with ${uniqueErrors.length} error(s)`);
356
422
  }
357
423
  if (targetSlug) {
358
424
  console.log(`skill validation ok: ${targetSlug} (1 skill checked)`);
359
425
  return;
360
426
  }
361
- const checkedCount = fs_1.default.existsSync((0, skills_indexer_1.resolveSkillsRoot)(options.root, config))
362
- ? fs_1.default
363
- .readdirSync((0, skills_indexer_1.resolveSkillsRoot)(options.root, config), { withFileTypes: true })
364
- .filter((value) => value.isDirectory()).length
365
- : 0;
366
427
  console.log(`skill validation ok: ${checkedCount} skill${checkedCount === 1 ? "" : "s"} checked`);
367
428
  }
368
429
  function runSkillSyncCommand(options) {
@@ -373,5 +434,12 @@ function runSkillSyncCommand(options) {
373
434
  createRoots: true,
374
435
  force: options.force,
375
436
  });
437
+ if (options.json) {
438
+ console.log(JSON.stringify({
439
+ action: "synced",
440
+ sync: result,
441
+ }, null, 2));
442
+ return;
443
+ }
376
444
  console.log(`skill mirror sync ok: ${result.synced} synced, ${result.pruned} pruned across ${result.targets} target${result.targets === 1 ? "" : "s"}`);
377
445
  }
@@ -9,12 +9,10 @@ exports.runTaskDoneCommand = runTaskDoneCommand;
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const config_1 = require("../core/config");
12
- const node_1 = require("../graph/node");
13
12
  const indexer_1 = require("../graph/indexer");
14
13
  const index_cache_1 = require("../graph/index_cache");
15
14
  const frontmatter_1 = require("../graph/frontmatter");
16
15
  const skills_indexer_1 = require("../graph/skills_indexer");
17
- const template_schema_1 = require("../graph/template_schema");
18
16
  const date_1 = require("../util/date");
19
17
  const errors_1 = require("../util/errors");
20
18
  const qid_1 = require("../util/qid");
@@ -114,12 +112,7 @@ function loadMutableTaskNode(root, idOrQid, wsHint) {
114
112
  }
115
113
  const filePath = path_1.default.resolve(root, node.path);
116
114
  const content = fs_1.default.readFileSync(filePath, "utf8");
117
- const parsed = (0, node_1.parseNode)(content, filePath, {
118
- workStatusEnum: config.work.status_enum,
119
- priorityMin: config.work.priority_min,
120
- priorityMax: config.work.priority_max,
121
- templateSchemas: (0, template_schema_1.loadTemplateSchemas)(root, config, node_1.ALLOWED_TYPES),
122
- });
115
+ const parsed = (0, frontmatter_1.parseFrontmatter)(content, filePath);
123
116
  return {
124
117
  config,
125
118
  index,
@@ -182,6 +175,30 @@ function maybeWarnEventsDisabled(root, config, ws) {
182
175
  }
183
176
  console.error(`note: events.jsonl is missing for workspace ${ws}; run mdkg event enable --ws ${ws} to restore JSONL provenance`);
184
177
  }
178
+ function taskReceipt(root, loaded) {
179
+ const rawPriority = loaded.frontmatter.priority;
180
+ const priority = rawPriority === undefined ? undefined : Number.parseInt(String(rawPriority), 10);
181
+ return {
182
+ workspace: loaded.ws,
183
+ id: loaded.id,
184
+ qid: loaded.qid,
185
+ path: path_1.default.relative(root, loaded.filePath),
186
+ type: loaded.type,
187
+ status: String(loaded.frontmatter.status ?? ""),
188
+ ...(Number.isInteger(priority) ? { priority } : {}),
189
+ };
190
+ }
191
+ function printTaskMutationReceipt(action, root, loaded, json, checkpoint) {
192
+ if (json) {
193
+ console.log(JSON.stringify({
194
+ action,
195
+ task: taskReceipt(root, loaded),
196
+ ...(checkpoint ? { checkpoint } : {}),
197
+ }, null, 2));
198
+ return;
199
+ }
200
+ console.log(`task ${action}: ${loaded.qid}`);
201
+ }
185
202
  function runTaskStartCommand(options) {
186
203
  const loaded = loadMutableTaskNode(options.root, options.id, options.ws);
187
204
  const now = options.now ?? new Date();
@@ -200,7 +217,7 @@ function runTaskStartCommand(options) {
200
217
  now,
201
218
  });
202
219
  maybeWarnEventsDisabled(options.root, loaded.config, loaded.ws);
203
- console.log(`task started: ${loaded.qid}`);
220
+ printTaskMutationReceipt("started", options.root, loaded, options.json);
204
221
  }
205
222
  function runTaskUpdateCommand(options) {
206
223
  const loaded = loadMutableTaskNode(options.root, options.id, options.ws);
@@ -242,7 +259,7 @@ function runTaskUpdateCommand(options) {
242
259
  runId: options.runId,
243
260
  now,
244
261
  });
245
- console.log(`task updated: ${loaded.qid}`);
262
+ printTaskMutationReceipt("updated", options.root, loaded, options.json);
246
263
  }
247
264
  function runTaskDoneCommand(options) {
248
265
  const loaded = loadMutableTaskNode(options.root, options.id, options.ws);
@@ -267,7 +284,20 @@ function runTaskDoneCommand(options) {
267
284
  runId: options.runId,
268
285
  now,
269
286
  });
270
- if (options.checkpoint) {
287
+ let checkpoint;
288
+ if (options.checkpoint && options.json) {
289
+ checkpoint = (0, checkpoint_1.createCheckpoint)({
290
+ root: options.root,
291
+ title: options.checkpoint,
292
+ ws: loaded.ws,
293
+ relates: loaded.id,
294
+ scope: loaded.id,
295
+ runId: options.runId,
296
+ note: `checkpoint created from mdkg task done for ${loaded.id}`,
297
+ now,
298
+ });
299
+ }
300
+ else if (options.checkpoint) {
271
301
  (0, checkpoint_1.runCheckpointNewCommand)({
272
302
  root: options.root,
273
303
  title: options.checkpoint,
@@ -281,5 +311,5 @@ function runTaskDoneCommand(options) {
281
311
  }
282
312
  maybeReindex(options.root, loaded.config);
283
313
  maybeWarnEventsDisabled(options.root, loaded.config, loaded.ws);
284
- console.log(`task done: ${loaded.qid}`);
314
+ printTaskMutationReceipt("done", options.root, loaded, options.json, checkpoint);
285
315
  }