mdkg 0.0.8 → 0.1.0
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 +71 -0
- package/CONTRIBUTING.md +124 -0
- package/README.md +49 -14
- package/dist/cli.js +113 -32
- package/dist/commands/checkpoint.js +19 -2
- package/dist/commands/event.js +12 -0
- package/dist/commands/init.js +4 -0
- package/dist/commands/init_manifest.js +131 -0
- package/dist/commands/new.js +57 -21
- package/dist/commands/pack.js +14 -0
- package/dist/commands/query_output.js +2 -0
- package/dist/commands/search.js +8 -0
- package/dist/commands/show.js +7 -0
- package/dist/commands/skill.js +80 -12
- package/dist/commands/task.js +42 -12
- package/dist/commands/upgrade.js +286 -0
- package/dist/commands/validate.js +31 -3
- package/dist/commands/workspace.js +105 -13
- package/dist/core/config.js +217 -22
- package/dist/core/migrate.js +39 -5
- package/dist/core/version.js +31 -0
- package/dist/core/workspace_path.js +41 -0
- package/dist/graph/agent_file_types.js +392 -0
- package/dist/graph/edges.js +13 -10
- package/dist/graph/frontmatter.js +33 -0
- package/dist/graph/indexer.js +1 -0
- package/dist/graph/node.js +43 -16
- package/dist/graph/skills_indexer.js +14 -1
- package/dist/graph/template_schema.js +13 -126
- package/dist/graph/validate_graph.js +302 -2
- package/dist/init/AGENT_START.md +14 -2
- package/dist/init/CLI_COMMAND_MATRIX.md +49 -1
- package/dist/init/README.md +14 -0
- package/dist/init/core/rule-6-templates-and-schemas.md +1 -3
- package/dist/init/init-manifest.json +197 -0
- package/dist/init/legacy/v0.0.9-init-manifest.json +197 -0
- package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +12 -11
- package/dist/init/templates/default/dispute.md +31 -0
- package/dist/init/templates/default/feedback.md +27 -0
- package/dist/init/templates/default/proposal.md +35 -0
- package/dist/init/templates/default/receipt.md +31 -0
- package/dist/init/templates/default/spec.md +43 -0
- package/dist/init/templates/default/work.md +44 -0
- package/dist/init/templates/default/work_order.md +32 -0
- package/dist/pack/export_json.js +3 -0
- package/dist/pack/export_md.js +9 -0
- package/dist/pack/export_xml.js +9 -0
- package/dist/pack/order.js +7 -0
- package/dist/pack/pack.js +1 -0
- package/dist/templates/loader.js +2 -2
- package/dist/util/argparse.js +2 -0
- package/dist/util/id.js +19 -0
- package/package.json +10 -2
- package/scripts/postinstall.js +89 -0
|
@@ -0,0 +1,286 @@
|
|
|
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.runUpgradeCommand = runUpgradeCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const migrate_1 = require("../core/migrate");
|
|
10
|
+
const config_1 = require("../core/config");
|
|
11
|
+
const paths_1 = require("../core/paths");
|
|
12
|
+
const version_1 = require("../core/version");
|
|
13
|
+
const errors_1 = require("../util/errors");
|
|
14
|
+
const init_manifest_1 = require("./init_manifest");
|
|
15
|
+
const skill_support_1 = require("./skill_support");
|
|
16
|
+
const skill_mirror_1 = require("./skill_mirror");
|
|
17
|
+
const DEFAULT_SEED_SUBDIR = path_1.default.resolve(__dirname, "..", "init");
|
|
18
|
+
const PROTECTED_CORE_DOCS = new Set([".mdkg/core/SOUL.md", ".mdkg/core/HUMAN.md"]);
|
|
19
|
+
const CREATE_ONLY_PRESERVED = new Set([".mdkg/core/core.md"]);
|
|
20
|
+
function seededInitEvent(nowIso) {
|
|
21
|
+
const event = {
|
|
22
|
+
ts: nowIso,
|
|
23
|
+
run_id: `upgrade-${nowIso.replace(/[^0-9]/g, "").slice(0, 14)}`,
|
|
24
|
+
workspace: "root",
|
|
25
|
+
agent: "mdkg",
|
|
26
|
+
kind: "RUN_STARTED",
|
|
27
|
+
status: "ok",
|
|
28
|
+
refs: ["edd-4"],
|
|
29
|
+
artifacts: [],
|
|
30
|
+
notes: "upgrade ensured agent event log",
|
|
31
|
+
redacted: true,
|
|
32
|
+
};
|
|
33
|
+
return `${JSON.stringify(event)}\n`;
|
|
34
|
+
}
|
|
35
|
+
function requireSeedAssets(seedRoot) {
|
|
36
|
+
for (const required of ["config.json", "README.md", "core", "templates"]) {
|
|
37
|
+
if (!fs_1.default.existsSync(path_1.default.join(seedRoot, required))) {
|
|
38
|
+
throw new errors_1.NotFoundError(`upgrade assets missing ${required} at ${seedRoot} (try reinstalling mdkg)`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function isAgentWorkspace(root) {
|
|
43
|
+
return [
|
|
44
|
+
path_1.default.join(root, ".mdkg", "skills"),
|
|
45
|
+
path_1.default.join(root, ".agents", "skills"),
|
|
46
|
+
path_1.default.join(root, ".claude", "skills"),
|
|
47
|
+
path_1.default.join(root, ".mdkg", "work", "events", "events.jsonl"),
|
|
48
|
+
].some((candidate) => fs_1.default.existsSync(candidate));
|
|
49
|
+
}
|
|
50
|
+
function copyFile(src, dest) {
|
|
51
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
|
|
52
|
+
fs_1.default.copyFileSync(src, dest);
|
|
53
|
+
}
|
|
54
|
+
function writeFile(filePath, content) {
|
|
55
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
56
|
+
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
57
|
+
}
|
|
58
|
+
function createSummary() {
|
|
59
|
+
return {
|
|
60
|
+
created: 0,
|
|
61
|
+
updated: 0,
|
|
62
|
+
migrated: 0,
|
|
63
|
+
synced: 0,
|
|
64
|
+
skipped: 0,
|
|
65
|
+
conflicted: 0,
|
|
66
|
+
unchanged: 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function record(summary, changes, change) {
|
|
70
|
+
changes.push(change);
|
|
71
|
+
switch (change.action) {
|
|
72
|
+
case "create":
|
|
73
|
+
summary.created += 1;
|
|
74
|
+
break;
|
|
75
|
+
case "update":
|
|
76
|
+
summary.updated += 1;
|
|
77
|
+
break;
|
|
78
|
+
case "migrate":
|
|
79
|
+
summary.migrated += 1;
|
|
80
|
+
break;
|
|
81
|
+
case "sync":
|
|
82
|
+
summary.synced += 1;
|
|
83
|
+
break;
|
|
84
|
+
case "conflict":
|
|
85
|
+
summary.skipped += 1;
|
|
86
|
+
summary.conflicted += 1;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function buildKnownHashes(manifests) {
|
|
91
|
+
const hashes = new Map();
|
|
92
|
+
for (const manifest of manifests) {
|
|
93
|
+
if (!manifest) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
for (const file of manifest.files) {
|
|
97
|
+
if (!hashes.has(file.path)) {
|
|
98
|
+
hashes.set(file.path, new Set());
|
|
99
|
+
}
|
|
100
|
+
hashes.get(file.path)?.add(file.sha256);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return hashes;
|
|
104
|
+
}
|
|
105
|
+
function shouldIncludeFile(file, agentWorkspace) {
|
|
106
|
+
if (file.category === "default_skill") {
|
|
107
|
+
return agentWorkspace;
|
|
108
|
+
}
|
|
109
|
+
return file.category !== "config";
|
|
110
|
+
}
|
|
111
|
+
function planSeedFile(options) {
|
|
112
|
+
const destPath = path_1.default.join(options.root, options.file.path);
|
|
113
|
+
const srcPath = (0, init_manifest_1.seedSourcePath)(options.seedRoot, options.file);
|
|
114
|
+
const currentHash = fs_1.default.existsSync(destPath) && fs_1.default.statSync(destPath).isFile() ? (0, init_manifest_1.sha256File)(destPath) : undefined;
|
|
115
|
+
const nextHash = options.file.sha256;
|
|
116
|
+
if (!currentHash) {
|
|
117
|
+
record(options.summary, options.changes, {
|
|
118
|
+
path: options.file.path,
|
|
119
|
+
category: options.file.category,
|
|
120
|
+
action: "create",
|
|
121
|
+
reason: "missing managed init asset",
|
|
122
|
+
});
|
|
123
|
+
if (!options.dryRun) {
|
|
124
|
+
copyFile(srcPath, destPath);
|
|
125
|
+
}
|
|
126
|
+
options.managedCurrentFiles.push(options.file);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (currentHash === nextHash) {
|
|
130
|
+
options.summary.unchanged += 1;
|
|
131
|
+
options.managedCurrentFiles.push(options.file);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (CREATE_ONLY_PRESERVED.has(options.file.path)) {
|
|
135
|
+
options.summary.unchanged += 1;
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (PROTECTED_CORE_DOCS.has(options.file.path)) {
|
|
139
|
+
record(options.summary, options.changes, {
|
|
140
|
+
path: options.file.path,
|
|
141
|
+
category: options.file.category,
|
|
142
|
+
action: "conflict",
|
|
143
|
+
reason: "protected core document exists; local content preserved",
|
|
144
|
+
});
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const known = options.knownHashes.get(options.file.path);
|
|
148
|
+
if (known?.has(currentHash)) {
|
|
149
|
+
record(options.summary, options.changes, {
|
|
150
|
+
path: options.file.path,
|
|
151
|
+
category: options.file.category,
|
|
152
|
+
action: "update",
|
|
153
|
+
reason: "matches a managed seed hash",
|
|
154
|
+
});
|
|
155
|
+
if (!options.dryRun) {
|
|
156
|
+
copyFile(srcPath, destPath);
|
|
157
|
+
}
|
|
158
|
+
options.managedCurrentFiles.push(options.file);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
record(options.summary, options.changes, {
|
|
162
|
+
path: options.file.path,
|
|
163
|
+
category: options.file.category,
|
|
164
|
+
action: "conflict",
|
|
165
|
+
reason: "local changes detected; content preserved",
|
|
166
|
+
});
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
function migrateConfigIfNeeded(root, dryRun, summary, changes) {
|
|
170
|
+
const cfgPath = (0, paths_1.configPath)(root);
|
|
171
|
+
const raw = JSON.parse(fs_1.default.readFileSync(cfgPath, "utf8"));
|
|
172
|
+
const migrated = (0, migrate_1.migrateConfig)(raw);
|
|
173
|
+
(0, config_1.validateConfigSchema)(migrated.config);
|
|
174
|
+
if (migrated.from === migrated.to) {
|
|
175
|
+
summary.unchanged += 1;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
record(summary, changes, {
|
|
179
|
+
path: ".mdkg/config.json",
|
|
180
|
+
category: "config",
|
|
181
|
+
action: "migrate",
|
|
182
|
+
reason: `config schema_version ${migrated.from} -> ${migrated.to}`,
|
|
183
|
+
});
|
|
184
|
+
if (!dryRun) {
|
|
185
|
+
writeFile(cfgPath, `${JSON.stringify(migrated.config, null, 2)}\n`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function ensureAgentRuntimeFiles(root, dryRun, summary, changes) {
|
|
189
|
+
const eventsPath = path_1.default.join(root, ".mdkg", "work", "events", "events.jsonl");
|
|
190
|
+
if (!fs_1.default.existsSync(eventsPath)) {
|
|
191
|
+
record(summary, changes, {
|
|
192
|
+
path: ".mdkg/work/events/events.jsonl",
|
|
193
|
+
category: "event_log",
|
|
194
|
+
action: "create",
|
|
195
|
+
reason: "agent workspace is missing event log",
|
|
196
|
+
});
|
|
197
|
+
if (!dryRun) {
|
|
198
|
+
writeFile(eventsPath, seededInitEvent(new Date().toISOString()));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
summary.unchanged += 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function emitHumanReceipt(receipt) {
|
|
206
|
+
const mode = receipt.dry_run ? "dry-run" : "apply";
|
|
207
|
+
console.log(`mdkg upgrade ${mode}: ${receipt.summary.created} create, ${receipt.summary.updated} update, ${receipt.summary.migrated} migrate, ${receipt.summary.synced} sync, ${receipt.summary.conflicted} conflict`);
|
|
208
|
+
if (receipt.changes.length === 0) {
|
|
209
|
+
console.log("no upgrade changes pending");
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
for (const change of receipt.changes) {
|
|
213
|
+
console.log(` ${change.action}: ${change.path} (${change.reason})`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (receipt.dry_run && receipt.changes.some((change) => change.action !== "conflict")) {
|
|
217
|
+
console.log("next: mdkg upgrade --apply");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function runUpgradeCommand(options) {
|
|
221
|
+
const root = path_1.default.resolve(options.root);
|
|
222
|
+
const seedRoot = options.seedRoot ? path_1.default.resolve(options.seedRoot) : DEFAULT_SEED_SUBDIR;
|
|
223
|
+
const dryRun = !options.apply;
|
|
224
|
+
const version = (0, version_1.readPackageVersion)();
|
|
225
|
+
requireSeedAssets(seedRoot);
|
|
226
|
+
const currentManifest = (0, init_manifest_1.createInitManifest)(seedRoot, version);
|
|
227
|
+
const existingManifest = (0, init_manifest_1.readInitManifest)(path_1.default.join(root, ".mdkg", init_manifest_1.INIT_MANIFEST_FILE));
|
|
228
|
+
const legacyManifests = (0, init_manifest_1.loadLegacyInitManifests)(seedRoot);
|
|
229
|
+
const knownHashes = buildKnownHashes([existingManifest, ...legacyManifests]);
|
|
230
|
+
const summary = createSummary();
|
|
231
|
+
const changes = [];
|
|
232
|
+
const agentWorkspace = isAgentWorkspace(root);
|
|
233
|
+
const managedCurrentFiles = [];
|
|
234
|
+
migrateConfigIfNeeded(root, dryRun, summary, changes);
|
|
235
|
+
for (const file of currentManifest.files) {
|
|
236
|
+
if (!shouldIncludeFile(file, agentWorkspace)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
planSeedFile({
|
|
240
|
+
root,
|
|
241
|
+
seedRoot,
|
|
242
|
+
file,
|
|
243
|
+
knownHashes,
|
|
244
|
+
dryRun,
|
|
245
|
+
summary,
|
|
246
|
+
changes,
|
|
247
|
+
managedCurrentFiles,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (agentWorkspace) {
|
|
251
|
+
ensureAgentRuntimeFiles(root, dryRun, summary, changes);
|
|
252
|
+
}
|
|
253
|
+
if (!dryRun) {
|
|
254
|
+
const appliedManifest = {
|
|
255
|
+
...currentManifest,
|
|
256
|
+
files: managedCurrentFiles.sort((a, b) => a.path.localeCompare(b.path)),
|
|
257
|
+
};
|
|
258
|
+
(0, init_manifest_1.writeInitManifest)(path_1.default.join(root, ".mdkg", init_manifest_1.INIT_MANIFEST_FILE), appliedManifest);
|
|
259
|
+
if (agentWorkspace) {
|
|
260
|
+
const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
|
|
261
|
+
(0, skill_support_1.refreshSkillsRegistry)(root, config);
|
|
262
|
+
(0, skill_mirror_1.scaffoldMirrorRoots)(root);
|
|
263
|
+
const result = (0, skill_mirror_1.syncSkillMirrors)({ root, config, createRoots: true });
|
|
264
|
+
record(summary, changes, {
|
|
265
|
+
path: ".agents/skills,.claude/skills",
|
|
266
|
+
category: "skill_mirror",
|
|
267
|
+
action: "sync",
|
|
268
|
+
reason: `${result.synced} mirrored skill(s), ${result.pruned} stale mirror(s) pruned`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const receipt = {
|
|
273
|
+
action: "upgrade",
|
|
274
|
+
dry_run: dryRun,
|
|
275
|
+
version,
|
|
276
|
+
summary,
|
|
277
|
+
changes,
|
|
278
|
+
};
|
|
279
|
+
if (options.json) {
|
|
280
|
+
console.log(`${JSON.stringify(receipt, null, 2)}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
emitHumanReceipt(receipt);
|
|
284
|
+
}
|
|
285
|
+
return receipt;
|
|
286
|
+
}
|
|
@@ -65,6 +65,13 @@ const RECOMMENDED_HEADINGS = {
|
|
|
65
65
|
"Rollout plan",
|
|
66
66
|
],
|
|
67
67
|
dec: ["Context", "Decision", "Alternatives considered", "Consequences", "Links / references"],
|
|
68
|
+
spec: ["Purpose", "Runtime", "Work Contracts", "Capabilities"],
|
|
69
|
+
work: ["Capability", "Inputs", "Outputs", "Receipt"],
|
|
70
|
+
work_order: ["Request", "Inputs", "Constraints"],
|
|
71
|
+
receipt: ["Outcome", "Artifacts", "Notes"],
|
|
72
|
+
feedback: ["Feedback", "Evidence"],
|
|
73
|
+
dispute: ["Dispute", "Evidence", "Resolution"],
|
|
74
|
+
proposal: ["Summary", "Evidence", "Proposed Change", "Review"],
|
|
68
75
|
};
|
|
69
76
|
function normalizeHeading(value) {
|
|
70
77
|
return value.trim().toLowerCase();
|
|
@@ -119,6 +126,7 @@ function buildIndexNode(root, ws, filePath, node) {
|
|
|
119
126
|
refs: node.refs,
|
|
120
127
|
aliases: node.aliases,
|
|
121
128
|
skills: node.skills,
|
|
129
|
+
attributes: node.attributes,
|
|
122
130
|
path: path_1.default.relative(root, filePath),
|
|
123
131
|
edges: normalizeEdges(node.edges, ws),
|
|
124
132
|
};
|
|
@@ -256,11 +264,10 @@ function runValidateCommand(options) {
|
|
|
256
264
|
nodes,
|
|
257
265
|
reverse_edges: {},
|
|
258
266
|
};
|
|
259
|
-
|
|
260
|
-
errors.push(...graphErrors);
|
|
267
|
+
let knownSkills = new Set();
|
|
261
268
|
try {
|
|
262
269
|
const skillsIndex = (0, skills_indexer_1.buildSkillsIndex)(options.root, config);
|
|
263
|
-
|
|
270
|
+
knownSkills = new Set(Object.keys(skillsIndex.skills));
|
|
264
271
|
for (const node of Object.values(nodes)) {
|
|
265
272
|
for (const slug of node.skills) {
|
|
266
273
|
if (!knownSkills.has(slug)) {
|
|
@@ -273,6 +280,11 @@ function runValidateCommand(options) {
|
|
|
273
280
|
const message = err instanceof Error ? err.message : "unknown skill validation error";
|
|
274
281
|
errors.push(message);
|
|
275
282
|
}
|
|
283
|
+
const graphErrors = (0, validate_graph_1.collectGraphErrors)(index, {
|
|
284
|
+
allowMissing: false,
|
|
285
|
+
knownSkillSlugs: knownSkills,
|
|
286
|
+
});
|
|
287
|
+
errors.push(...graphErrors);
|
|
276
288
|
const skillsRoot = (0, skills_indexer_1.resolveSkillsRoot)(options.root, config);
|
|
277
289
|
for (const dirPath of listDirectories(skillsRoot)) {
|
|
278
290
|
const canonicalPath = path_1.default.join(dirPath, "SKILL.md");
|
|
@@ -305,6 +317,22 @@ function runValidateCommand(options) {
|
|
|
305
317
|
fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
|
|
306
318
|
fs_1.default.writeFileSync(outPath, reportLines.join("\n"), "utf8");
|
|
307
319
|
}
|
|
320
|
+
const receipt = {
|
|
321
|
+
action: "validated",
|
|
322
|
+
ok: uniqueErrors.length === 0,
|
|
323
|
+
warning_count: uniqueWarnings.length,
|
|
324
|
+
error_count: uniqueErrors.length,
|
|
325
|
+
warnings: uniqueWarnings,
|
|
326
|
+
errors: uniqueErrors,
|
|
327
|
+
...(outPath ? { report_path: outPath } : {}),
|
|
328
|
+
};
|
|
329
|
+
if (options.json) {
|
|
330
|
+
console.log(JSON.stringify(receipt, null, 2));
|
|
331
|
+
if (uniqueErrors.length > 0) {
|
|
332
|
+
throw new errors_1.ValidationError(`validation failed with ${uniqueErrors.length} error(s)`);
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
308
336
|
if (!options.quiet) {
|
|
309
337
|
for (const warning of uniqueWarnings) {
|
|
310
338
|
console.error(`warning: ${warning}`);
|
|
@@ -6,11 +6,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runWorkspaceListCommand = runWorkspaceListCommand;
|
|
7
7
|
exports.runWorkspaceAddCommand = runWorkspaceAddCommand;
|
|
8
8
|
exports.runWorkspaceRemoveCommand = runWorkspaceRemoveCommand;
|
|
9
|
+
exports.runWorkspaceEnableCommand = runWorkspaceEnableCommand;
|
|
10
|
+
exports.runWorkspaceDisableCommand = runWorkspaceDisableCommand;
|
|
9
11
|
const fs_1 = __importDefault(require("fs"));
|
|
10
12
|
const path_1 = __importDefault(require("path"));
|
|
11
13
|
const config_1 = require("../core/config");
|
|
14
|
+
const migrate_1 = require("../core/migrate");
|
|
15
|
+
const workspace_path_1 = require("../core/workspace_path");
|
|
12
16
|
const errors_1 = require("../util/errors");
|
|
13
17
|
const ALIAS_RE = /^[a-z][a-z0-9_]*$/;
|
|
18
|
+
function workspaceReceipt(alias, workspace) {
|
|
19
|
+
return {
|
|
20
|
+
alias,
|
|
21
|
+
path: workspace.path,
|
|
22
|
+
enabled: workspace.enabled,
|
|
23
|
+
mdkg_dir: workspace.mdkg_dir,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function printWorkspaceMutationReceipt(action, workspace, json) {
|
|
27
|
+
if (json) {
|
|
28
|
+
console.log(JSON.stringify({ action, workspace }, null, 2));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (action === "added") {
|
|
32
|
+
console.log(`workspace added: ${workspace.alias} (${workspace.path})`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.log(`workspace ${action}: ${workspace.alias}`);
|
|
36
|
+
}
|
|
14
37
|
function readRawConfig(root) {
|
|
15
38
|
const configPath = path_1.default.join(root, ".mdkg", "config.json");
|
|
16
39
|
if (!fs_1.default.existsSync(configPath)) {
|
|
@@ -27,24 +50,51 @@ function readRawConfig(root) {
|
|
|
27
50
|
if (typeof raw !== "object" || raw === null) {
|
|
28
51
|
throw new errors_1.UsageError("config must be a JSON object");
|
|
29
52
|
}
|
|
30
|
-
|
|
53
|
+
const migrated = (0, migrate_1.migrateConfig)(raw).config;
|
|
54
|
+
(0, config_1.validateConfigSchema)(migrated);
|
|
55
|
+
if (typeof migrated !== "object" || migrated === null || Array.isArray(migrated)) {
|
|
56
|
+
throw new errors_1.UsageError("config must be a JSON object");
|
|
57
|
+
}
|
|
58
|
+
return { path: configPath, raw: migrated };
|
|
31
59
|
}
|
|
32
60
|
function writeRawConfig(configPath, raw) {
|
|
33
61
|
fs_1.default.writeFileSync(configPath, JSON.stringify(raw, null, 2), "utf8");
|
|
34
62
|
}
|
|
35
63
|
function normalizeAlias(alias) {
|
|
36
|
-
|
|
37
|
-
if (normalized === "all") {
|
|
64
|
+
if (alias.toLowerCase() === "all") {
|
|
38
65
|
throw new errors_1.UsageError("workspace alias cannot be 'all'");
|
|
39
66
|
}
|
|
40
|
-
if (!ALIAS_RE.test(
|
|
67
|
+
if (alias !== alias.toLowerCase() || !ALIAS_RE.test(alias)) {
|
|
41
68
|
throw new errors_1.UsageError("workspace alias must be lowercase and use [a-z0-9_]");
|
|
42
69
|
}
|
|
43
|
-
return
|
|
70
|
+
return alias;
|
|
71
|
+
}
|
|
72
|
+
function normalizeCommandWorkspacePath(value, label) {
|
|
73
|
+
try {
|
|
74
|
+
return (0, workspace_path_1.normalizeContainedWorkspacePath)(value, label);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78
|
+
throw new errors_1.UsageError(message);
|
|
79
|
+
}
|
|
44
80
|
}
|
|
45
81
|
function runWorkspaceListCommand(options) {
|
|
46
82
|
const config = (0, config_1.loadConfig)(options.root);
|
|
47
83
|
const aliases = Object.keys(config.workspaces).sort();
|
|
84
|
+
if (options.json) {
|
|
85
|
+
console.log(JSON.stringify({
|
|
86
|
+
workspaces: aliases.map((alias) => {
|
|
87
|
+
const ws = config.workspaces[alias];
|
|
88
|
+
return {
|
|
89
|
+
alias,
|
|
90
|
+
path: ws.path,
|
|
91
|
+
enabled: ws.enabled,
|
|
92
|
+
mdkg_dir: ws.mdkg_dir,
|
|
93
|
+
};
|
|
94
|
+
}),
|
|
95
|
+
}, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
48
98
|
if (aliases.length === 0) {
|
|
49
99
|
console.log("no workspaces registered");
|
|
50
100
|
return;
|
|
@@ -57,11 +107,11 @@ function runWorkspaceListCommand(options) {
|
|
|
57
107
|
}
|
|
58
108
|
function runWorkspaceAddCommand(options) {
|
|
59
109
|
const alias = normalizeAlias(options.alias);
|
|
60
|
-
const workspacePath = options.workspacePath
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
const workspacePath = normalizeCommandWorkspacePath(options.workspacePath, "workspace path");
|
|
111
|
+
const mdkgDir = normalizeCommandWorkspacePath(options.mdkgDir ?? ".mdkg", "workspace mdkg dir");
|
|
112
|
+
if ((0, workspace_path_1.isRootWorkspacePath)(workspacePath)) {
|
|
113
|
+
throw new errors_1.UsageError('workspace path must not be "." for non-root workspaces');
|
|
63
114
|
}
|
|
64
|
-
const mdkgDir = options.mdkgDir?.trim() || ".mdkg";
|
|
65
115
|
const { path: configPath, raw } = readRawConfig(options.root);
|
|
66
116
|
const workspacesRaw = raw.workspaces;
|
|
67
117
|
if (typeof workspacesRaw !== "object" || workspacesRaw === null) {
|
|
@@ -71,14 +121,27 @@ function runWorkspaceAddCommand(options) {
|
|
|
71
121
|
if (workspaces[alias]) {
|
|
72
122
|
throw new errors_1.UsageError(`workspace already exists: ${alias}`);
|
|
73
123
|
}
|
|
74
|
-
|
|
124
|
+
const docRootKey = (0, workspace_path_1.workspaceDocumentRootKey)(workspacePath, mdkgDir);
|
|
125
|
+
for (const [existingAlias, entry] of Object.entries(workspaces)) {
|
|
126
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const existing = entry;
|
|
130
|
+
if (typeof existing.path === "string" &&
|
|
131
|
+
typeof existing.mdkg_dir === "string" &&
|
|
132
|
+
(0, workspace_path_1.workspaceDocumentRootKey)(existing.path, existing.mdkg_dir) === docRootKey) {
|
|
133
|
+
throw new errors_1.UsageError(`workspace document root already registered by ${existingAlias}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const workspace = { path: workspacePath, enabled: true, mdkg_dir: mdkgDir };
|
|
137
|
+
workspaces[alias] = workspace;
|
|
75
138
|
raw.workspaces = workspaces;
|
|
76
139
|
writeRawConfig(configPath, raw);
|
|
77
140
|
const wsRoot = path_1.default.resolve(options.root, workspacePath, mdkgDir);
|
|
78
141
|
fs_1.default.mkdirSync(path_1.default.join(wsRoot, "core"), { recursive: true });
|
|
79
142
|
fs_1.default.mkdirSync(path_1.default.join(wsRoot, "design"), { recursive: true });
|
|
80
143
|
fs_1.default.mkdirSync(path_1.default.join(wsRoot, "work"), { recursive: true });
|
|
81
|
-
|
|
144
|
+
printWorkspaceMutationReceipt("added", workspaceReceipt(alias, workspace), options.json);
|
|
82
145
|
}
|
|
83
146
|
function runWorkspaceRemoveCommand(options) {
|
|
84
147
|
const alias = normalizeAlias(options.alias);
|
|
@@ -91,11 +154,40 @@ function runWorkspaceRemoveCommand(options) {
|
|
|
91
154
|
throw new errors_1.UsageError("config.workspaces must be an object");
|
|
92
155
|
}
|
|
93
156
|
const workspaces = workspacesRaw;
|
|
94
|
-
|
|
157
|
+
const workspace = workspaces[alias];
|
|
158
|
+
if (!workspace || typeof workspace !== "object" || Array.isArray(workspace)) {
|
|
95
159
|
throw new errors_1.NotFoundError(`workspace not found: ${alias}`);
|
|
96
160
|
}
|
|
161
|
+
const removed = workspaceReceipt(alias, workspace);
|
|
97
162
|
delete workspaces[alias];
|
|
98
163
|
raw.workspaces = workspaces;
|
|
99
164
|
writeRawConfig(configPath, raw);
|
|
100
|
-
|
|
165
|
+
printWorkspaceMutationReceipt("removed", removed, options.json);
|
|
166
|
+
}
|
|
167
|
+
function setWorkspaceEnabled(options, enabled) {
|
|
168
|
+
const alias = normalizeAlias(options.alias);
|
|
169
|
+
if (alias === "root" && !enabled) {
|
|
170
|
+
throw new errors_1.UsageError("cannot disable root workspace");
|
|
171
|
+
}
|
|
172
|
+
const { path: configPath, raw } = readRawConfig(options.root);
|
|
173
|
+
const workspacesRaw = raw.workspaces;
|
|
174
|
+
if (typeof workspacesRaw !== "object" || workspacesRaw === null) {
|
|
175
|
+
throw new errors_1.UsageError("config.workspaces must be an object");
|
|
176
|
+
}
|
|
177
|
+
const workspaces = workspacesRaw;
|
|
178
|
+
const workspace = workspaces[alias];
|
|
179
|
+
if (!workspace || typeof workspace !== "object" || Array.isArray(workspace)) {
|
|
180
|
+
throw new errors_1.NotFoundError(`workspace not found: ${alias}`);
|
|
181
|
+
}
|
|
182
|
+
const updated = { ...workspace, enabled };
|
|
183
|
+
workspaces[alias] = updated;
|
|
184
|
+
raw.workspaces = workspaces;
|
|
185
|
+
writeRawConfig(configPath, raw);
|
|
186
|
+
printWorkspaceMutationReceipt(enabled ? "enabled" : "disabled", workspaceReceipt(alias, updated), options.json);
|
|
187
|
+
}
|
|
188
|
+
function runWorkspaceEnableCommand(options) {
|
|
189
|
+
setWorkspaceEnabled(options, true);
|
|
190
|
+
}
|
|
191
|
+
function runWorkspaceDisableCommand(options) {
|
|
192
|
+
setWorkspaceEnabled(options, false);
|
|
101
193
|
}
|