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.
- package/CHANGELOG.md +93 -0
- package/README.md +108 -15
- package/dist/cli.js +566 -15
- package/dist/commands/archive.js +474 -0
- package/dist/commands/bundle.js +743 -0
- package/dist/commands/bundle_import.js +243 -0
- package/dist/commands/capability.js +162 -0
- package/dist/commands/doctor.js +233 -2
- package/dist/commands/format.js +38 -9
- package/dist/commands/index.js +11 -0
- package/dist/commands/init.js +188 -63
- package/dist/commands/init_manifest.js +19 -6
- package/dist/commands/list.js +5 -2
- package/dist/commands/new.js +6 -0
- package/dist/commands/next.js +7 -0
- package/dist/commands/node_card.js +4 -1
- package/dist/commands/pack.js +62 -2
- package/dist/commands/query_output.js +1 -0
- package/dist/commands/search.js +5 -2
- package/dist/commands/show.js +7 -14
- package/dist/commands/skill_mirror.js +22 -0
- package/dist/commands/task.js +3 -0
- package/dist/commands/upgrade.js +151 -13
- package/dist/commands/validate.js +19 -2
- package/dist/commands/work.js +365 -0
- package/dist/commands/workspace.js +12 -2
- package/dist/core/config.js +100 -1
- package/dist/graph/agent_file_types.js +78 -5
- package/dist/graph/archive_file.js +125 -0
- package/dist/graph/archive_integrity.js +66 -0
- package/dist/graph/bundle_imports.js +418 -0
- package/dist/graph/capabilities_index_cache.js +103 -0
- package/dist/graph/capabilities_indexer.js +231 -0
- package/dist/graph/frontmatter.js +19 -0
- package/dist/graph/index_cache.js +21 -4
- package/dist/graph/indexer.js +4 -1
- package/dist/graph/node.js +23 -4
- package/dist/graph/node_body.js +37 -0
- package/dist/graph/skills_indexer.js +8 -3
- package/dist/graph/template_schema.js +33 -5
- package/dist/graph/validate_graph.js +83 -7
- package/dist/graph/visibility.js +214 -0
- package/dist/graph/workspace_files.js +22 -0
- package/dist/init/AGENT_START.md +21 -0
- package/dist/init/CLI_COMMAND_MATRIX.md +58 -3
- package/dist/init/README.md +60 -3
- package/dist/init/config.json +13 -1
- package/dist/init/core/guide.md +6 -2
- package/dist/init/core/rule-3-cli-contract.md +71 -4
- package/dist/init/core/rule-4-repo-safety-and-ignores.md +20 -0
- package/dist/init/core/rule-6-templates-and-schemas.md +10 -1
- package/dist/init/init-manifest.json +19 -14
- package/dist/init/skills/default/build-pack-and-execute-task/SKILL.md +2 -1
- package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +26 -0
- package/dist/init/templates/default/archive.md +33 -0
- package/dist/init/templates/default/receipt.md +15 -1
- package/dist/init/templates/default/work.md +6 -1
- package/dist/init/templates/default/work_order.md +15 -1
- package/dist/pack/export_md.js +3 -0
- package/dist/pack/export_xml.js +3 -0
- package/dist/pack/order.js +1 -0
- package/dist/pack/pack.js +3 -13
- package/dist/templates/builtin.js +38 -0
- package/dist/templates/loader.js +9 -16
- package/dist/util/argparse.js +30 -0
- package/dist/util/refs.js +40 -0
- package/dist/util/zip.js +153 -0
- 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
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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.
|
|
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
|
}
|