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
@@ -11,6 +11,7 @@ const index_cache_1 = require("../graph/index_cache");
11
11
  const frontmatter_1 = require("../graph/frontmatter");
12
12
  const skills_index_cache_1 = require("../graph/skills_index_cache");
13
13
  const node_1 = require("../graph/node");
14
+ const visibility_1 = require("../graph/visibility");
14
15
  const budget_1 = require("../pack/budget");
15
16
  const export_json_1 = require("../pack/export_json");
16
17
  const export_md_1 = require("../pack/export_md");
@@ -217,6 +218,53 @@ function applyNodeCountLimit(pack, maxNodes) {
217
218
  nodes: included,
218
219
  };
219
220
  }
221
+ function packNodeVisibility(node, index, config) {
222
+ const indexNode = index.nodes[node.qid];
223
+ if (indexNode) {
224
+ return (0, visibility_1.effectiveNodeVisibility)(indexNode, config);
225
+ }
226
+ return (0, visibility_1.normalizeVisibility)(config.workspaces[node.workspace]?.visibility, "workspace visibility");
227
+ }
228
+ function applyVisibilityFilter(pack, index, config, scope) {
229
+ if (scope === "private") {
230
+ return {
231
+ ...pack,
232
+ meta: {
233
+ ...pack.meta,
234
+ visibility: scope,
235
+ },
236
+ };
237
+ }
238
+ const includedNodes = pack.nodes.filter((node) => (0, visibility_1.isVisibleAt)(packNodeVisibility(node, index, config), scope));
239
+ if (includedNodes.length === 0 || includedNodes[0].qid !== pack.nodes[0]?.qid) {
240
+ throw new errors_1.ValidationError(`pack root ${pack.meta.root} is not visible at ${scope}`);
241
+ }
242
+ const includedQids = new Set(includedNodes.map((node) => node.qid));
243
+ const graphIncludedQids = new Set(includedNodes.filter((node) => Boolean(index.nodes[node.qid])).map((node) => node.qid));
244
+ const violations = (0, visibility_1.collectVisibilityViolations)(index, config, {
245
+ includedQids: graphIncludedQids,
246
+ scope,
247
+ });
248
+ if (violations.length > 0) {
249
+ throw new errors_1.ValidationError(`${scope} pack contains less-visible references:\n${(0, visibility_1.visibilityViolationMessages)(violations).join("\n")}`);
250
+ }
251
+ const dropped = pack.nodes
252
+ .filter((node) => !includedQids.has(node.qid))
253
+ .map((node) => node.qid);
254
+ return {
255
+ ...pack,
256
+ meta: {
257
+ ...pack.meta,
258
+ visibility: scope,
259
+ node_count: includedNodes.length,
260
+ truncated: {
261
+ ...pack.meta.truncated,
262
+ dropped: [...pack.meta.truncated.dropped, ...dropped],
263
+ },
264
+ },
265
+ nodes: includedNodes,
266
+ };
267
+ }
220
268
  function writeJsonFile(outPath, payload) {
221
269
  fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
222
270
  fs_1.default.writeFileSync(outPath, JSON.stringify(payload, null, 2), "utf8");
@@ -243,6 +291,9 @@ function printDryRunSummary(pack, stats, format) {
243
291
  console.log(`root: ${pack.meta.root}`);
244
292
  console.log(`profile: ${pack.meta.profile ?? "standard"}`);
245
293
  console.log(`body_mode: ${pack.meta.body_mode ?? "full"}`);
294
+ if (pack.meta.visibility) {
295
+ console.log(`visibility: ${pack.meta.visibility}`);
296
+ }
246
297
  console.log(`format: ${format}`);
247
298
  console.log(`nodes: ${pack.nodes.length}`);
248
299
  if (pack.meta.latest_checkpoint_qid) {
@@ -267,10 +318,10 @@ function printDryRunSummary(pack, stats, format) {
267
318
  function runPackCommand(options) {
268
319
  const config = (0, config_1.loadConfig)(options.root);
269
320
  const ws = normalizeWorkspace(options.ws);
270
- if (ws && !config.workspaces[ws]) {
321
+ if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
271
322
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
272
323
  }
273
- const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
324
+ const { index, rebuilt, stale, warnings } = (0, index_cache_1.loadIndex)({
274
325
  root: options.root,
275
326
  config,
276
327
  useCache: !options.noCache,
@@ -279,6 +330,9 @@ function runPackCommand(options) {
279
330
  if (stale && !rebuilt && !options.noCache) {
280
331
  console.error("warning: index is stale; run mdkg index to refresh");
281
332
  }
333
+ for (const warning of warnings) {
334
+ console.error(`warning: ${warning}`);
335
+ }
282
336
  const resolved = (0, qid_1.resolveQid)(index, options.id, ws);
283
337
  if (resolved.status !== "ok") {
284
338
  throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
@@ -291,6 +345,9 @@ function runPackCommand(options) {
291
345
  const edges = normalizeEdges([...config.pack.default_edges, ...extraEdges]);
292
346
  const skillsPolicy = resolveSkillsPolicy(options.skills);
293
347
  const skillsDepth = resolveSkillsDepth(options.skillsDepth);
348
+ const visibility = options.visibility
349
+ ? (0, visibility_1.normalizeVisibility)(options.visibility)
350
+ : undefined;
294
351
  let resolvedProfile;
295
352
  try {
296
353
  resolvedProfile = (0, profile_1.resolvePackProfile)({
@@ -350,6 +407,9 @@ function runPackCommand(options) {
350
407
  packWithSkills = appendSkillsToPack(packWithSkills, selectedEntries, skillsDepth, options.root);
351
408
  packWithSkills = applyNodeCountLimit(packWithSkills, config.pack.limits.max_nodes);
352
409
  }
410
+ if (visibility) {
411
+ packWithSkills = applyVisibilityFilter(packWithSkills, index, config, visibility);
412
+ }
353
413
  const templateHeadingMap = resolvedProfile.bodyMode === "summary"
354
414
  ? (0, headings_1.loadTemplateHeadingMap)(options.root, config, Array.from(node_1.ALLOWED_TYPES))
355
415
  : {};
@@ -36,6 +36,7 @@ function toNodeSummaryJson(node) {
36
36
  blocked_by: [...node.edges.blocked_by],
37
37
  blocks: [...node.edges.blocks],
38
38
  },
39
+ ...(node.source ? { source: node.source } : {}),
39
40
  };
40
41
  }
41
42
  function toNodeDetailJson(node, body) {
@@ -54,14 +54,14 @@ function runSearchCommand(options) {
54
54
  }
55
55
  const config = (0, config_1.loadConfig)(options.root);
56
56
  const ws = normalizeWorkspace(options.ws);
57
- if (ws && !config.workspaces[ws]) {
57
+ if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
58
58
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
59
59
  }
60
60
  const normalizedType = options.type?.toLowerCase();
61
61
  if (normalizedType === "skill") {
62
62
  throw new errors_1.UsageError("--type skill is no longer supported here; use `mdkg skill search`");
63
63
  }
64
- const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
64
+ const { index, rebuilt, stale, warnings } = (0, index_cache_1.loadIndex)({
65
65
  root: options.root,
66
66
  config,
67
67
  useCache: !options.noCache,
@@ -70,6 +70,9 @@ function runSearchCommand(options) {
70
70
  if (stale && !rebuilt && !options.noCache) {
71
71
  console.error("warning: index is stale; run mdkg index to refresh");
72
72
  }
73
+ for (const warning of warnings) {
74
+ console.error(`warning: ${warning}`);
75
+ }
73
76
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
74
77
  const nodeResults = (0, filter_1.filterNodes)(Object.values(index.nodes), {
75
78
  ws,
@@ -1,14 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.runShowCommand = runShowCommand;
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
4
  const config_1 = require("../core/config");
10
5
  const index_cache_1 = require("../graph/index_cache");
11
- const frontmatter_1 = require("../graph/frontmatter");
6
+ const node_body_1 = require("../graph/node_body");
12
7
  const errors_1 = require("../util/errors");
13
8
  const qid_1 = require("../util/qid");
14
9
  const node_card_1 = require("./node_card");
@@ -34,14 +29,14 @@ function formatAttributeLine(key, value) {
34
29
  function runShowCommand(options) {
35
30
  const config = (0, config_1.loadConfig)(options.root);
36
31
  const ws = normalizeWorkspace(options.ws);
37
- if (ws && !config.workspaces[ws]) {
32
+ if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
38
33
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
39
34
  }
40
35
  const normalizedId = options.id.toLowerCase();
41
36
  if (normalizedId.startsWith("skill:") || normalizedId.startsWith("root:skill:")) {
42
37
  throw new errors_1.UsageError(`generic skill show is no longer supported; use \`mdkg skill show ${options.id.replace(/^root:skill:|^skill:/i, "")}\``);
43
38
  }
44
- const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
39
+ const { index, rebuilt, stale, warnings } = (0, index_cache_1.loadIndex)({
45
40
  root: options.root,
46
41
  config,
47
42
  useCache: !options.noCache,
@@ -50,19 +45,17 @@ function runShowCommand(options) {
50
45
  if (stale && !rebuilt && !options.noCache) {
51
46
  console.error("warning: index is stale; run mdkg index to refresh");
52
47
  }
48
+ for (const warning of warnings) {
49
+ console.error(`warning: ${warning}`);
50
+ }
53
51
  const resolved = (0, qid_1.resolveQid)(index, options.id, ws);
54
52
  if (resolved.status !== "ok") {
55
53
  throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
56
54
  }
57
55
  const node = index.nodes[resolved.qid];
58
- const filePath = path_1.default.resolve(options.root, node.path);
59
56
  let body = "";
60
57
  if (!options.metaOnly) {
61
- if (!fs_1.default.existsSync(filePath)) {
62
- throw new errors_1.NotFoundError(`file not found for ${node.qid}: ${node.path}`);
63
- }
64
- const content = fs_1.default.readFileSync(filePath, "utf8");
65
- body = (0, frontmatter_1.parseFrontmatter)(content, filePath).body.trimEnd();
58
+ body = (0, node_body_1.readNodeBody)(options.root, node);
66
59
  }
67
60
  const format = options.format ?? (options.json ? "json" : undefined);
68
61
  if (format) {
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.syncSkillMirrors = syncSkillMirrors;
7
+ exports.preflightSkillMirrorTargets = preflightSkillMirrorTargets;
7
8
  exports.shouldMaintainSkillMirrors = shouldMaintainSkillMirrors;
8
9
  exports.auditSkillMirrors = auditSkillMirrors;
9
10
  exports.scaffoldMirrorRoots = scaffoldMirrorRoots;
@@ -271,6 +272,27 @@ function syncSkillMirrors(options) {
271
272
  }
272
273
  return { synced, pruned, targets: touchedTargets };
273
274
  }
275
+ function preflightSkillMirrorTargets(options) {
276
+ if (options.force) {
277
+ return;
278
+ }
279
+ const slugs = Array.from(new Set(options.slugs.map((value) => value.trim().toLowerCase()).filter(Boolean)));
280
+ if (slugs.length === 0) {
281
+ return;
282
+ }
283
+ for (const target of resolveMirrorTargets(options.root)) {
284
+ if (!fs_1.default.existsSync(target.rootDir) && !fs_1.default.existsSync(target.skillsRoot)) {
285
+ continue;
286
+ }
287
+ const managed = readManifest(target);
288
+ for (const slug of slugs) {
289
+ const destDir = path_1.default.join(target.skillsRoot, slug);
290
+ if (fs_1.default.existsSync(destDir) && !managed.has(slug)) {
291
+ throw new errors_1.UsageError(`${path_1.default.relative(options.root, destDir)} already exists and is not mdkg-managed; rerun \`mdkg init --agent --force\` or \`mdkg skill sync --force\` to replace it`);
292
+ }
293
+ }
294
+ }
295
+ }
274
296
  function shouldMaintainSkillMirrors(root) {
275
297
  return shouldCreateMirrorRoots(root);
276
298
  }
@@ -107,6 +107,9 @@ function loadMutableTaskNode(root, idOrQid, wsHint) {
107
107
  if (!node) {
108
108
  throw new errors_1.NotFoundError(`task not found: ${idOrQid}`);
109
109
  }
110
+ if (node.source?.imported) {
111
+ throw new errors_1.UsageError(`cannot mutate read-only imported node ${node.qid}; update the source workspace for bundle import ${node.source.import_alias}`);
112
+ }
110
113
  if (!MUTABLE_TASK_TYPES.has(node.type)) {
111
114
  throw new errors_1.UsageError(`mdkg task only supports task, bug, and test nodes; use markdown editing for ${node.type}:${node.id}`);
112
115
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runUpgradeCommand = runUpgradeCommand;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const child_process_1 = require("child_process");
9
10
  const migrate_1 = require("../core/migrate");
10
11
  const config_1 = require("../core/config");
11
12
  const paths_1 = require("../core/paths");
@@ -17,6 +18,7 @@ const skill_mirror_1 = require("./skill_mirror");
17
18
  const DEFAULT_SEED_SUBDIR = path_1.default.resolve(__dirname, "..", "init");
18
19
  const PROTECTED_CORE_DOCS = new Set([".mdkg/core/SOUL.md", ".mdkg/core/HUMAN.md"]);
19
20
  const CREATE_ONLY_PRESERVED = new Set([".mdkg/core/core.md"]);
21
+ const ARCHIVE_RAW_IGNORE_ENTRIES = [".mdkg/archive/**/source/"];
20
22
  function seededInitEvent(nowIso) {
21
23
  const event = {
22
24
  ts: nowIso,
@@ -85,6 +87,9 @@ function record(summary, changes, change) {
85
87
  summary.skipped += 1;
86
88
  summary.conflicted += 1;
87
89
  break;
90
+ case "skip":
91
+ summary.skipped += 1;
92
+ break;
88
93
  }
89
94
  }
90
95
  function buildKnownHashes(manifests) {
@@ -103,7 +108,7 @@ function buildKnownHashes(manifests) {
103
108
  return hashes;
104
109
  }
105
110
  function shouldIncludeFile(file, agentWorkspace) {
106
- if (file.category === "default_skill") {
111
+ if (file.category === "agent_doc" || file.category === "startup_doc" || file.category === "default_skill") {
107
112
  return agentWorkspace;
108
113
  }
109
114
  return file.category !== "config";
@@ -185,11 +190,59 @@ function migrateConfigIfNeeded(root, dryRun, summary, changes) {
185
190
  writeFile(cfgPath, `${JSON.stringify(migrated.config, null, 2)}\n`);
186
191
  }
187
192
  }
193
+ function isIgnoredBySimpleGitignore(root, relativePath) {
194
+ const ignorePath = path_1.default.join(root, ".gitignore");
195
+ if (!fs_1.default.existsSync(ignorePath)) {
196
+ return false;
197
+ }
198
+ const normalized = relativePath.replace(/\\/g, "/");
199
+ const lines = fs_1.default.readFileSync(ignorePath, "utf8").split(/\r?\n/);
200
+ let ignored = false;
201
+ for (const rawLine of lines) {
202
+ const line = rawLine.trim();
203
+ if (!line || line.startsWith("#")) {
204
+ continue;
205
+ }
206
+ const negated = line.startsWith("!");
207
+ const pattern = negated ? line.slice(1) : line;
208
+ const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\/+/, "");
209
+ const matches = normalizedPattern === normalized ||
210
+ (normalizedPattern.endsWith("/") && normalized.startsWith(normalizedPattern)) ||
211
+ (normalizedPattern.endsWith("/*") && normalized.startsWith(normalizedPattern.slice(0, -1)));
212
+ if (matches) {
213
+ ignored = !negated;
214
+ }
215
+ }
216
+ return ignored;
217
+ }
218
+ function isGitIgnored(root, relativePath) {
219
+ const result = (0, child_process_1.spawnSync)("git", ["check-ignore", "--quiet", "--", relativePath], {
220
+ cwd: root,
221
+ stdio: "ignore",
222
+ });
223
+ if (result.status === 0) {
224
+ return true;
225
+ }
226
+ if (result.status === 1) {
227
+ return false;
228
+ }
229
+ return isIgnoredBySimpleGitignore(root, relativePath);
230
+ }
188
231
  function ensureAgentRuntimeFiles(root, dryRun, summary, changes) {
189
232
  const eventsPath = path_1.default.join(root, ".mdkg", "work", "events", "events.jsonl");
233
+ const relEventsPath = ".mdkg/work/events/events.jsonl";
190
234
  if (!fs_1.default.existsSync(eventsPath)) {
235
+ if (isGitIgnored(root, relEventsPath)) {
236
+ record(summary, changes, {
237
+ path: relEventsPath,
238
+ category: "event_log",
239
+ action: "skip",
240
+ reason: "event log path is ignored; run `mdkg event enable` if local event provenance should be restored",
241
+ });
242
+ return;
243
+ }
191
244
  record(summary, changes, {
192
- path: ".mdkg/work/events/events.jsonl",
245
+ path: relEventsPath,
193
246
  category: "event_log",
194
247
  action: "create",
195
248
  reason: "agent workspace is missing event log",
@@ -202,9 +255,71 @@ function ensureAgentRuntimeFiles(root, dryRun, summary, changes) {
202
255
  summary.unchanged += 1;
203
256
  }
204
257
  }
258
+ function ensureArchiveIgnorePolicy(root, dryRun, summary, changes) {
259
+ const ignorePath = path_1.default.join(root, ".gitignore");
260
+ const raw = fs_1.default.existsSync(ignorePath) ? fs_1.default.readFileSync(ignorePath, "utf8") : "";
261
+ const lines = raw.split(/\r?\n/);
262
+ const existing = new Set(lines.map((line) => line.trim()).filter(Boolean));
263
+ const additions = ARCHIVE_RAW_IGNORE_ENTRIES.filter((entry) => !existing.has(entry));
264
+ if (additions.length === 0) {
265
+ summary.unchanged += 1;
266
+ return;
267
+ }
268
+ record(summary, changes, {
269
+ path: ".gitignore",
270
+ category: "ignore_policy",
271
+ action: fs_1.default.existsSync(ignorePath) ? "update" : "create",
272
+ reason: "ignore raw local archive source copies while keeping sidecars and zip caches commit-eligible",
273
+ });
274
+ if (!dryRun) {
275
+ const suffix = raw.length === 0 || raw.endsWith("\n") ? "" : "\n";
276
+ writeFile(ignorePath, `${raw}${suffix}${additions.join("\n")}\n`);
277
+ }
278
+ }
279
+ function isWritableChange(change) {
280
+ return change.action === "create" || change.action === "update" || change.action === "migrate" || change.action === "sync";
281
+ }
282
+ function buildApplySideEffects(options) {
283
+ const hasDirectWrites = options.changes.some(isWritableChange);
284
+ if (!hasDirectWrites && options.existingManifest) {
285
+ return [];
286
+ }
287
+ const effects = [
288
+ {
289
+ path: ".mdkg/init-manifest.json",
290
+ category: "init_manifest",
291
+ action: options.existingManifest ? "update" : "create",
292
+ reason: "records managed init asset fingerprints after apply",
293
+ },
294
+ ];
295
+ if (options.agentWorkspace) {
296
+ effects.push({
297
+ path: ".mdkg/skills/registry.md",
298
+ category: "skill_registry",
299
+ action: "update",
300
+ reason: "refreshes canonical skill registry after apply",
301
+ }, {
302
+ path: ".agents/skills,.claude/skills",
303
+ category: "skill_mirror",
304
+ action: "sync",
305
+ reason: "syncs managed skill mirrors after apply",
306
+ });
307
+ }
308
+ return effects;
309
+ }
205
310
  function emitHumanReceipt(receipt) {
206
311
  const mode = receipt.dry_run ? "dry-run" : "apply";
207
312
  console.log(`mdkg upgrade ${mode}: ${receipt.summary.created} create, ${receipt.summary.updated} update, ${receipt.summary.migrated} migrate, ${receipt.summary.synced} sync, ${receipt.summary.conflicted} conflict`);
313
+ console.log(`safe to apply: ${receipt.safe_to_apply ? "yes" : "no"}`);
314
+ if (receipt.will_write_paths.length > 0) {
315
+ console.log(`will write: ${receipt.will_write_paths.join(", ")}`);
316
+ }
317
+ if (receipt.preserved_customizations.length > 0) {
318
+ console.log(`preserved customizations: ${receipt.preserved_customizations.length}`);
319
+ }
320
+ if (receipt.blocking_conflicts.length > 0) {
321
+ console.log(`blocking conflicts: ${receipt.blocking_conflicts.length}`);
322
+ }
208
323
  if (receipt.changes.length === 0) {
209
324
  console.log("no upgrade changes pending");
210
325
  }
@@ -213,8 +328,18 @@ function emitHumanReceipt(receipt) {
213
328
  console.log(` ${change.action}: ${change.path} (${change.reason})`);
214
329
  }
215
330
  }
216
- if (receipt.dry_run && receipt.changes.some((change) => change.action !== "conflict")) {
217
- console.log("next: mdkg upgrade --apply");
331
+ if (receipt.apply_side_effects.length > 0) {
332
+ for (const effect of receipt.apply_side_effects) {
333
+ console.log(` apply-side-effect: ${effect.path} (${effect.reason})`);
334
+ }
335
+ }
336
+ if (receipt.dry_run && receipt.will_write_paths.length > 0) {
337
+ if (receipt.safe_to_apply) {
338
+ console.log("next: mdkg upgrade --apply (writes only safe managed paths; preserves customized files)");
339
+ }
340
+ else {
341
+ console.log("next: resolve blocking conflicts before running mdkg upgrade --apply");
342
+ }
218
343
  }
219
344
  }
220
345
  function runUpgradeCommand(options) {
@@ -250,30 +375,43 @@ function runUpgradeCommand(options) {
250
375
  if (agentWorkspace) {
251
376
  ensureAgentRuntimeFiles(root, dryRun, summary, changes);
252
377
  }
378
+ ensureArchiveIgnorePolicy(root, dryRun, summary, changes);
379
+ const applySideEffects = buildApplySideEffects({
380
+ existingManifest,
381
+ agentWorkspace,
382
+ changes,
383
+ });
384
+ for (const effect of applySideEffects) {
385
+ record(summary, changes, effect);
386
+ }
253
387
  if (!dryRun) {
254
388
  const appliedManifest = {
255
389
  ...currentManifest,
256
390
  files: managedCurrentFiles.sort((a, b) => a.path.localeCompare(b.path)),
257
391
  };
258
- (0, init_manifest_1.writeInitManifest)(path_1.default.join(root, ".mdkg", init_manifest_1.INIT_MANIFEST_FILE), appliedManifest);
259
- if (agentWorkspace) {
392
+ if (applySideEffects.length > 0) {
393
+ (0, init_manifest_1.writeInitManifest)(path_1.default.join(root, ".mdkg", init_manifest_1.INIT_MANIFEST_FILE), appliedManifest);
394
+ }
395
+ if (agentWorkspace && applySideEffects.length > 0) {
260
396
  const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
261
397
  (0, skill_support_1.refreshSkillsRegistry)(root, config);
262
398
  (0, skill_mirror_1.scaffoldMirrorRoots)(root);
263
- const result = (0, skill_mirror_1.syncSkillMirrors)({ root, config, createRoots: true });
264
- record(summary, changes, {
265
- path: ".agents/skills,.claude/skills",
266
- category: "skill_mirror",
267
- action: "sync",
268
- reason: `${result.synced} mirrored skill(s), ${result.pruned} stale mirror(s) pruned`,
269
- });
399
+ (0, skill_mirror_1.syncSkillMirrors)({ root, config, createRoots: true });
270
400
  }
271
401
  }
402
+ const preservedCustomizations = changes.filter((change) => change.action === "conflict");
403
+ const blockingConflicts = [];
404
+ const willWritePaths = changes.filter(isWritableChange).map((change) => change.path);
272
405
  const receipt = {
273
406
  action: "upgrade",
274
407
  dry_run: dryRun,
275
408
  version,
409
+ safe_to_apply: blockingConflicts.length === 0,
276
410
  summary,
411
+ will_write_paths: Array.from(new Set(willWritePaths)),
412
+ preserved_customizations: preservedCustomizations,
413
+ blocking_conflicts: blockingConflicts,
414
+ apply_side_effects: applySideEffects,
277
415
  changes,
278
416
  };
279
417
  if (options.json) {
@@ -12,6 +12,8 @@ const node_1 = require("../graph/node");
12
12
  const skills_indexer_1 = require("../graph/skills_indexer");
13
13
  const workspace_files_1 = require("../graph/workspace_files");
14
14
  const validate_graph_1 = require("../graph/validate_graph");
15
+ const bundle_imports_1 = require("../graph/bundle_imports");
16
+ const visibility_1 = require("../graph/visibility");
15
17
  const errors_1 = require("../util/errors");
16
18
  const skill_mirror_1 = require("./skill_mirror");
17
19
  const RECOMMENDED_HEADINGS = {
@@ -200,10 +202,14 @@ function validateEventsJsonl(root, config, errors) {
200
202
  }
201
203
  function runValidateCommand(options) {
202
204
  const config = (0, config_1.loadConfig)(options.root);
203
- const templateSchemas = (0, template_schema_1.loadTemplateSchemas)(options.root, config, node_1.ALLOWED_TYPES);
205
+ const templateSchemaInfo = (0, template_schema_1.loadTemplateSchemasWithInfo)(options.root, config, node_1.ALLOWED_TYPES);
206
+ const templateSchemas = templateSchemaInfo.schemas;
204
207
  const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(options.root, config);
205
208
  const errors = [];
206
209
  const warnings = [];
210
+ if (templateSchemaInfo.fallbackTypes.length > 0) {
211
+ warnings.push(`using bundled template schema fallback for missing local type(s): ${templateSchemaInfo.fallbackTypes.join(", ")}; run \`mdkg upgrade --apply\` to vendor built-in templates`);
212
+ }
207
213
  const nodes = {};
208
214
  const idsByWorkspace = {};
209
215
  for (const [alias, files] of Object.entries(filesByAlias)) {
@@ -264,6 +270,16 @@ function runValidateCommand(options) {
264
270
  nodes,
265
271
  reverse_edges: {},
266
272
  };
273
+ const importProjection = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config);
274
+ for (const item of importProjection.index.imports) {
275
+ for (const warning of item.warnings) {
276
+ warnings.push(`bundle import ${item.alias}: ${warning}`);
277
+ }
278
+ for (const error of item.errors) {
279
+ errors.push(`bundle import ${item.alias}: ${error}`);
280
+ }
281
+ }
282
+ const validationIndex = (0, bundle_imports_1.mergeBundleImportsIntoIndex)(index, importProjection);
267
283
  let knownSkills = new Set();
268
284
  try {
269
285
  const skillsIndex = (0, skills_indexer_1.buildSkillsIndex)(options.root, config);
@@ -280,11 +296,12 @@ function runValidateCommand(options) {
280
296
  const message = err instanceof Error ? err.message : "unknown skill validation error";
281
297
  errors.push(message);
282
298
  }
283
- const graphErrors = (0, validate_graph_1.collectGraphErrors)(index, {
299
+ const graphErrors = (0, validate_graph_1.collectGraphErrors)(validationIndex, {
284
300
  allowMissing: false,
285
301
  knownSkillSlugs: knownSkills,
286
302
  });
287
303
  errors.push(...graphErrors);
304
+ errors.push(...(0, visibility_1.visibilityViolationMessages)((0, visibility_1.collectVisibilityViolations)(validationIndex, config)).map((message) => `visibility: ${message}`));
288
305
  const skillsRoot = (0, skills_indexer_1.resolveSkillsRoot)(options.root, config);
289
306
  for (const dirPath of listDirectories(skillsRoot)) {
290
307
  const canonicalPath = path_1.default.join(dirPath, "SKILL.md");