mdkg 0.3.8 → 0.3.9

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,32 @@ mdkg is pre-v1 public alpha software. Command, graph, cache, bundle, and DAL con
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.3.9 - 2026-06-27
12
+
13
+ ### Added
14
+
15
+ - Added `.mdkg/config.json` customization overlays for organization standards,
16
+ custom core docs, and configured skill mirror target paths while keeping the
17
+ mdkg CLI kernel upgradable through `mdkg upgrade --apply`.
18
+ - Added configurable skill mirror support so repos can mirror canonical
19
+ `.mdkg/skills` into arbitrary contained agent-local skill roots, with
20
+ `.agents/skills` and `.claude/skills` preserved as defaults.
21
+ - Added `COLLABORATION.md` as the canonical collaboration/operator profile core
22
+ doc while keeping `HUMAN.md` as a one-release legacy alias.
23
+ - Added deterministic release-notes data generation from `CHANGELOG.md` for
24
+ public docs and future per-release cards.
25
+
26
+ ### Changed
27
+
28
+ - Updated `mdkg init --agent` and `mdkg upgrade --apply` to seed and preserve
29
+ customization overlays, configurable mirrors, `COLLABORATION.md`, legacy
30
+ `HUMAN.md`, and accurate managed init-manifest hashes.
31
+ - Refreshed first-party mdkg skills and default init seed skills for current CLI
32
+ coverage, MANIFEST authoring, configured mirror targets, pre-publish gates,
33
+ and explicit no-publish/no-tag/no-push approval boundaries.
34
+ - Expanded `npm run docs:check` and `prepublishOnly` to verify generated CLI
35
+ docs, generated release-note data, and public command examples before publish.
36
+
11
37
  ## 0.3.8 - 2026-06-25
12
38
 
13
39
  ### Added
@@ -1,7 +1,7 @@
1
1
  # CLI Command Matrix
2
2
 
3
3
  as_of: 2026-06-21
4
- package_version_in_source: 0.3.8
4
+ package_version_in_source: 0.3.9
5
5
  source: live help from `src/cli.ts`, runtime command handlers, and `dec-15`..`dec-18`
6
6
  status: canonical single-source command and flag reference for mdkg
7
7
 
@@ -118,7 +118,7 @@ Notes:
118
118
  - `--agent` is the canonical complete AI-agent bootstrap path
119
119
  - removed flags `--llm`, `--agents`, `--claude`, and `--omni` fail before mutation with guidance to use `mdkg init --agent`
120
120
  - published bootstrap config is root-only by default
121
- - `--agent` creates `AGENT_START.md`, `AGENTS.md`, `CLAUDE.md`, `llms.txt`, `CLI_COMMAND_MATRIX.md`, strict-node core docs, default mdkg usage skills, `events.jsonl`, registry, and skill mirrors
121
+ - `--agent` creates `AGENT_START.md`, `AGENTS.md`, `CLAUDE.md`, `llms.txt`, `CLI_COMMAND_MATRIX.md`, strict-node `SOUL.md` / `COLLABORATION.md` core docs, legacy `HUMAN.md`, default mdkg usage skills, `events.jsonl`, registry, and configured skill mirrors
122
122
  - run `mdkg index` after fresh init before treating `mdkg doctor --strict --json` as a clean health gate; init writes source scaffold files and index writes generated caches
123
123
 
124
124
  ### `mdkg upgrade`
@@ -443,7 +443,9 @@ JSON receipt:
443
443
  - `{ action: "synced", sync: { synced, pruned, targets } }`
444
444
 
445
445
  Notes:
446
- - syncs canonical `.mdkg/skills/` into `.agents/skills/` and `.claude/skills/`
446
+ - syncs canonical `.mdkg/skills/` into configured
447
+ `customization.skill_mirrors.targets` paths from `.mdkg/config.json`
448
+ - defaults are `.agents/skills` and `.claude/skills`
447
449
  - preserves unrelated existing folders
448
450
  - same-slug collisions fail unless forced
449
451
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ mdkg stays deliberately boring:
14
14
  - first-class rebuildable SQLite cache through built-in `node:sqlite`
15
15
  - no daemon, hosted index, or vector DB
16
16
 
17
- Current package version in source: `0.3.8`
17
+ Current package version in source: `0.3.9`
18
18
 
19
19
  mdkg is still pre-v1 public alpha software. The public package is usable, but graph, cache, bundle, and DAL contracts may continue to change quickly while the project converges on a stable v1 surface.
20
20
 
@@ -82,7 +82,7 @@ mdkg init --agent
82
82
  mdkg index
83
83
  ```
84
84
 
85
- This is the canonical AI-agent bootstrap path. It creates `.mdkg/`, `AGENT_START.md`, `AGENTS.md`, `CLAUDE.md`, `llms.txt`, `CLI_COMMAND_MATRIX.md`, strict-node `SOUL.md` / `HUMAN.md`, the three default mdkg usage skills, `events.jsonl`, the skill registry, core pin updates, and mirrored skill folders under `.agents/skills/` and `.claude/skills/`. It also updates `.gitignore` / `.npmignore` by default. Use `--no-update-ignores` to opt out of those ignore-file updates.
85
+ This is the canonical AI-agent bootstrap path. It creates `.mdkg/`, `AGENT_START.md`, `AGENTS.md`, `CLAUDE.md`, `llms.txt`, `CLI_COMMAND_MATRIX.md`, strict-node `SOUL.md` / `COLLABORATION.md`, legacy `HUMAN.md` for one release, the default mdkg usage skills, `events.jsonl`, the skill registry, core pin updates, and configured skill mirrors under `.agents/skills/` and `.claude/skills/` by default. It also updates `.gitignore` / `.npmignore` by default. Use `--no-update-ignores` to opt out of those ignore-file updates.
86
86
 
87
87
  Run `mdkg index` after a fresh init before using `mdkg status --json` or
88
88
  `mdkg doctor --strict --json` as health gates. Init writes source scaffold
package/dist/cli.js CHANGED
@@ -414,7 +414,8 @@ function printSkillHelp(log, subcommand) {
414
414
  log("Usage:");
415
415
  log(" mdkg skill sync [--force] [--json]");
416
416
  log("\nWhen to use:");
417
- log(" Rebuild .agents/skills and .claude/skills from canonical .mdkg/skills.");
417
+ log(" Rebuild configured skill mirror targets from canonical .mdkg/skills.");
418
+ log(" Defaults are .agents/skills and .claude/skills; add custom targets in .mdkg/config.json.");
418
419
  printGlobalOptions(log);
419
420
  return;
420
421
  default:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "tool": "mdkg",
4
- "package_version": "0.3.8",
4
+ "package_version": "0.3.9",
5
5
  "source": {
6
6
  "help_targets": "scripts/cli_help_targets.js",
7
7
  "command_matrix": "CLI_COMMAND_MATRIX.md"
@@ -9128,5 +9128,5 @@
9128
9128
  }
9129
9129
  }
9130
9130
  ],
9131
- "contract_hash": "145781176fcd00d6b7c7edd8e013e902acea2ace8764dbf0bb063a8d3913a3e1"
9131
+ "contract_hash": "3c13c572c740f1e40db33fa5867fa227a8347df0750ef3beb0696d5931761a6c"
9132
9132
  }
@@ -17,6 +17,7 @@ const skill_support_1 = require("./skill_support");
17
17
  const skill_mirror_1 = require("./skill_mirror");
18
18
  const DEFAULT_SEED_SUBDIR = path_1.default.resolve(__dirname, "..", "init");
19
19
  const SOUL_PIN_ID = "rule-soul";
20
+ const COLLABORATION_PIN_ID = "rule-7";
20
21
  const HUMAN_PIN_ID = "rule-human";
21
22
  const DEFAULT_CORE_LIST_HEADER = [
22
23
  "# mdkg verbose core list",
@@ -175,6 +176,52 @@ function humanTemplate(created) {
175
176
  "",
176
177
  ].join("\n");
177
178
  }
179
+ function collaborationTemplate(created) {
180
+ return [
181
+ "---",
182
+ `id: ${COLLABORATION_PIN_ID}`,
183
+ "type: rule",
184
+ "title: collaboration profile and operator preferences",
185
+ "tags: [collaboration, preferences, operator]",
186
+ "owners: []",
187
+ "links: []",
188
+ "artifacts: []",
189
+ "relates: [rule-human]",
190
+ "refs: [dec-53]",
191
+ "aliases: [collaboration, operator-profile]",
192
+ `created: ${created}`,
193
+ `updated: ${created}`,
194
+ "---",
195
+ "",
196
+ "# Purpose",
197
+ "",
198
+ "Capture stable collaboration preferences, operating boundaries, and repo-specific human expectations so agents can work with less ambiguity.",
199
+ "",
200
+ "# Scope",
201
+ "",
202
+ "Applies to planning, implementation, review, release, and handoff interactions in this repository.",
203
+ "",
204
+ "# Compatibility",
205
+ "",
206
+ "`COLLABORATION.md` is canonical. `HUMAN.md` remains a one-release legacy alias for repos and agent prompts that still reference it; read `COLLABORATION.md` first and then use `HUMAN.md` only for compatibility notes not yet migrated.",
207
+ "",
208
+ "# Requirements",
209
+ "",
210
+ "- Keep top goals, boundaries, and style preferences current.",
211
+ "- Include ask-before-doing constraints for risky or high-impact actions.",
212
+ "- Record preferred environment assumptions and validation commands.",
213
+ "- Preserve local operator customizations during `mdkg upgrade --apply`.",
214
+ "",
215
+ "# Notes",
216
+ "",
217
+ "Suggested prompts:",
218
+ "- What are your top 3 goals in this repo right now?",
219
+ "- What should never happen without confirmation?",
220
+ "- What coding/review style should the agent prefer?",
221
+ "- What OS/runtime/test commands should be assumed?",
222
+ "",
223
+ ].join("\n");
224
+ }
178
225
  function seededInitEvent(nowIso) {
179
226
  const event = {
180
227
  ts: nowIso,
@@ -277,6 +324,31 @@ function ensureCorePins(coreListPath, requiredPins) {
277
324
  fs_1.default.mkdirSync(path_1.default.dirname(coreListPath), { recursive: true });
278
325
  fs_1.default.writeFileSync(coreListPath, output, "utf8");
279
326
  }
327
+ function refreshManifestHash(root, manifest, relPath) {
328
+ const entry = manifest.files.find((file) => file.path === relPath);
329
+ if (!entry) {
330
+ return;
331
+ }
332
+ const targetPath = path_1.default.join(root, relPath);
333
+ if (fs_1.default.existsSync(targetPath) && fs_1.default.statSync(targetPath).isFile()) {
334
+ entry.sha256 = (0, init_manifest_1.sha256File)(targetPath);
335
+ }
336
+ }
337
+ function ensureCreatedCoreManifestEntry(root, manifest, relPath, stats) {
338
+ if (manifest.files.some((file) => file.path === relPath) || !stats.createdPaths.includes(relPath)) {
339
+ return;
340
+ }
341
+ const targetPath = path_1.default.join(root, relPath);
342
+ if (!fs_1.default.existsSync(targetPath) || !fs_1.default.statSync(targetPath).isFile()) {
343
+ return;
344
+ }
345
+ manifest.files.push({
346
+ path: relPath,
347
+ category: "core",
348
+ sha256: (0, init_manifest_1.sha256File)(targetPath),
349
+ });
350
+ manifest.files.sort((a, b) => a.path.localeCompare(b.path));
351
+ }
280
352
  function runInitCommand(options) {
281
353
  const root = path_1.default.resolve(options.root);
282
354
  const seedRoot = options.seedRoot ? path_1.default.resolve(options.seedRoot) : DEFAULT_SEED_SUBDIR;
@@ -295,6 +367,7 @@ function runInitCommand(options) {
295
367
  const seedReadme = path_1.default.join(seedRoot, "README.md");
296
368
  const seedDefaultSkills = path_1.default.join(seedRoot, "skills", "default");
297
369
  const seedSoul = path_1.default.join(seedCore, "SOUL.md");
370
+ const seedCollaboration = path_1.default.join(seedCore, "COLLABORATION.md");
298
371
  const seedHuman = path_1.default.join(seedCore, "HUMAN.md");
299
372
  const seedManifest = (0, init_manifest_1.createInitManifest)(seedRoot, (0, version_1.readPackageVersion)(), {
300
373
  includeAgentDocs: Boolean(options.agent),
@@ -367,6 +440,7 @@ function runInitCommand(options) {
367
440
  if (options.agent) {
368
441
  const today = (0, date_1.formatDate)(new Date());
369
442
  const soulPath = path_1.default.join(mdkgDir, "core", "SOUL.md");
443
+ const collaborationPath = path_1.default.join(mdkgDir, "core", "COLLABORATION.md");
370
444
  const humanPath = path_1.default.join(mdkgDir, "core", "HUMAN.md");
371
445
  const skillsDir = path_1.default.join(mdkgDir, "skills");
372
446
  const registryPath = path_1.default.join(skillsDir, "registry.md");
@@ -378,6 +452,9 @@ function runInitCommand(options) {
378
452
  if (!fs_1.default.existsSync(seedSoul)) {
379
453
  writeFileIfMissing(root, soulPath, soulTemplate(today), force, stats);
380
454
  }
455
+ if (!fs_1.default.existsSync(seedCollaboration)) {
456
+ writeFileIfMissing(root, collaborationPath, collaborationTemplate(today), force, stats);
457
+ }
381
458
  if (!fs_1.default.existsSync(seedHuman)) {
382
459
  writeFileIfMissing(root, humanPath, humanTemplate(today), force, stats);
383
460
  }
@@ -386,9 +463,13 @@ function runInitCommand(options) {
386
463
  writeFileIfMissing(root, eventsPath, seededInitEvent(new Date().toISOString()), force, stats);
387
464
  }
388
465
  const coreListPath = path_1.default.join(mdkgDir, "core", "core.md");
389
- ensureCorePins(coreListPath, [SOUL_PIN_ID, HUMAN_PIN_ID]);
390
- (0, skill_mirror_1.scaffoldMirrorRoots)(root);
466
+ ensureCorePins(coreListPath, [SOUL_PIN_ID, COLLABORATION_PIN_ID, HUMAN_PIN_ID]);
467
+ refreshManifestHash(root, seedManifest, ".mdkg/core/core.md");
468
+ ensureCreatedCoreManifestEntry(root, seedManifest, ".mdkg/core/SOUL.md", stats);
469
+ ensureCreatedCoreManifestEntry(root, seedManifest, ".mdkg/core/COLLABORATION.md", stats);
470
+ ensureCreatedCoreManifestEntry(root, seedManifest, ".mdkg/core/HUMAN.md", stats);
391
471
  const config = (0, config_1.loadConfig)(root);
472
+ (0, skill_mirror_1.scaffoldMirrorRoots)(root, config);
392
473
  (0, skill_support_1.refreshSkillsRegistry)(root, config);
393
474
  stats.registryRefreshed = true;
394
475
  const mirrorResult = (0, skill_mirror_1.syncSkillMirrors)({ root, config, createRoots: true, force });
@@ -452,7 +533,7 @@ function runInitCommand(options) {
452
533
  }
453
534
  if (options.agent) {
454
535
  console.log("agent bootstrap: AGENT_START.md, AGENTS.md, CLAUDE.md, llms.txt, CLI_COMMAND_MATRIX.md");
455
- console.log("agent core pins: rule-soul, rule-human");
536
+ console.log("agent core pins: rule-soul, rule-7, rule-human");
456
537
  console.log("agent event log: .mdkg/work/events/events.jsonl");
457
538
  console.log(`skill mirrors: ${stats.mirroredSkills} sync operation(s) across ${stats.mirrorTargets} target(s)`);
458
539
  if (stats.registryRefreshed) {
@@ -186,7 +186,7 @@ function runSkillNewCommandLocked(options) {
186
186
  (0, atomic_1.atomicWriteFile)(canonicalPath, content);
187
187
  (0, skill_support_1.ensureSkillsRegistry)(root, config);
188
188
  (0, skill_support_1.refreshSkillsRegistry)(root, config);
189
- if ((0, skill_mirror_1.shouldMaintainSkillMirrors)(root)) {
189
+ if ((0, skill_mirror_1.shouldMaintainSkillMirrors)(root, config)) {
190
190
  (0, skill_mirror_1.syncSkillMirrors)({ root, config, createRoots: true, force });
191
191
  }
192
192
  if (config.index.auto_reindex) {
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configuredSkillMirrorTargets = configuredSkillMirrorTargets;
6
7
  exports.syncSkillMirrors = syncSkillMirrors;
7
8
  exports.preflightSkillMirrorTargets = preflightSkillMirrorTargets;
8
9
  exports.shouldMaintainSkillMirrors = shouldMaintainSkillMirrors;
@@ -10,22 +11,26 @@ exports.auditSkillMirrors = auditSkillMirrors;
10
11
  exports.scaffoldMirrorRoots = scaffoldMirrorRoots;
11
12
  const fs_1 = __importDefault(require("fs"));
12
13
  const path_1 = __importDefault(require("path"));
14
+ const config_1 = require("../core/config");
13
15
  const skills_indexer_1 = require("../graph/skills_indexer");
14
16
  const errors_1 = require("../util/errors");
15
- const MIRROR_PRODUCTS = ["agents", "claude"];
16
17
  const MANIFEST_FILE = ".mdkg-managed.json";
17
18
  const MANAGED_ROOT_MARKERS = [
18
19
  path_1.default.join(".mdkg", "core", "SOUL.md"),
20
+ path_1.default.join(".mdkg", "core", "COLLABORATION.md"),
19
21
  path_1.default.join(".mdkg", "core", "HUMAN.md"),
20
22
  ];
21
23
  const ALLOWED_ROOT_ENTRIES = ["SKILL.md", "references", "assets", "scripts"];
22
- function resolveMirrorTargets(root) {
23
- return MIRROR_PRODUCTS.map((product) => {
24
- const rootDir = path_1.default.join(root, `.${product}`);
25
- const skillsRoot = path_1.default.join(rootDir, "skills");
24
+ function configuredSkillMirrorTargets(config) {
25
+ const configured = config?.customization.skill_mirrors.targets ?? (0, config_1.defaultCustomizationConfig)().skill_mirrors.targets;
26
+ return Array.from(new Set(configured.map((value) => value.trim()).filter(Boolean)));
27
+ }
28
+ function resolveMirrorTargets(root, config) {
29
+ return configuredSkillMirrorTargets(config).map((configuredPath) => {
30
+ const skillsRoot = path_1.default.join(root, configuredPath);
26
31
  return {
27
- product,
28
- rootDir,
32
+ configuredPath,
33
+ rootDir: path_1.default.dirname(skillsRoot),
29
34
  skillsRoot,
30
35
  manifestPath: path_1.default.join(skillsRoot, MANIFEST_FILE),
31
36
  };
@@ -55,11 +60,11 @@ function writeManifest(target, managed) {
55
60
  fs_1.default.mkdirSync(target.skillsRoot, { recursive: true });
56
61
  fs_1.default.writeFileSync(target.manifestPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
57
62
  }
58
- function shouldCreateMirrorRoots(root) {
63
+ function shouldCreateMirrorRoots(root, config) {
59
64
  if (MANAGED_ROOT_MARKERS.some((relPath) => fs_1.default.existsSync(path_1.default.join(root, relPath)))) {
60
65
  return true;
61
66
  }
62
- return resolveMirrorTargets(root).some((target) => fs_1.default.existsSync(target.rootDir) || fs_1.default.existsSync(target.skillsRoot));
67
+ return resolveMirrorTargets(root, config).some((target) => fs_1.default.existsSync(target.rootDir) || fs_1.default.existsSync(target.skillsRoot));
63
68
  }
64
69
  function listAllowedEntries(dirPath) {
65
70
  if (!fs_1.default.existsSync(dirPath)) {
@@ -235,7 +240,7 @@ function syncSkillMirrors(options) {
235
240
  const sources = loadCanonicalSources(options.root, options.config);
236
241
  const createRoots = Boolean(options.createRoots);
237
242
  const force = Boolean(options.force);
238
- const targets = resolveMirrorTargets(options.root);
243
+ const targets = resolveMirrorTargets(options.root, options.config);
239
244
  let synced = 0;
240
245
  let pruned = 0;
241
246
  let touchedTargets = 0;
@@ -280,7 +285,7 @@ function preflightSkillMirrorTargets(options) {
280
285
  if (slugs.length === 0) {
281
286
  return;
282
287
  }
283
- for (const target of resolveMirrorTargets(options.root)) {
288
+ for (const target of resolveMirrorTargets(options.root, options.config)) {
284
289
  if (!fs_1.default.existsSync(target.rootDir) && !fs_1.default.existsSync(target.skillsRoot)) {
285
290
  continue;
286
291
  }
@@ -293,18 +298,18 @@ function preflightSkillMirrorTargets(options) {
293
298
  }
294
299
  }
295
300
  }
296
- function shouldMaintainSkillMirrors(root) {
297
- return shouldCreateMirrorRoots(root);
301
+ function shouldMaintainSkillMirrors(root, config) {
302
+ return shouldCreateMirrorRoots(root, config);
298
303
  }
299
304
  function auditSkillMirrors(root, config) {
300
- const shouldAudit = shouldCreateMirrorRoots(root);
305
+ const shouldAudit = shouldCreateMirrorRoots(root, config);
301
306
  if (!shouldAudit) {
302
307
  return [];
303
308
  }
304
309
  const warnings = [];
305
310
  const sources = loadCanonicalSources(root, config);
306
311
  const sourceBySlug = new Map(sources.map((source) => [source.slug, source]));
307
- for (const target of resolveMirrorTargets(root)) {
312
+ for (const target of resolveMirrorTargets(root, config)) {
308
313
  if (!fs_1.default.existsSync(target.skillsRoot)) {
309
314
  warnings.push(`${path_1.default.relative(root, target.skillsRoot)}: mirror root missing; run \`mdkg skill sync\``);
310
315
  continue;
@@ -335,8 +340,8 @@ function auditSkillMirrors(root, config) {
335
340
  }
336
341
  return warnings;
337
342
  }
338
- function scaffoldMirrorRoots(root) {
339
- for (const target of resolveMirrorTargets(root)) {
343
+ function scaffoldMirrorRoots(root, config) {
344
+ for (const target of resolveMirrorTargets(root, config)) {
340
345
  fs_1.default.mkdirSync(target.skillsRoot, { recursive: true });
341
346
  if (!fs_1.default.existsSync(target.manifestPath)) {
342
347
  writeManifest(target, []);
@@ -52,7 +52,7 @@ function registryTemplate() {
52
52
  "This directory stores Agent Skills packages used by mdkg tooling and orchestrators.",
53
53
  "",
54
54
  "Use `mdkg skill new <slug> \"<name>\" --description \"...\"` to scaffold a new skill from the built-in Anthropic-aligned template.",
55
- "Use `mdkg skill sync` to mirror canonical skills into `.agents/skills/` and `.claude/skills/` when agent bootstrap is enabled.",
55
+ "Use `mdkg skill sync` to mirror canonical skills into configured `.mdkg/config.json` targets; defaults are `.agents/skills/` and `.claude/skills/`.",
56
56
  "Use `CLI_COMMAND_MATRIX.md` as the canonical command and flag reference when updating skill procedures.",
57
57
  "",
58
58
  "## Conventions",
@@ -21,7 +21,7 @@ const init_manifest_1 = require("./init_manifest");
21
21
  const skill_support_1 = require("./skill_support");
22
22
  const skill_mirror_1 = require("./skill_mirror");
23
23
  const DEFAULT_SEED_SUBDIR = path_1.default.resolve(__dirname, "..", "init");
24
- const PROTECTED_CORE_DOCS = new Set([".mdkg/core/SOUL.md", ".mdkg/core/HUMAN.md"]);
24
+ const PROTECTED_CORE_DOCS = new Set([".mdkg/core/SOUL.md", ".mdkg/core/COLLABORATION.md", ".mdkg/core/HUMAN.md"]);
25
25
  const CREATE_ONLY_PRESERVED = new Set([".mdkg/core/core.md"]);
26
26
  const LOCAL_STATE_IGNORE_ENTRIES = [
27
27
  ".mdkg/state/",
@@ -51,12 +51,11 @@ function requireSeedAssets(seedRoot) {
51
51
  }
52
52
  }
53
53
  }
54
- function isAgentWorkspace(root) {
54
+ function isAgentWorkspace(root, config) {
55
55
  return [
56
56
  path_1.default.join(root, ".mdkg", "skills"),
57
- path_1.default.join(root, ".agents", "skills"),
58
- path_1.default.join(root, ".claude", "skills"),
59
57
  path_1.default.join(root, ".mdkg", "work", "events", "events.jsonl"),
58
+ ...(0, skill_mirror_1.configuredSkillMirrorTargets)(config).map((target) => path_1.default.join(root, target)),
60
59
  ].some((candidate) => fs_1.default.existsSync(candidate));
61
60
  }
62
61
  function copyFile(src, dest) {
@@ -157,15 +156,6 @@ function planSeedFile(options) {
157
156
  options.summary.unchanged += 1;
158
157
  return false;
159
158
  }
160
- if (PROTECTED_CORE_DOCS.has(options.file.path)) {
161
- record(options.summary, options.changes, {
162
- path: options.file.path,
163
- category: options.file.category,
164
- action: "conflict",
165
- reason: "protected core document exists; local content preserved",
166
- });
167
- return false;
168
- }
169
159
  const known = options.knownHashes.get(options.file.path);
170
160
  if (known?.has(currentHash)) {
171
161
  record(options.summary, options.changes, {
@@ -180,6 +170,15 @@ function planSeedFile(options) {
180
170
  options.managedCurrentFiles.push(options.file);
181
171
  return true;
182
172
  }
173
+ if (PROTECTED_CORE_DOCS.has(options.file.path)) {
174
+ record(options.summary, options.changes, {
175
+ path: options.file.path,
176
+ category: options.file.category,
177
+ action: "conflict",
178
+ reason: "protected core document exists; local content preserved",
179
+ });
180
+ return false;
181
+ }
183
182
  record(options.summary, options.changes, {
184
183
  path: options.file.path,
185
184
  category: options.file.category,
@@ -253,14 +252,26 @@ function migrateProjectDbConfig(input) {
253
252
  };
254
253
  return { config: raw, changed: true };
255
254
  }
255
+ function migrateCustomizationConfig(input) {
256
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
257
+ return { config: input, changed: false };
258
+ }
259
+ const raw = { ...input };
260
+ if (raw.customization !== undefined) {
261
+ return { config: raw, changed: false };
262
+ }
263
+ raw.customization = (0, config_1.defaultCustomizationConfig)();
264
+ return { config: raw, changed: true };
265
+ }
256
266
  function migrateConfigIfNeeded(root, dryRun, summary, changes) {
257
267
  const cfgPath = (0, paths_1.configPath)(root);
258
268
  const raw = JSON.parse(fs_1.default.readFileSync(cfgPath, "utf8"));
259
269
  const migrated = (0, migrate_1.migrateConfig)(raw);
260
270
  const bundleConfig = migrateLegacyBundleImportsConfig(migrated.config);
261
271
  const nextConfig = migrateProjectDbConfig(bundleConfig.config);
262
- (0, config_1.validateConfigSchema)(nextConfig.config);
263
- if (migrated.from === migrated.to && !bundleConfig.changed && !nextConfig.changed) {
272
+ const customizationConfig = migrateCustomizationConfig(nextConfig.config);
273
+ (0, config_1.validateConfigSchema)(customizationConfig.config);
274
+ if (migrated.from === migrated.to && !bundleConfig.changed && !nextConfig.changed && !customizationConfig.changed) {
264
275
  summary.unchanged += 1;
265
276
  return;
266
277
  }
@@ -271,6 +282,9 @@ function migrateConfigIfNeeded(root, dryRun, summary, changes) {
271
282
  if (nextConfig.changed) {
272
283
  reasons.push("project db config defaults");
273
284
  }
285
+ if (customizationConfig.changed) {
286
+ reasons.push("customization overlay defaults");
287
+ }
274
288
  if (migrated.from !== migrated.to) {
275
289
  reasons.push(`schema_version ${migrated.from} -> ${migrated.to}`);
276
290
  }
@@ -281,9 +295,32 @@ function migrateConfigIfNeeded(root, dryRun, summary, changes) {
281
295
  reason: reasons.join(" and "),
282
296
  });
283
297
  if (!dryRun) {
284
- writeFile(cfgPath, `${JSON.stringify(nextConfig.config, null, 2)}\n`);
298
+ writeFile(cfgPath, `${JSON.stringify(customizationConfig.config, null, 2)}\n`);
285
299
  }
286
300
  }
301
+ function sameStringArray(left, right) {
302
+ return left.length === right.length && left.every((value, index) => value === right[index]);
303
+ }
304
+ function hasOperatorCustomization(customization) {
305
+ const defaults = (0, config_1.defaultCustomizationConfig)();
306
+ return (customization.standards.profile !== defaults.standards.profile ||
307
+ customization.standards.refs.length > 0 ||
308
+ customization.core_docs.custom_paths.length > 0 ||
309
+ !sameStringArray(customization.skill_mirrors.targets, defaults.skill_mirrors.targets));
310
+ }
311
+ function reportPreservedCustomizationOverlay(root, summary, changes) {
312
+ const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
313
+ if (!hasOperatorCustomization(config.customization)) {
314
+ summary.unchanged += 1;
315
+ return;
316
+ }
317
+ record(summary, changes, {
318
+ path: ".mdkg/config.json",
319
+ category: "customization_overlay",
320
+ action: "skip",
321
+ reason: "operator customization overlay is preserved; upgrade does not replace organization standards, custom core docs, or configured skill mirror targets",
322
+ });
323
+ }
287
324
  function migrateClosedGoalActiveNodes(root, dryRun, summary, changes) {
288
325
  const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
289
326
  const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(root, config);
@@ -503,12 +540,15 @@ function buildApplySideEffects(options) {
503
540
  category: "skill_registry",
504
541
  action: "update",
505
542
  reason: "refreshes canonical skill registry after apply",
506
- }, {
507
- path: ".agents/skills,.claude/skills",
508
- category: "skill_mirror",
509
- action: "sync",
510
- reason: "syncs managed skill mirrors after apply",
511
543
  });
544
+ if (options.mirrorTargets.length > 0) {
545
+ effects.push({
546
+ path: options.mirrorTargets.join(","),
547
+ category: "skill_mirror",
548
+ action: "sync",
549
+ reason: "syncs configured managed skill mirrors after apply",
550
+ });
551
+ }
512
552
  }
513
553
  return effects;
514
554
  }
@@ -560,9 +600,11 @@ function runUpgradeCommand(options) {
560
600
  const knownHashes = buildKnownHashes([existingManifest, ...legacyManifests]);
561
601
  const summary = createSummary();
562
602
  const changes = [];
563
- const agentWorkspace = isAgentWorkspace(root);
603
+ const initialConfig = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
604
+ const agentWorkspace = isAgentWorkspace(root, initialConfig);
564
605
  const managedCurrentFiles = [];
565
606
  migrateConfigIfNeeded(root, dryRun, summary, changes);
607
+ reportPreservedCustomizationOverlay(root, summary, changes);
566
608
  migrateClosedGoalActiveNodes(root, dryRun, summary, changes);
567
609
  migrateLegacySpecManifests(root, dryRun, summary, changes);
568
610
  for (const file of currentManifest.files) {
@@ -587,6 +629,7 @@ function runUpgradeCommand(options) {
587
629
  const applySideEffects = buildApplySideEffects({
588
630
  existingManifest,
589
631
  agentWorkspace,
632
+ mirrorTargets: (0, skill_mirror_1.configuredSkillMirrorTargets)(initialConfig),
590
633
  changes,
591
634
  });
592
635
  for (const effect of applySideEffects) {
@@ -603,11 +646,11 @@ function runUpgradeCommand(options) {
603
646
  if (agentWorkspace && applySideEffects.length > 0) {
604
647
  const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
605
648
  (0, skill_support_1.refreshSkillsRegistry)(root, config);
606
- (0, skill_mirror_1.scaffoldMirrorRoots)(root);
649
+ (0, skill_mirror_1.scaffoldMirrorRoots)(root, config);
607
650
  (0, skill_mirror_1.syncSkillMirrors)({ root, config, createRoots: true });
608
651
  }
609
652
  }
610
- const preservedCustomizations = changes.filter((change) => change.action === "conflict");
653
+ const preservedCustomizations = changes.filter((change) => change.action === "conflict" || change.category === "customization_overlay");
611
654
  const blockingConflicts = changes.filter((change) => change.action === "conflict" && change.category === "manifest_migration");
612
655
  const willWritePaths = changes.filter(isWritableChange).map(writablePathForChange);
613
656
  const receipt = {