mdkg 0.1.0 → 0.1.2

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