mdkg 0.1.1 → 0.1.3

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 (73) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +124 -18
  3. package/dist/cli.js +567 -15
  4. package/dist/commands/archive.js +486 -0
  5. package/dist/commands/bundle.js +743 -0
  6. package/dist/commands/bundle_import.js +255 -0
  7. package/dist/commands/capability.js +162 -0
  8. package/dist/commands/checkpoint.js +31 -5
  9. package/dist/commands/doctor.js +269 -9
  10. package/dist/commands/format.js +38 -9
  11. package/dist/commands/index.js +12 -12
  12. package/dist/commands/init.js +194 -63
  13. package/dist/commands/init_manifest.js +19 -6
  14. package/dist/commands/list.js +5 -2
  15. package/dist/commands/new.js +36 -7
  16. package/dist/commands/next.js +7 -0
  17. package/dist/commands/node_card.js +4 -1
  18. package/dist/commands/pack.js +62 -2
  19. package/dist/commands/query_output.js +1 -0
  20. package/dist/commands/search.js +5 -2
  21. package/dist/commands/show.js +7 -14
  22. package/dist/commands/skill_mirror.js +22 -0
  23. package/dist/commands/task.js +23 -6
  24. package/dist/commands/upgrade.js +24 -1
  25. package/dist/commands/validate.js +20 -1
  26. package/dist/commands/work.js +397 -0
  27. package/dist/commands/workspace.js +12 -2
  28. package/dist/core/config.js +115 -1
  29. package/dist/graph/agent_file_types.js +78 -5
  30. package/dist/graph/archive_file.js +125 -0
  31. package/dist/graph/archive_integrity.js +66 -0
  32. package/dist/graph/bundle_imports.js +418 -0
  33. package/dist/graph/capabilities_index_cache.js +103 -0
  34. package/dist/graph/capabilities_indexer.js +231 -0
  35. package/dist/graph/frontmatter.js +19 -0
  36. package/dist/graph/index_cache.js +23 -6
  37. package/dist/graph/indexer.js +4 -1
  38. package/dist/graph/node.js +23 -4
  39. package/dist/graph/node_body.js +37 -0
  40. package/dist/graph/reindex.js +46 -0
  41. package/dist/graph/skills_index_cache.js +2 -2
  42. package/dist/graph/skills_indexer.js +8 -3
  43. package/dist/graph/sqlite_index.js +293 -0
  44. package/dist/graph/validate_graph.js +83 -7
  45. package/dist/graph/visibility.js +214 -0
  46. package/dist/graph/workspace_files.js +22 -0
  47. package/dist/init/AGENT_START.md +24 -0
  48. package/dist/init/CLI_COMMAND_MATRIX.md +61 -3
  49. package/dist/init/README.md +70 -4
  50. package/dist/init/config.json +18 -2
  51. package/dist/init/core/guide.md +6 -2
  52. package/dist/init/core/rule-1-mdkg-conventions.md +2 -1
  53. package/dist/init/core/rule-3-cli-contract.md +72 -4
  54. package/dist/init/core/rule-4-repo-safety-and-ignores.md +47 -11
  55. package/dist/init/core/rule-5-release-and-versioning.md +4 -3
  56. package/dist/init/core/rule-6-templates-and-schemas.md +7 -0
  57. package/dist/init/init-manifest.json +21 -16
  58. package/dist/init/skills/default/build-pack-and-execute-task/SKILL.md +2 -1
  59. package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +26 -0
  60. package/dist/init/templates/default/archive.md +33 -0
  61. package/dist/init/templates/default/receipt.md +15 -1
  62. package/dist/init/templates/default/work.md +6 -1
  63. package/dist/init/templates/default/work_order.md +15 -1
  64. package/dist/pack/export_md.js +3 -0
  65. package/dist/pack/export_xml.js +3 -0
  66. package/dist/pack/order.js +1 -0
  67. package/dist/pack/pack.js +3 -13
  68. package/dist/util/argparse.js +30 -0
  69. package/dist/util/atomic.js +44 -0
  70. package/dist/util/lock.js +72 -0
  71. package/dist/util/refs.js +40 -0
  72. package/dist/util/zip.js +153 -0
  73. package/package.json +14 -5
@@ -0,0 +1,397 @@
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.runWorkContractNewCommand = runWorkContractNewCommand;
7
+ exports.runWorkOrderNewCommand = runWorkOrderNewCommand;
8
+ exports.runWorkOrderUpdateCommand = runWorkOrderUpdateCommand;
9
+ exports.runWorkReceiptNewCommand = runWorkReceiptNewCommand;
10
+ exports.runWorkReceiptUpdateCommand = runWorkReceiptUpdateCommand;
11
+ exports.runWorkArtifactAddCommand = runWorkArtifactAddCommand;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const config_1 = require("../core/config");
15
+ const frontmatter_1 = require("../graph/frontmatter");
16
+ const indexer_1 = require("../graph/indexer");
17
+ const index_cache_1 = require("../graph/index_cache");
18
+ const reindex_1 = require("../graph/reindex");
19
+ const agent_file_types_1 = require("../graph/agent_file_types");
20
+ const loader_1 = require("../templates/loader");
21
+ const date_1 = require("../util/date");
22
+ const errors_1 = require("../util/errors");
23
+ const id_1 = require("../util/id");
24
+ const qid_1 = require("../util/qid");
25
+ const atomic_1 = require("../util/atomic");
26
+ const lock_1 = require("../util/lock");
27
+ const event_support_1 = require("./event_support");
28
+ const archive_1 = require("./archive");
29
+ const PRICING_MODELS = new Set(["free", "included", "quoted", "fixed", "metered", "subscription"]);
30
+ const ORDER_STATUSES = new Set(["submitted", "accepted", "running", "completed", "cancelled", "failed"]);
31
+ const RECEIPT_STATUSES = new Set(["recorded", "verified", "rejected", "superseded"]);
32
+ const OUTCOMES = new Set(["success", "partial", "failure"]);
33
+ function parseCsvList(raw) {
34
+ if (!raw) {
35
+ return [];
36
+ }
37
+ return raw
38
+ .split(",")
39
+ .map((value) => value.trim())
40
+ .filter(Boolean);
41
+ }
42
+ function normalizeWorkspace(value) {
43
+ if (!value) {
44
+ return "root";
45
+ }
46
+ const normalized = value.toLowerCase();
47
+ if (normalized === "all") {
48
+ throw new errors_1.UsageError("--ws all is not valid for work commands");
49
+ }
50
+ return normalized;
51
+ }
52
+ function normalizePortableId(value, flag) {
53
+ const normalized = value.toLowerCase();
54
+ if (!(0, id_1.isPortableId)(normalized)) {
55
+ throw new errors_1.UsageError(`${flag} must be a lowercase portable id`);
56
+ }
57
+ return normalized;
58
+ }
59
+ function normalizeEnum(value, flag, allowed) {
60
+ const normalized = value.toLowerCase();
61
+ if (!allowed.has(normalized)) {
62
+ throw new errors_1.UsageError(`${flag} must be one of ${Array.from(allowed).join(", ")}`);
63
+ }
64
+ return normalized;
65
+ }
66
+ function slugifyTitle(title) {
67
+ const slug = title
68
+ .trim()
69
+ .toLowerCase()
70
+ .replace(/[^a-z0-9]+/g, "-")
71
+ .replace(/^-+|-+$/g, "")
72
+ .replace(/-+/g, "-");
73
+ return slug || "work";
74
+ }
75
+ function toPosixPath(value) {
76
+ return value.split(path_1.default.sep).join("/");
77
+ }
78
+ function appendUnique(existing, additions) {
79
+ const next = [...existing];
80
+ const seen = new Set(existing);
81
+ for (const addition of additions) {
82
+ if (!seen.has(addition)) {
83
+ next.push(addition);
84
+ seen.add(addition);
85
+ }
86
+ }
87
+ return next;
88
+ }
89
+ function toStringList(value) {
90
+ return Array.isArray(value) ? value.map(String) : [];
91
+ }
92
+ function maybeReindex(root, config) {
93
+ if (!config.index.auto_reindex) {
94
+ return;
95
+ }
96
+ (0, reindex_1.writeDerivedIndexes)(root, config, (0, indexer_1.buildIndex)(root, config, { tolerant: config.index.tolerant }));
97
+ }
98
+ function findNodeById(index, ws, id, type) {
99
+ const node = index.nodes[`${ws}:${id}`];
100
+ if (!node) {
101
+ return undefined;
102
+ }
103
+ if (type && node.type !== type) {
104
+ return undefined;
105
+ }
106
+ return node;
107
+ }
108
+ function requireNodeById(index, ws, id, type, label) {
109
+ const node = findNodeById(index, ws, id, type);
110
+ if (!node) {
111
+ throw new errors_1.NotFoundError(`${label} not found: ${id}`);
112
+ }
113
+ return node;
114
+ }
115
+ function nodeReceipt(root, node) {
116
+ return {
117
+ workspace: node.ws,
118
+ id: node.id,
119
+ qid: node.qid,
120
+ path: node.path,
121
+ type: node.type,
122
+ title: node.title,
123
+ };
124
+ }
125
+ function writeFrontmatterFile(filePath, frontmatter, body) {
126
+ const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER);
127
+ const content = ["---", ...lines, "---", body.trimStart()].join("\n");
128
+ (0, atomic_1.atomicWriteFile)(filePath, content.endsWith("\n") ? content : `${content}\n`);
129
+ }
130
+ function createAgentWorkflowNode(options) {
131
+ const config = (0, config_1.loadConfig)(options.root);
132
+ const ws = normalizeWorkspace(options.ws);
133
+ const workspace = config.workspaces[ws];
134
+ if (!workspace) {
135
+ throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
136
+ }
137
+ const id = normalizePortableId(options.id, "--id");
138
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
139
+ if (index.nodes[`${ws}:${id}`]) {
140
+ throw new errors_1.UsageError(`node already exists: ${ws}:${id}`);
141
+ }
142
+ const today = (0, date_1.formatDate)(options.now ?? new Date());
143
+ const slug = slugifyTitle(options.title);
144
+ const filePath = path_1.default.resolve(options.root, workspace.path, workspace.mdkg_dir, "work", `${id}-${slug}`, agent_file_types_1.AGENT_FILE_BASENAMES[options.type]);
145
+ const template = (0, loader_1.loadTemplate)(options.root, config, options.type);
146
+ const content = (0, loader_1.renderTemplate)(template, {
147
+ id,
148
+ type: options.type,
149
+ title: options.title,
150
+ created: today,
151
+ updated: today,
152
+ ...options.overrides,
153
+ });
154
+ try {
155
+ (0, atomic_1.writeFileExclusive)(filePath, content);
156
+ }
157
+ catch (err) {
158
+ const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : "";
159
+ if (code === "EEXIST") {
160
+ throw new errors_1.UsageError(`node already exists: ${path_1.default.relative(options.root, filePath)}`);
161
+ }
162
+ throw err;
163
+ }
164
+ maybeReindex(options.root, config);
165
+ (0, event_support_1.appendAutomaticEvent)({
166
+ root: options.root,
167
+ ws,
168
+ kind: "WORK_NODE_CREATED",
169
+ status: "ok",
170
+ refs: [id],
171
+ notes: `${options.type} semantic mirror created via mdkg work`,
172
+ now: options.now,
173
+ });
174
+ return {
175
+ workspace: ws,
176
+ id,
177
+ qid: `${ws}:${id}`,
178
+ path: toPosixPath(path_1.default.relative(options.root, filePath)),
179
+ type: options.type,
180
+ title: options.title,
181
+ };
182
+ }
183
+ function resolveWorkNode(index, idOrQid, ws, allowedTypes, label) {
184
+ const resolved = (0, qid_1.resolveQid)(index, idOrQid, ws);
185
+ if (resolved.status !== "ok") {
186
+ throw new errors_1.NotFoundError((0, qid_1.formatResolveError)(label, idOrQid, resolved, ws));
187
+ }
188
+ const node = index.nodes[resolved.qid];
189
+ if (!node || !allowedTypes.has(node.type)) {
190
+ throw new errors_1.NotFoundError(`${label} not found: ${idOrQid}`);
191
+ }
192
+ if (node.source?.imported) {
193
+ throw new errors_1.UsageError(`cannot mutate read-only imported node ${node.qid}; update the source workspace for bundle import ${node.source.import_alias}`);
194
+ }
195
+ return node;
196
+ }
197
+ function loadMutableAgentNode(root, idOrQid, wsRaw, type) {
198
+ const config = (0, config_1.loadConfig)(root);
199
+ const ws = normalizeWorkspace(wsRaw);
200
+ const { index } = (0, index_cache_1.loadIndex)({ root, config });
201
+ const node = resolveWorkNode(index, idOrQid, ws, new Set([type]), type);
202
+ const filePath = path_1.default.resolve(root, node.path);
203
+ const parsed = (0, frontmatter_1.parseFrontmatter)(fs_1.default.readFileSync(filePath, "utf8"), filePath);
204
+ return { config, node, filePath, frontmatter: { ...parsed.frontmatter }, body: parsed.body };
205
+ }
206
+ function printReceipt(action, receipt, json) {
207
+ if (json) {
208
+ console.log(JSON.stringify({ action, node: receipt }, null, 2));
209
+ return;
210
+ }
211
+ console.log(`work ${action}: ${receipt.qid} (${receipt.path})`);
212
+ }
213
+ function runWorkContractNewCommandLocked(options) {
214
+ const config = (0, config_1.loadConfig)(options.root);
215
+ const ws = normalizeWorkspace(options.ws);
216
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
217
+ const agentId = normalizePortableId(options.agentId, "--agent-id");
218
+ const relates = findNodeById(index, ws, agentId, "spec") ? [agentId] : [];
219
+ const receipt = createAgentWorkflowNode({
220
+ root: options.root,
221
+ ws,
222
+ type: "work",
223
+ title: options.title,
224
+ id: options.id,
225
+ now: options.now,
226
+ overrides: {
227
+ agent_id: agentId,
228
+ kind: options.kind.toLowerCase(),
229
+ pricing_model: normalizeEnum(options.pricingModel ?? "quoted", "--pricing-model", PRICING_MODELS),
230
+ required_capabilities: parseCsvList(options.requiredCapabilities),
231
+ inputs: parseCsvList(options.inputs),
232
+ outputs: parseCsvList(options.outputs),
233
+ receipt_required: true,
234
+ relates,
235
+ },
236
+ });
237
+ printReceipt("contract created", receipt, options.json);
238
+ }
239
+ function runWorkOrderNewCommandLocked(options) {
240
+ const config = (0, config_1.loadConfig)(options.root);
241
+ const ws = normalizeWorkspace(options.ws);
242
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
243
+ const workId = normalizePortableId(options.workId, "--work-id");
244
+ const workNode = requireNodeById(index, ws, workId, "work", "work contract");
245
+ const workVersion = String(workNode.attributes.version ?? "0.1.0");
246
+ const receipt = createAgentWorkflowNode({
247
+ root: options.root,
248
+ ws,
249
+ type: "work_order",
250
+ title: options.title,
251
+ id: options.id,
252
+ now: options.now,
253
+ overrides: {
254
+ work_id: workId,
255
+ work_version: workVersion,
256
+ requester: options.requester,
257
+ order_status: "submitted",
258
+ request_ref: options.requestRef ?? "request.redacted",
259
+ input_refs: parseCsvList(options.inputRefs),
260
+ requested_outputs: parseCsvList(options.requestedOutputs),
261
+ constraint_refs: parseCsvList(options.constraintRefs),
262
+ artifact_policy: "commit_sidecar_and_zip",
263
+ relates: [workId],
264
+ },
265
+ });
266
+ printReceipt("order created", receipt, options.json);
267
+ }
268
+ function runWorkOrderUpdateCommandLocked(options) {
269
+ const loaded = loadMutableAgentNode(options.root, options.id, options.ws, "work_order");
270
+ if (options.status) {
271
+ loaded.frontmatter.order_status = normalizeEnum(options.status, "--status", ORDER_STATUSES);
272
+ }
273
+ loaded.frontmatter.input_refs = appendUnique(toStringList(loaded.frontmatter.input_refs), parseCsvList(options.addInputRefs));
274
+ loaded.frontmatter.artifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), parseCsvList(options.addArtifacts));
275
+ loaded.frontmatter.updated = (0, date_1.formatDate)(options.now ?? new Date());
276
+ writeFrontmatterFile(loaded.filePath, loaded.frontmatter, loaded.body);
277
+ maybeReindex(options.root, loaded.config);
278
+ printReceipt("order updated", nodeReceipt(options.root, loaded.node), options.json);
279
+ }
280
+ function runWorkReceiptNewCommandLocked(options) {
281
+ const config = (0, config_1.loadConfig)(options.root);
282
+ const ws = normalizeWorkspace(options.ws);
283
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
284
+ const workOrderId = normalizePortableId(options.workOrderId, "--work-order-id");
285
+ requireNodeById(index, ws, workOrderId, "work_order", "work order");
286
+ const receipt = createAgentWorkflowNode({
287
+ root: options.root,
288
+ ws,
289
+ type: "receipt",
290
+ title: options.title,
291
+ id: options.id,
292
+ now: options.now,
293
+ overrides: {
294
+ work_order_id: workOrderId,
295
+ receipt_status: normalizeEnum(options.receiptStatus ?? "recorded", "--receipt-status", RECEIPT_STATUSES),
296
+ outcome: normalizeEnum(options.outcome, "--outcome", OUTCOMES),
297
+ cost_ref: options.costRef ?? "cost.redacted",
298
+ artifacts: parseCsvList(options.artifacts),
299
+ proof_refs: parseCsvList(options.proofRefs),
300
+ attestation_refs: parseCsvList(options.attestationRefs),
301
+ input_hashes: parseCsvList(options.inputHashes),
302
+ output_hashes: parseCsvList(options.outputHashes),
303
+ relates: [workOrderId],
304
+ },
305
+ });
306
+ printReceipt("receipt created", receipt, options.json);
307
+ }
308
+ function runWorkReceiptUpdateCommandLocked(options) {
309
+ const loaded = loadMutableAgentNode(options.root, options.id, options.ws, "receipt");
310
+ if (options.receiptStatus) {
311
+ loaded.frontmatter.receipt_status = normalizeEnum(options.receiptStatus, "--receipt-status", RECEIPT_STATUSES);
312
+ }
313
+ loaded.frontmatter.artifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), parseCsvList(options.addArtifacts));
314
+ loaded.frontmatter.proof_refs = appendUnique(toStringList(loaded.frontmatter.proof_refs), parseCsvList(options.addProofRefs));
315
+ loaded.frontmatter.attestation_refs = appendUnique(toStringList(loaded.frontmatter.attestation_refs), parseCsvList(options.addAttestationRefs));
316
+ loaded.frontmatter.updated = (0, date_1.formatDate)(options.now ?? new Date());
317
+ writeFrontmatterFile(loaded.filePath, loaded.frontmatter, loaded.body);
318
+ maybeReindex(options.root, loaded.config);
319
+ printReceipt("receipt updated", nodeReceipt(options.root, loaded.node), options.json);
320
+ }
321
+ function runWorkArtifactAddCommandLocked(options) {
322
+ const config = (0, config_1.loadConfig)(options.root);
323
+ const ws = normalizeWorkspace(options.ws);
324
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
325
+ const target = resolveWorkNode(index, options.targetId, ws, new Set(["work_order", "receipt"]), "work order or receipt");
326
+ const archiveLogs = [];
327
+ const originalLog = console.log;
328
+ console.log = (...args) => {
329
+ archiveLogs.push(args.map(String).join(" "));
330
+ };
331
+ let archivePayload = {};
332
+ try {
333
+ (0, archive_1.runArchiveAddCommand)({
334
+ root: options.root,
335
+ ws: target.ws,
336
+ file: options.file,
337
+ id: options.id,
338
+ kind: options.kind ?? (target.type === "work_order" ? "source" : "artifact"),
339
+ relates: target.id,
340
+ json: true,
341
+ now: options.now,
342
+ });
343
+ archivePayload = JSON.parse(archiveLogs.join("\n"));
344
+ }
345
+ finally {
346
+ console.log = originalLog;
347
+ }
348
+ const archiveUri = archivePayload.archive?.archive_uri;
349
+ if (!archiveUri) {
350
+ throw new Error("archive add did not return an archive URI");
351
+ }
352
+ const loaded = loadMutableAgentNode(options.root, target.qid, target.ws, target.type);
353
+ const archiveKind = (options.kind ?? (target.type === "work_order" ? "source" : "artifact")).toLowerCase();
354
+ if (target.type === "work_order" && archiveKind === "source") {
355
+ loaded.frontmatter.input_refs = appendUnique(toStringList(loaded.frontmatter.input_refs), [archiveUri]);
356
+ }
357
+ else {
358
+ loaded.frontmatter.artifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), [archiveUri]);
359
+ }
360
+ if (archivePayload.archive?.id) {
361
+ loaded.frontmatter.relates = appendUnique(toStringList(loaded.frontmatter.relates), [archivePayload.archive.id]);
362
+ }
363
+ loaded.frontmatter.updated = (0, date_1.formatDate)(options.now ?? new Date());
364
+ writeFrontmatterFile(loaded.filePath, loaded.frontmatter, loaded.body);
365
+ maybeReindex(options.root, loaded.config);
366
+ if (options.json) {
367
+ console.log(JSON.stringify({
368
+ action: "artifact_registered",
369
+ target: nodeReceipt(options.root, target),
370
+ archive: archivePayload.archive,
371
+ }, null, 2));
372
+ return;
373
+ }
374
+ console.log(`work artifact registered: ${target.qid} -> ${archiveUri}`);
375
+ }
376
+ function withWorkLock(root, fn) {
377
+ const config = (0, config_1.loadConfig)(root);
378
+ return (0, lock_1.withMutationLock)(root, config.index.lock_timeout_ms, fn);
379
+ }
380
+ function runWorkContractNewCommand(options) {
381
+ return withWorkLock(options.root, () => runWorkContractNewCommandLocked(options));
382
+ }
383
+ function runWorkOrderNewCommand(options) {
384
+ return withWorkLock(options.root, () => runWorkOrderNewCommandLocked(options));
385
+ }
386
+ function runWorkOrderUpdateCommand(options) {
387
+ return withWorkLock(options.root, () => runWorkOrderUpdateCommandLocked(options));
388
+ }
389
+ function runWorkReceiptNewCommand(options) {
390
+ return withWorkLock(options.root, () => runWorkReceiptNewCommandLocked(options));
391
+ }
392
+ function runWorkReceiptUpdateCommand(options) {
393
+ return withWorkLock(options.root, () => runWorkReceiptUpdateCommandLocked(options));
394
+ }
395
+ function runWorkArtifactAddCommand(options) {
396
+ return withWorkLock(options.root, () => runWorkArtifactAddCommandLocked(options));
397
+ }
@@ -21,6 +21,7 @@ function workspaceReceipt(alias, workspace) {
21
21
  path: workspace.path,
22
22
  enabled: workspace.enabled,
23
23
  mdkg_dir: workspace.mdkg_dir,
24
+ visibility: workspace.visibility ?? "private",
24
25
  };
25
26
  }
26
27
  function printWorkspaceMutationReceipt(action, workspace, json) {
@@ -90,6 +91,7 @@ function runWorkspaceListCommand(options) {
90
91
  path: ws.path,
91
92
  enabled: ws.enabled,
92
93
  mdkg_dir: ws.mdkg_dir,
94
+ visibility: ws.visibility,
93
95
  };
94
96
  }),
95
97
  }, null, 2));
@@ -102,13 +104,21 @@ function runWorkspaceListCommand(options) {
102
104
  for (const alias of aliases) {
103
105
  const ws = config.workspaces[alias];
104
106
  const status = ws.enabled ? "enabled" : "disabled";
105
- console.log(`${alias} | ${status} | ${ws.path} | ${ws.mdkg_dir}`);
107
+ console.log(`${alias} | ${status} | ${ws.visibility} | ${ws.path} | ${ws.mdkg_dir}`);
106
108
  }
107
109
  }
110
+ function normalizeVisibility(value) {
111
+ const normalized = (value ?? "private").toLowerCase();
112
+ if (normalized === "private" || normalized === "internal" || normalized === "public") {
113
+ return normalized;
114
+ }
115
+ throw new errors_1.UsageError("--visibility must be private, internal, or public");
116
+ }
108
117
  function runWorkspaceAddCommand(options) {
109
118
  const alias = normalizeAlias(options.alias);
110
119
  const workspacePath = normalizeCommandWorkspacePath(options.workspacePath, "workspace path");
111
120
  const mdkgDir = normalizeCommandWorkspacePath(options.mdkgDir ?? ".mdkg", "workspace mdkg dir");
121
+ const visibility = normalizeVisibility(options.visibility);
112
122
  if ((0, workspace_path_1.isRootWorkspacePath)(workspacePath)) {
113
123
  throw new errors_1.UsageError('workspace path must not be "." for non-root workspaces');
114
124
  }
@@ -133,7 +143,7 @@ function runWorkspaceAddCommand(options) {
133
143
  throw new errors_1.UsageError(`workspace document root already registered by ${existingAlias}`);
134
144
  }
135
145
  }
136
- const workspace = { path: workspacePath, enabled: true, mdkg_dir: mdkgDir };
146
+ const workspace = { path: workspacePath, enabled: true, mdkg_dir: mdkgDir, visibility };
137
147
  workspaces[alias] = workspace;
138
148
  raw.workspaces = workspaces;
139
149
  writeRawConfig(configPath, raw);
@@ -20,6 +20,12 @@ const PACK_EDGE_KEYS = new Set([
20
20
  "next",
21
21
  ]);
22
22
  const NEXT_WORK_STRATEGIES = new Set(["chain_then_priority"]);
23
+ const WORKSPACE_VISIBILITY_VALUES = new Set(["private", "internal", "public"]);
24
+ const BUNDLE_PROFILE_VALUES = new Set(["private", "public"]);
25
+ const INDEX_BACKEND_VALUES = new Set(["json", "sqlite"]);
26
+ const DEFAULT_ARCHIVE_LARGE_CACHE_WARNING_BYTES = 26214400;
27
+ const DEFAULT_SQLITE_COMMIT_WARNING_BYTES = 52428800;
28
+ const DEFAULT_LOCK_TIMEOUT_MS = 10000;
23
29
  function isObject(value) {
24
30
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
31
  }
@@ -166,6 +172,18 @@ function validateWorkspaceAlias(alias, errors) {
166
172
  errors.push(`workspaces.${alias} alias must be lowercase and use [a-z0-9_]`);
167
173
  }
168
174
  }
175
+ function validateBundleImportAlias(alias, workspaces, errors) {
176
+ if (alias === "all") {
177
+ errors.push("bundle_imports.all alias is reserved");
178
+ return;
179
+ }
180
+ if (alias !== alias.toLowerCase() || !WORKSPACE_ALIAS_RE.test(alias)) {
181
+ errors.push(`bundle_imports.${alias} alias must be lowercase and use [a-z0-9_]`);
182
+ }
183
+ if (workspaces[alias]) {
184
+ errors.push(`bundle_imports.${alias} must not collide with workspaces.${alias}`);
185
+ }
186
+ }
169
187
  function requireContainedPath(value, path, errors) {
170
188
  const raw = requireString(value, path, errors);
171
189
  if (!raw) {
@@ -188,16 +206,58 @@ function validateConfigSchema(raw) {
188
206
  const schema_version = requireNumber(raw.schema_version, "schema_version", errors);
189
207
  const tool = requireString(raw.tool, "tool", errors);
190
208
  const root_required = requireBoolean(raw.root_required, "root_required", errors);
209
+ const archiveRaw = raw.archive === undefined
210
+ ? { large_cache_warning_bytes: DEFAULT_ARCHIVE_LARGE_CACHE_WARNING_BYTES }
211
+ : requireObject(raw.archive, "archive", errors);
191
212
  const indexRaw = requireObject(raw.index, "index", errors);
213
+ const capabilitiesRaw = raw.capabilities === undefined
214
+ ? { cache_path: ".mdkg/index/capabilities.json" }
215
+ : requireObject(raw.capabilities, "capabilities", errors);
216
+ const bundlesRaw = raw.bundles === undefined
217
+ ? { output_dir: ".mdkg/bundles", default_profile: "private" }
218
+ : requireObject(raw.bundles, "bundles", errors);
219
+ const bundleImportsRaw = raw.bundle_imports === undefined
220
+ ? {}
221
+ : requireObject(raw.bundle_imports, "bundle_imports", errors);
192
222
  const packRaw = requireObject(raw.pack, "pack", errors);
193
223
  const templatesRaw = requireObject(raw.templates, "templates", errors);
194
224
  const workRaw = requireObject(raw.work, "work", errors);
195
225
  const workspacesRaw = requireObject(raw.workspaces, "workspaces", errors);
226
+ const archive = archiveRaw
227
+ ? {
228
+ large_cache_warning_bytes: archiveRaw.large_cache_warning_bytes === undefined
229
+ ? DEFAULT_ARCHIVE_LARGE_CACHE_WARNING_BYTES
230
+ : requireNonNegativeInteger(archiveRaw.large_cache_warning_bytes, "archive.large_cache_warning_bytes", errors),
231
+ }
232
+ : undefined;
196
233
  const index = indexRaw
197
234
  ? {
198
235
  auto_reindex: requireBoolean(indexRaw.auto_reindex, "index.auto_reindex", errors),
199
236
  tolerant: requireBoolean(indexRaw.tolerant, "index.tolerant", errors),
237
+ backend: indexRaw.backend === undefined
238
+ ? "json"
239
+ : requireStringInSet(indexRaw.backend, "index.backend", INDEX_BACKEND_VALUES, errors),
200
240
  global_index_path: requireContainedPath(indexRaw.global_index_path, "index.global_index_path", errors),
241
+ sqlite_path: indexRaw.sqlite_path === undefined
242
+ ? ".mdkg/index/mdkg.sqlite"
243
+ : requireContainedPath(indexRaw.sqlite_path, "index.sqlite_path", errors),
244
+ sqlite_commit_warning_bytes: indexRaw.sqlite_commit_warning_bytes === undefined
245
+ ? DEFAULT_SQLITE_COMMIT_WARNING_BYTES
246
+ : requireNonNegativeInteger(indexRaw.sqlite_commit_warning_bytes, "index.sqlite_commit_warning_bytes", errors),
247
+ lock_timeout_ms: indexRaw.lock_timeout_ms === undefined
248
+ ? DEFAULT_LOCK_TIMEOUT_MS
249
+ : requirePositiveInteger(indexRaw.lock_timeout_ms, "index.lock_timeout_ms", errors),
250
+ }
251
+ : undefined;
252
+ const capabilities = capabilitiesRaw
253
+ ? {
254
+ cache_path: requireContainedPath(capabilitiesRaw.cache_path, "capabilities.cache_path", errors),
255
+ }
256
+ : undefined;
257
+ const bundles = bundlesRaw
258
+ ? {
259
+ output_dir: requireContainedPath(bundlesRaw.output_dir, "bundles.output_dir", errors),
260
+ default_profile: requireStringInSet(bundlesRaw.default_profile, "bundles.default_profile", BUNDLE_PROFILE_VALUES, errors),
201
261
  }
202
262
  : undefined;
203
263
  const packLimitsRaw = packRaw ? requireObject(packRaw.limits, "pack.limits", errors) : undefined;
@@ -268,7 +328,10 @@ function validateConfigSchema(raw) {
268
328
  const wsPath = requireString(ws.path, `workspaces.${alias}.path`, errors);
269
329
  const wsEnabled = requireBoolean(ws.enabled, `workspaces.${alias}.enabled`, errors);
270
330
  const wsMdkgDir = requireString(ws.mdkg_dir, `workspaces.${alias}.mdkg_dir`, errors);
271
- if (wsPath && wsEnabled !== undefined && wsMdkgDir) {
331
+ const wsVisibility = ws.visibility === undefined
332
+ ? "private"
333
+ : requireStringInSet(ws.visibility, `workspaces.${alias}.visibility`, WORKSPACE_VISIBILITY_VALUES, errors);
334
+ if (wsPath && wsEnabled !== undefined && wsMdkgDir && wsVisibility) {
272
335
  let normalizedPath;
273
336
  let normalizedMdkgDir;
274
337
  try {
@@ -303,6 +366,7 @@ function validateConfigSchema(raw) {
303
366
  path: normalizedPath,
304
367
  enabled: wsEnabled,
305
368
  mdkg_dir: normalizedMdkgDir,
369
+ visibility: wsVisibility,
306
370
  };
307
371
  }
308
372
  }
@@ -325,6 +389,52 @@ function validateConfigSchema(raw) {
325
389
  errors.push('workspaces.root.mdkg_dir must be ".mdkg"');
326
390
  }
327
391
  }
392
+ const bundle_imports = {};
393
+ if (bundleImportsRaw) {
394
+ for (const [alias, entry] of Object.entries(bundleImportsRaw)) {
395
+ validateBundleImportAlias(alias, workspaces, errors);
396
+ const rawImport = requireObject(entry, `bundle_imports.${alias}`, errors);
397
+ if (!rawImport) {
398
+ continue;
399
+ }
400
+ const importPath = requireContainedPath(rawImport.path, `bundle_imports.${alias}.path`, errors);
401
+ const enabled = rawImport.enabled === undefined
402
+ ? true
403
+ : requireBoolean(rawImport.enabled, `bundle_imports.${alias}.enabled`, errors);
404
+ const visibility = rawImport.visibility === undefined
405
+ ? "private"
406
+ : requireStringInSet(rawImport.visibility, `bundle_imports.${alias}.visibility`, WORKSPACE_VISIBILITY_VALUES, errors);
407
+ const expectedProfile = rawImport.expected_profile === undefined
408
+ ? "private"
409
+ : requireStringInSet(rawImport.expected_profile, `bundle_imports.${alias}.expected_profile`, BUNDLE_PROFILE_VALUES, errors);
410
+ const sourcePath = rawImport.source_path === undefined
411
+ ? undefined
412
+ : requireContainedPath(rawImport.source_path, `bundle_imports.${alias}.source_path`, errors);
413
+ const sourceRepo = rawImport.source_repo === undefined
414
+ ? undefined
415
+ : requireString(rawImport.source_repo, `bundle_imports.${alias}.source_repo`, errors);
416
+ const maxStaleSeconds = rawImport.max_stale_seconds === undefined
417
+ ? undefined
418
+ : requirePositiveInteger(rawImport.max_stale_seconds, `bundle_imports.${alias}.max_stale_seconds`, errors);
419
+ if (visibility !== undefined &&
420
+ expectedProfile !== undefined &&
421
+ visibility !== "private" &&
422
+ expectedProfile !== "public") {
423
+ errors.push(`bundle_imports.${alias}.expected_profile must be public when visibility is ${visibility}`);
424
+ }
425
+ if (importPath && enabled !== undefined && visibility && expectedProfile) {
426
+ bundle_imports[alias] = {
427
+ path: importPath,
428
+ enabled,
429
+ visibility: visibility,
430
+ expected_profile: expectedProfile,
431
+ ...(sourcePath ? { source_path: sourcePath } : {}),
432
+ ...(sourceRepo ? { source_repo: sourceRepo } : {}),
433
+ ...(maxStaleSeconds !== undefined ? { max_stale_seconds: maxStaleSeconds } : {}),
434
+ };
435
+ }
436
+ }
437
+ }
328
438
  if (errors.length > 0) {
329
439
  throw new Error(`config validation failed:\n${errors.join("\n")}`);
330
440
  }
@@ -332,7 +442,11 @@ function validateConfigSchema(raw) {
332
442
  schema_version: schema_version,
333
443
  tool: tool,
334
444
  root_required: root_required,
445
+ archive: archive,
335
446
  index: index,
447
+ capabilities: capabilities,
448
+ bundles: bundles,
449
+ bundle_imports,
336
450
  pack: pack,
337
451
  templates: templates,
338
452
  work: work,