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,243 @@
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.runBundleImportAddCommand = runBundleImportAddCommand;
7
+ exports.runBundleImportListCommand = runBundleImportListCommand;
8
+ exports.runBundleImportRemoveCommand = runBundleImportRemoveCommand;
9
+ exports.runBundleImportEnableCommand = runBundleImportEnableCommand;
10
+ exports.runBundleImportDisableCommand = runBundleImportDisableCommand;
11
+ exports.runBundleImportVerifyCommand = runBundleImportVerifyCommand;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const config_1 = require("../core/config");
15
+ const migrate_1 = require("../core/migrate");
16
+ const workspace_path_1 = require("../core/workspace_path");
17
+ const bundle_imports_1 = require("../graph/bundle_imports");
18
+ const errors_1 = require("../util/errors");
19
+ const ALIAS_RE = /^[a-z][a-z0-9_]*$/;
20
+ function writeJson(value) {
21
+ console.log(JSON.stringify(value, null, 2));
22
+ }
23
+ function normalizeAlias(alias) {
24
+ if (alias === "all") {
25
+ throw new errors_1.UsageError("bundle import alias cannot be 'all'");
26
+ }
27
+ if (alias !== alias.toLowerCase() || !ALIAS_RE.test(alias)) {
28
+ throw new errors_1.UsageError("bundle import alias must be lowercase and use [a-z0-9_]");
29
+ }
30
+ return alias;
31
+ }
32
+ function normalizeVisibility(value) {
33
+ const normalized = (value ?? "private").toLowerCase();
34
+ if (normalized === "private" || normalized === "internal" || normalized === "public") {
35
+ return normalized;
36
+ }
37
+ throw new errors_1.UsageError("--visibility must be private, internal, or public");
38
+ }
39
+ function normalizeProfile(value) {
40
+ const normalized = (value ?? "private").toLowerCase();
41
+ if (normalized === "private" || normalized === "public") {
42
+ return normalized;
43
+ }
44
+ throw new errors_1.UsageError("--profile must be private or public");
45
+ }
46
+ function normalizeContained(value, label) {
47
+ try {
48
+ return (0, workspace_path_1.normalizeContainedWorkspacePath)(value, label);
49
+ }
50
+ catch (err) {
51
+ throw new errors_1.UsageError(err instanceof Error ? err.message : String(err));
52
+ }
53
+ }
54
+ function readRawConfig(root) {
55
+ const configPath = path_1.default.join(root, ".mdkg", "config.json");
56
+ if (!fs_1.default.existsSync(configPath)) {
57
+ throw new errors_1.NotFoundError(`config not found at ${configPath}`);
58
+ }
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
62
+ }
63
+ catch (err) {
64
+ throw new errors_1.UsageError(`failed to read config: ${err instanceof Error ? err.message : String(err)}`);
65
+ }
66
+ const migrated = (0, migrate_1.migrateConfig)(parsed).config;
67
+ (0, config_1.validateConfigSchema)(migrated);
68
+ if (typeof migrated !== "object" || migrated === null || Array.isArray(migrated)) {
69
+ throw new errors_1.UsageError("config must be a JSON object");
70
+ }
71
+ return { configPath, raw: migrated };
72
+ }
73
+ function writeRawConfig(configPath, raw) {
74
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf8");
75
+ }
76
+ function getImports(raw) {
77
+ const imports = raw.bundle_imports;
78
+ if (imports === undefined) {
79
+ raw.bundle_imports = {};
80
+ return raw.bundle_imports;
81
+ }
82
+ if (typeof imports !== "object" || imports === null || Array.isArray(imports)) {
83
+ throw new errors_1.UsageError("config.bundle_imports must be an object");
84
+ }
85
+ return imports;
86
+ }
87
+ function receiptForHealth(action, health) {
88
+ return {
89
+ action,
90
+ import: health,
91
+ };
92
+ }
93
+ function healthByAlias(root, alias) {
94
+ const config = (0, config_1.loadConfig)(root);
95
+ const health = (0, bundle_imports_1.buildBundleImportsIndex)(root, config).index.imports.find((item) => item.alias === alias);
96
+ if (!health) {
97
+ throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
98
+ }
99
+ return health;
100
+ }
101
+ function runBundleImportAddCommand(options) {
102
+ const alias = normalizeAlias(options.alias);
103
+ const bundlePath = normalizeContained(options.bundlePath, "bundle import path");
104
+ const visibility = normalizeVisibility(options.visibility);
105
+ const expected_profile = normalizeProfile(options.profile);
106
+ if (visibility !== "private" && expected_profile !== "public") {
107
+ throw new errors_1.UsageError("--profile public is required when --visibility is public or internal");
108
+ }
109
+ const source_path = options.sourcePath
110
+ ? normalizeContained(options.sourcePath, "bundle import source path")
111
+ : undefined;
112
+ if (options.maxStaleSeconds !== undefined && (!Number.isInteger(options.maxStaleSeconds) || options.maxStaleSeconds <= 0)) {
113
+ throw new errors_1.UsageError("--max-stale-seconds must be a positive integer");
114
+ }
115
+ const { configPath, raw } = readRawConfig(options.root);
116
+ const imports = getImports(raw);
117
+ if (imports[alias]) {
118
+ throw new errors_1.UsageError(`bundle import already exists: ${alias}`);
119
+ }
120
+ const workspaces = raw.workspaces;
121
+ if (workspaces && workspaces[alias]) {
122
+ throw new errors_1.UsageError(`bundle import alias collides with workspace: ${alias}`);
123
+ }
124
+ imports[alias] = {
125
+ path: bundlePath,
126
+ enabled: true,
127
+ visibility,
128
+ expected_profile,
129
+ ...(source_path ? { source_path } : {}),
130
+ ...(options.sourceRepo ? { source_repo: options.sourceRepo } : {}),
131
+ ...(options.maxStaleSeconds !== undefined ? { max_stale_seconds: options.maxStaleSeconds } : {}),
132
+ };
133
+ raw.bundle_imports = imports;
134
+ const validated = (0, config_1.validateConfigSchema)(raw);
135
+ const health = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, validated).index.imports.find((item) => item.alias === alias);
136
+ if (!health) {
137
+ throw new errors_1.NotFoundError(`bundle import not found after validation: ${alias}`);
138
+ }
139
+ if (health.error_count > 0) {
140
+ throw new errors_1.ValidationError(`bundle import ${alias} is invalid:\n${health.errors.join("\n")}`);
141
+ }
142
+ writeRawConfig(configPath, raw);
143
+ const receipt = receiptForHealth("added", health);
144
+ if (options.json) {
145
+ writeJson(receipt);
146
+ return;
147
+ }
148
+ console.log(`bundle import added: ${alias} (${bundlePath})`);
149
+ if (health.warning_count > 0) {
150
+ console.log(`warnings: ${health.warning_count}`);
151
+ }
152
+ }
153
+ function runBundleImportListCommand(options) {
154
+ const config = (0, config_1.loadConfig)(options.root);
155
+ const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports;
156
+ if (options.json) {
157
+ writeJson({ action: "list", count: imports.length, imports });
158
+ return;
159
+ }
160
+ if (imports.length === 0) {
161
+ console.log("no bundle imports configured");
162
+ return;
163
+ }
164
+ for (const item of imports) {
165
+ const status = item.enabled ? item.error_count > 0 ? "invalid" : item.stale ? "stale" : "ok" : "disabled";
166
+ console.log(`${item.alias} | ${status} | ${item.visibility} | ${item.path}`);
167
+ }
168
+ }
169
+ function runBundleImportRemoveCommand(options) {
170
+ const alias = normalizeAlias(options.alias);
171
+ const { configPath, raw } = readRawConfig(options.root);
172
+ const imports = getImports(raw);
173
+ const existing = imports[alias];
174
+ if (!existing) {
175
+ throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
176
+ }
177
+ delete imports[alias];
178
+ raw.bundle_imports = imports;
179
+ (0, config_1.validateConfigSchema)(raw);
180
+ writeRawConfig(configPath, raw);
181
+ const receipt = { action: "removed", import: { alias } };
182
+ if (options.json) {
183
+ writeJson(receipt);
184
+ return;
185
+ }
186
+ console.log(`bundle import removed: ${alias}`);
187
+ }
188
+ function setBundleImportEnabled(options, enabled) {
189
+ const alias = normalizeAlias(options.alias);
190
+ const { configPath, raw } = readRawConfig(options.root);
191
+ const imports = getImports(raw);
192
+ const existing = imports[alias];
193
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
194
+ throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
195
+ }
196
+ imports[alias] = { ...existing, enabled };
197
+ raw.bundle_imports = imports;
198
+ (0, config_1.validateConfigSchema)(raw);
199
+ writeRawConfig(configPath, raw);
200
+ const health = healthByAlias(options.root, alias);
201
+ const receipt = receiptForHealth(enabled ? "enabled" : "disabled", health);
202
+ if (options.json) {
203
+ writeJson(receipt);
204
+ return;
205
+ }
206
+ console.log(`bundle import ${enabled ? "enabled" : "disabled"}: ${alias}`);
207
+ }
208
+ function runBundleImportEnableCommand(options) {
209
+ setBundleImportEnabled(options, true);
210
+ }
211
+ function runBundleImportDisableCommand(options) {
212
+ setBundleImportEnabled(options, false);
213
+ }
214
+ function runBundleImportVerifyCommand(options) {
215
+ const config = (0, config_1.loadConfig)(options.root);
216
+ const all = options.all || !options.alias;
217
+ const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports.filter((item) => all ? true : item.alias === options.alias);
218
+ if (!all && imports.length === 0) {
219
+ throw new errors_1.NotFoundError(`bundle import not found: ${options.alias}`);
220
+ }
221
+ const ok = imports.every((item) => item.error_count === 0 && !item.stale);
222
+ const receipt = { action: "verified", ok, count: imports.length, imports };
223
+ if (options.json) {
224
+ writeJson(receipt);
225
+ }
226
+ else if (ok) {
227
+ console.log(`bundle imports verified: ${imports.length}`);
228
+ }
229
+ else {
230
+ console.log(`bundle import verify failed: ${imports.length}`);
231
+ for (const item of imports) {
232
+ for (const warning of item.warnings) {
233
+ console.log(`warning: ${item.alias}: ${warning}`);
234
+ }
235
+ for (const error of item.errors) {
236
+ console.log(`error: ${item.alias}: ${error}`);
237
+ }
238
+ }
239
+ }
240
+ if (!ok) {
241
+ throw new errors_1.ValidationError("bundle import verify failed");
242
+ }
243
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCapabilityListCommand = runCapabilityListCommand;
4
+ exports.runCapabilitySearchCommand = runCapabilitySearchCommand;
5
+ exports.runCapabilityShowCommand = runCapabilityShowCommand;
6
+ const config_1 = require("../core/config");
7
+ const capabilities_indexer_1 = require("../graph/capabilities_indexer");
8
+ const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
9
+ const bundle_imports_1 = require("../graph/bundle_imports");
10
+ const errors_1 = require("../util/errors");
11
+ function normalizeKind(value) {
12
+ if (value === undefined) {
13
+ return undefined;
14
+ }
15
+ const normalized = value.toLowerCase();
16
+ if (capabilities_indexer_1.CAPABILITY_KINDS.includes(normalized)) {
17
+ return normalized;
18
+ }
19
+ throw new errors_1.UsageError(`--kind must be one of ${capabilities_indexer_1.CAPABILITY_KINDS.join(", ")}`);
20
+ }
21
+ function normalizeVisibility(value) {
22
+ if (value === undefined) {
23
+ return undefined;
24
+ }
25
+ const normalized = value.toLowerCase();
26
+ if (capabilities_indexer_1.CAPABILITY_VISIBILITIES.includes(normalized)) {
27
+ return normalized;
28
+ }
29
+ throw new errors_1.UsageError(`--visibility must be one of ${capabilities_indexer_1.CAPABILITY_VISIBILITIES.join(", ")}`);
30
+ }
31
+ function loadRecords(options) {
32
+ const config = (0, config_1.loadConfig)(options.root);
33
+ const { index, stale, rebuilt } = (0, capabilities_index_cache_1.loadCapabilitiesIndex)({
34
+ root: options.root,
35
+ config,
36
+ useCache: !options.noCache,
37
+ allowReindex: !options.noReindex,
38
+ });
39
+ if (stale && !rebuilt && !options.noCache) {
40
+ console.error("warning: capabilities index is stale; run mdkg index to refresh");
41
+ }
42
+ const imported = (0, bundle_imports_1.buildImportedCapabilityRecords)(options.root, config);
43
+ for (const warning of imported.warnings) {
44
+ console.error(`warning: ${warning}`);
45
+ }
46
+ return [...index.records, ...imported.records];
47
+ }
48
+ function applyFilters(records, options) {
49
+ const kind = normalizeKind(options.kind);
50
+ const visibility = normalizeVisibility(options.visibility);
51
+ return records.filter((record) => {
52
+ if (kind && record.kind !== kind) {
53
+ return false;
54
+ }
55
+ if (visibility && record.visibility !== visibility) {
56
+ return false;
57
+ }
58
+ return true;
59
+ });
60
+ }
61
+ function capabilitySearchText(record) {
62
+ return [
63
+ record.kind,
64
+ record.workspace,
65
+ record.visibility,
66
+ record.id,
67
+ record.qid,
68
+ record.slug,
69
+ record.name,
70
+ record.title,
71
+ record.description,
72
+ record.path,
73
+ ...record.tags,
74
+ ...record.refs,
75
+ ...record.aliases,
76
+ ...record.links,
77
+ ...record.headings.map((heading) => heading.text),
78
+ JSON.stringify(record.spec ?? {}),
79
+ JSON.stringify(record.work ?? {}),
80
+ JSON.stringify(record.skill ?? {}),
81
+ ]
82
+ .filter((value) => typeof value === "string" && value.length > 0)
83
+ .join(" ")
84
+ .toLowerCase();
85
+ }
86
+ function matchesQuery(record, query) {
87
+ const terms = query
88
+ .toLowerCase()
89
+ .split(/\s+/)
90
+ .map((term) => term.trim())
91
+ .filter(Boolean);
92
+ if (terms.length === 0) {
93
+ return true;
94
+ }
95
+ const text = capabilitySearchText(record);
96
+ return terms.every((term) => text.includes(term));
97
+ }
98
+ function printCapabilityList(records, json, query) {
99
+ if (json) {
100
+ console.log(JSON.stringify({
101
+ kind: "capability",
102
+ query,
103
+ count: records.length,
104
+ items: records,
105
+ }, null, 2));
106
+ return;
107
+ }
108
+ if (records.length === 0) {
109
+ console.log(query ? `no capabilities matched query "${query}"` : "no capabilities matched current filters");
110
+ return;
111
+ }
112
+ console.log(`capabilities: ${records.length}`);
113
+ for (const record of records) {
114
+ const label = record.slug ?? record.id;
115
+ console.log(`${record.qid} | ${record.kind} | ${record.visibility} | ${label} | ${record.title}`);
116
+ }
117
+ }
118
+ function resolveCapability(records, id) {
119
+ const normalized = id.toLowerCase();
120
+ const exact = records.find((record) => record.qid === id || record.id === id);
121
+ if (exact) {
122
+ return exact;
123
+ }
124
+ const matches = records.filter((record) => record.slug === normalized ||
125
+ record.id === normalized ||
126
+ record.qid.toLowerCase() === normalized ||
127
+ `skill:${record.slug}` === normalized);
128
+ if (matches.length === 1) {
129
+ return matches[0];
130
+ }
131
+ if (matches.length > 1) {
132
+ throw new errors_1.UsageError(`capability reference is ambiguous: ${id}`);
133
+ }
134
+ throw new errors_1.NotFoundError(`capability not found: ${id}`);
135
+ }
136
+ function printCapability(record, json) {
137
+ if (json) {
138
+ console.log(JSON.stringify({ kind: "capability", item: record }, null, 2));
139
+ return;
140
+ }
141
+ console.log(`${record.qid} | ${record.kind} | ${record.visibility}`);
142
+ console.log(`title: ${record.title}`);
143
+ if (record.description) {
144
+ console.log(`description: ${record.description}`);
145
+ }
146
+ if (record.tags.length > 0) {
147
+ console.log(`tags: ${record.tags.join(", ")}`);
148
+ }
149
+ console.log(`path: ${record.path}`);
150
+ }
151
+ function runCapabilityListCommand(options) {
152
+ const records = applyFilters(loadRecords(options), options);
153
+ printCapabilityList(records, options.json);
154
+ }
155
+ function runCapabilitySearchCommand(options) {
156
+ const records = applyFilters(loadRecords(options), options).filter((record) => matchesQuery(record, options.query));
157
+ printCapabilityList(records, options.json, options.query);
158
+ }
159
+ function runCapabilityShowCommand(options) {
160
+ const records = loadRecords(options);
161
+ printCapability(resolveCapability(records, options.id), options.json);
162
+ }
@@ -8,10 +8,14 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const config_1 = require("../core/config");
10
10
  const index_cache_1 = require("../graph/index_cache");
11
+ const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
12
+ const bundle_imports_1 = require("../graph/bundle_imports");
11
13
  const node_1 = require("../graph/node");
12
14
  const template_schema_1 = require("../graph/template_schema");
15
+ const visibility_1 = require("../graph/visibility");
13
16
  const errors_1 = require("../util/errors");
14
17
  const REQUIRED_NODE_MAJOR = 18;
18
+ const ARCHIVE_RAW_ALLOWED_DIRS = new Set(["source"]);
15
19
  function parseNodeMajor(version) {
16
20
  const major = Number.parseInt(version.split(".")[0] ?? "", 10);
17
21
  if (!Number.isInteger(major)) {
@@ -42,6 +46,183 @@ function runNodeVersionCheck() {
42
46
  detail: `Node.js ${nodeVersion} (ok)`,
43
47
  };
44
48
  }
49
+ function walkFiles(root) {
50
+ if (!fs_1.default.existsSync(root)) {
51
+ return [];
52
+ }
53
+ const entries = fs_1.default.readdirSync(root, { withFileTypes: true });
54
+ const files = [];
55
+ for (const entry of entries) {
56
+ const fullPath = path_1.default.join(root, entry.name);
57
+ if (entry.isDirectory()) {
58
+ files.push(...walkFiles(fullPath));
59
+ }
60
+ else if (entry.isFile()) {
61
+ files.push(fullPath);
62
+ }
63
+ }
64
+ return files;
65
+ }
66
+ function runArchiveStorageCheck(root) {
67
+ const archiveRoot = path_1.default.join(root, ".mdkg", "archive");
68
+ const files = walkFiles(archiveRoot);
69
+ const strayRaw = files
70
+ .filter((filePath) => {
71
+ const relative = path_1.default.relative(archiveRoot, filePath).split(path_1.default.sep);
72
+ const ext = path_1.default.extname(filePath).toLowerCase();
73
+ if (ext === ".md" || ext === ".zip") {
74
+ return false;
75
+ }
76
+ return !relative.some((segment) => ARCHIVE_RAW_ALLOWED_DIRS.has(segment));
77
+ })
78
+ .map((filePath) => path_1.default.relative(root, filePath).split(path_1.default.sep).join("/"))
79
+ .sort();
80
+ if (strayRaw.length === 0) {
81
+ return {
82
+ name: "archive-storage",
83
+ ok: true,
84
+ detail: ".mdkg/archive has no stray raw files outside managed source directories",
85
+ };
86
+ }
87
+ return {
88
+ name: "archive-storage",
89
+ ok: true,
90
+ level: "warn",
91
+ detail: `stray uncompressed archive file(s) found without managed sidecars: ${strayRaw.join(", ")}; run \`mdkg archive add <file>\` or move raw files under a managed archive source directory`,
92
+ };
93
+ }
94
+ function runArchiveLargeCacheCheck(root, warningBytes) {
95
+ if (warningBytes === 0) {
96
+ return {
97
+ name: "archive-large-cache",
98
+ ok: true,
99
+ detail: "archive large-cache warning disabled",
100
+ };
101
+ }
102
+ const archiveRoot = path_1.default.join(root, ".mdkg", "archive");
103
+ const largeCaches = walkFiles(archiveRoot)
104
+ .filter((filePath) => filePath.endsWith(".zip"))
105
+ .map((filePath) => ({
106
+ path: path_1.default.relative(root, filePath).split(path_1.default.sep).join("/"),
107
+ size: fs_1.default.statSync(filePath).size,
108
+ }))
109
+ .filter((entry) => entry.size > warningBytes)
110
+ .sort((a, b) => a.path.localeCompare(b.path));
111
+ if (largeCaches.length === 0) {
112
+ return {
113
+ name: "archive-large-cache",
114
+ ok: true,
115
+ detail: `no archive ZIP cache exceeds ${warningBytes} bytes`,
116
+ };
117
+ }
118
+ return {
119
+ name: "archive-large-cache",
120
+ ok: true,
121
+ level: "warn",
122
+ detail: `archive ZIP cache(s) exceed ${warningBytes} bytes: ${largeCaches
123
+ .map((entry) => `${entry.path} (${entry.size} bytes)`)
124
+ .join(", ")}; keep large caches private or move bulky originals to repo policy-managed storage`,
125
+ };
126
+ }
127
+ function runBundleStorageCheck(root, outputDir) {
128
+ const bundleRoot = path_1.default.resolve(root, outputDir);
129
+ if (!fs_1.default.existsSync(bundleRoot)) {
130
+ return {
131
+ name: "bundle-storage",
132
+ ok: true,
133
+ detail: `no bundle directory found; run \`mdkg bundle create --profile private\` when a snapshot should be tracked`,
134
+ };
135
+ }
136
+ const bundles = walkFiles(bundleRoot)
137
+ .filter((filePath) => filePath.endsWith(".mdkg.zip"))
138
+ .map((filePath) => path_1.default.relative(root, filePath).split(path_1.default.sep).join("/"))
139
+ .sort();
140
+ if (bundles.length === 0) {
141
+ return {
142
+ name: "bundle-storage",
143
+ ok: true,
144
+ detail: `bundle directory has no .mdkg.zip files; run \`mdkg bundle create --profile private\` when a snapshot should be tracked`,
145
+ };
146
+ }
147
+ return {
148
+ name: "bundle-storage",
149
+ ok: true,
150
+ detail: `${bundles.length} bundle(s) found; run \`mdkg bundle verify <path>\` to check freshness before handoff`,
151
+ };
152
+ }
153
+ function runBundleImportChecks(root, config) {
154
+ const projection = (0, bundle_imports_1.buildBundleImportsIndex)(root, config);
155
+ if (projection.index.imports.length === 0) {
156
+ return [
157
+ {
158
+ name: "bundle-imports",
159
+ ok: true,
160
+ detail: "no bundle imports configured",
161
+ },
162
+ ];
163
+ }
164
+ return projection.index.imports.map((item) => {
165
+ if (!item.enabled) {
166
+ return {
167
+ name: `bundle-import:${item.alias}`,
168
+ ok: true,
169
+ level: "warn",
170
+ detail: `disabled import at ${item.path}`,
171
+ };
172
+ }
173
+ if (item.error_count > 0) {
174
+ return {
175
+ name: `bundle-import:${item.alias}`,
176
+ ok: false,
177
+ detail: item.errors.join("; "),
178
+ };
179
+ }
180
+ if (item.stale || item.warning_count > 0) {
181
+ return {
182
+ name: `bundle-import:${item.alias}`,
183
+ ok: true,
184
+ level: "warn",
185
+ detail: `import is stale or has warnings; run \`mdkg bundle import verify ${item.alias}\` (${item.warnings.join("; ")})`,
186
+ };
187
+ }
188
+ return {
189
+ name: `bundle-import:${item.alias}`,
190
+ ok: true,
191
+ detail: `import loaded from ${item.path}`,
192
+ };
193
+ });
194
+ }
195
+ function runVisibilityPolicyCheck(root, config, options) {
196
+ try {
197
+ const { index } = (0, index_cache_1.loadIndex)({
198
+ root,
199
+ config,
200
+ useCache: !options.noCache,
201
+ allowReindex: !options.noReindex,
202
+ });
203
+ const messages = (0, visibility_1.visibilityViolationMessages)((0, visibility_1.collectVisibilityViolations)(index, config));
204
+ if (messages.length === 0) {
205
+ return {
206
+ name: "visibility-policy",
207
+ ok: true,
208
+ detail: "public/internal records do not reference less-visible mdkg records",
209
+ };
210
+ }
211
+ return {
212
+ name: "visibility-policy",
213
+ ok: false,
214
+ detail: `${messages.length} violation(s): ${messages.join("; ")}`,
215
+ };
216
+ }
217
+ catch (err) {
218
+ const message = err instanceof Error ? err.message : String(err);
219
+ return {
220
+ name: "visibility-policy",
221
+ ok: false,
222
+ detail: message,
223
+ };
224
+ }
225
+ }
45
226
  function runDoctorCommand(options) {
46
227
  const results = [];
47
228
  results.push(runNodeVersionCheck());
@@ -78,13 +259,26 @@ function runDoctorCommand(options) {
78
259
  });
79
260
  }
80
261
  if (config) {
262
+ results.push(runArchiveStorageCheck(options.root));
263
+ results.push(runArchiveLargeCacheCheck(options.root, config.archive.large_cache_warning_bytes));
264
+ results.push(runBundleStorageCheck(options.root, config.bundles.output_dir));
265
+ results.push(...runBundleImportChecks(options.root, config));
266
+ results.push(runVisibilityPolicyCheck(options.root, config, options));
81
267
  try {
82
- (0, template_schema_1.loadTemplateSchemas)(options.root, config, node_1.ALLOWED_TYPES);
268
+ const templateSchemaInfo = (0, template_schema_1.loadTemplateSchemasWithInfo)(options.root, config, node_1.ALLOWED_TYPES);
83
269
  results.push({
84
270
  name: "templates",
85
271
  ok: true,
86
272
  detail: "template schema set loaded",
87
273
  });
274
+ if (templateSchemaInfo.fallbackTypes.length > 0) {
275
+ results.push({
276
+ name: "local-templates",
277
+ ok: true,
278
+ level: "warn",
279
+ detail: `missing local template schema(s) covered by bundled fallback: ${templateSchemaInfo.fallbackTypes.join(", ")}; run \`mdkg upgrade --apply\` to vendor them`,
280
+ });
281
+ }
88
282
  }
89
283
  catch (err) {
90
284
  const message = err instanceof Error ? err.message : String(err);
@@ -131,6 +325,43 @@ function runDoctorCommand(options) {
131
325
  detail: message,
132
326
  });
133
327
  }
328
+ try {
329
+ const { rebuilt, stale } = (0, capabilities_index_cache_1.loadCapabilitiesIndex)({
330
+ root: options.root,
331
+ config,
332
+ useCache: !options.noCache,
333
+ allowReindex: !options.noReindex,
334
+ });
335
+ if (rebuilt) {
336
+ results.push({
337
+ name: "capabilities-index",
338
+ ok: true,
339
+ detail: "capabilities cache rebuilt and loaded",
340
+ });
341
+ }
342
+ else if (stale) {
343
+ results.push({
344
+ name: "capabilities-index",
345
+ ok: true,
346
+ detail: "capabilities cache is stale (run mdkg index to refresh)",
347
+ });
348
+ }
349
+ else {
350
+ results.push({
351
+ name: "capabilities-index",
352
+ ok: true,
353
+ detail: "capabilities cache loaded",
354
+ });
355
+ }
356
+ }
357
+ catch (err) {
358
+ const message = err instanceof Error ? err.message : String(err);
359
+ results.push({
360
+ name: "capabilities-index",
361
+ ok: false,
362
+ detail: message,
363
+ });
364
+ }
134
365
  }
135
366
  const failures = results.filter((result) => !result.ok);
136
367
  if (options.json) {
@@ -143,7 +374,7 @@ function runDoctorCommand(options) {
143
374
  }
144
375
  else {
145
376
  for (const result of results) {
146
- const prefix = result.ok ? "ok" : "fail";
377
+ const prefix = result.ok ? result.level ?? "ok" : "fail";
147
378
  console.log(`${prefix}: ${result.name} - ${result.detail}`);
148
379
  }
149
380
  }