mdkg 0.0.7 → 0.0.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 +56 -0
- package/CONTRIBUTING.md +124 -0
- package/README.md +33 -10
- package/dist/cli.js +80 -32
- package/dist/commands/checkpoint.js +19 -2
- package/dist/commands/event.js +12 -0
- package/dist/commands/new.js +50 -4
- 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 +41 -4
- 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/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 +21 -5
- package/dist/graph/skills_indexer.js +14 -1
- package/dist/graph/validate_graph.js +302 -2
- package/dist/init/AGENT_START.md +13 -0
- package/dist/init/CLI_COMMAND_MATRIX.md +43 -1
- package/dist/init/README.md +7 -0
- package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +1 -1
- 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/util/argparse.js +1 -0
- package/dist/util/id.js +19 -0
- package/package.json +9 -2
- package/scripts/postinstall.js +89 -0
|
@@ -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
|
}
|
package/dist/core/config.js
CHANGED
|
@@ -3,10 +3,23 @@ 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.validateConfigSchema = validateConfigSchema;
|
|
6
7
|
exports.loadConfig = loadConfig;
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const paths_1 = require("./paths");
|
|
9
10
|
const migrate_1 = require("./migrate");
|
|
11
|
+
const workspace_path_1 = require("./workspace_path");
|
|
12
|
+
const WORKSPACE_ALIAS_RE = /^[a-z][a-z0-9_]*$/;
|
|
13
|
+
const PACK_EDGE_KEYS = new Set([
|
|
14
|
+
"parent",
|
|
15
|
+
"epic",
|
|
16
|
+
"relates",
|
|
17
|
+
"blocked_by",
|
|
18
|
+
"blocks",
|
|
19
|
+
"prev",
|
|
20
|
+
"next",
|
|
21
|
+
]);
|
|
22
|
+
const NEXT_WORK_STRATEGIES = new Set(["chain_then_priority"]);
|
|
10
23
|
function isObject(value) {
|
|
11
24
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
25
|
}
|
|
@@ -17,6 +30,17 @@ function requireString(value, path, errors) {
|
|
|
17
30
|
}
|
|
18
31
|
return value;
|
|
19
32
|
}
|
|
33
|
+
function requireStringInSet(value, path, allowed, errors) {
|
|
34
|
+
const raw = requireString(value, path, errors);
|
|
35
|
+
if (raw === undefined) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (!allowed.has(raw)) {
|
|
39
|
+
errors.push(`${path} must be one of ${Array.from(allowed).join(", ")}`);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return raw;
|
|
43
|
+
}
|
|
20
44
|
function requireBoolean(value, path, errors) {
|
|
21
45
|
if (typeof value !== "boolean") {
|
|
22
46
|
errors.push(`${path} must be a boolean`);
|
|
@@ -31,6 +55,39 @@ function requireNumber(value, path, errors) {
|
|
|
31
55
|
}
|
|
32
56
|
return value;
|
|
33
57
|
}
|
|
58
|
+
function requireInteger(value, path, errors) {
|
|
59
|
+
const number = requireNumber(value, path, errors);
|
|
60
|
+
if (number === undefined) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
if (!Number.isInteger(number)) {
|
|
64
|
+
errors.push(`${path} must be an integer`);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return number;
|
|
68
|
+
}
|
|
69
|
+
function requireNonNegativeInteger(value, path, errors) {
|
|
70
|
+
const integer = requireInteger(value, path, errors);
|
|
71
|
+
if (integer === undefined) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
if (integer < 0) {
|
|
75
|
+
errors.push(`${path} must be a non-negative integer`);
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return integer;
|
|
79
|
+
}
|
|
80
|
+
function requirePositiveInteger(value, path, errors) {
|
|
81
|
+
const integer = requireInteger(value, path, errors);
|
|
82
|
+
if (integer === undefined) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
if (integer <= 0) {
|
|
86
|
+
errors.push(`${path} must be a positive integer`);
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return integer;
|
|
90
|
+
}
|
|
34
91
|
function requireStringArray(value, path, errors) {
|
|
35
92
|
if (!Array.isArray(value)) {
|
|
36
93
|
errors.push(`${path} must be an array of strings`);
|
|
@@ -46,6 +103,53 @@ function requireStringArray(value, path, errors) {
|
|
|
46
103
|
}
|
|
47
104
|
return items;
|
|
48
105
|
}
|
|
106
|
+
function requireLowercaseUniqueStringArray(value, path, errors, allowEmpty = false) {
|
|
107
|
+
const items = requireStringArray(value, path, errors);
|
|
108
|
+
if (items === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (items.length === 0) {
|
|
112
|
+
if (!allowEmpty) {
|
|
113
|
+
errors.push(`${path} must not be empty`);
|
|
114
|
+
}
|
|
115
|
+
return items;
|
|
116
|
+
}
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
119
|
+
const item = items[i];
|
|
120
|
+
if (item.trim().length === 0) {
|
|
121
|
+
errors.push(`${path}[${i}] must not be empty`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (item !== item.trim()) {
|
|
125
|
+
errors.push(`${path}[${i}] must not include surrounding whitespace`);
|
|
126
|
+
}
|
|
127
|
+
if (item !== item.toLowerCase()) {
|
|
128
|
+
errors.push(`${path}[${i}] must be lowercase`);
|
|
129
|
+
}
|
|
130
|
+
if (seen.has(item)) {
|
|
131
|
+
errors.push(`${path} must not contain duplicate value "${item}"`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
seen.add(item);
|
|
135
|
+
}
|
|
136
|
+
return items;
|
|
137
|
+
}
|
|
138
|
+
function requireKnownLowercaseUniqueStringArray(value, path, allowed, errors, allowEmpty = false) {
|
|
139
|
+
const items = requireLowercaseUniqueStringArray(value, path, errors, allowEmpty);
|
|
140
|
+
if (items === undefined) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
144
|
+
if (items[i].trim().length === 0) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!allowed.has(items[i])) {
|
|
148
|
+
errors.push(`${path}[${i}] must be one of ${Array.from(allowed).join(", ")}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return items;
|
|
152
|
+
}
|
|
49
153
|
function requireObject(value, path, errors) {
|
|
50
154
|
if (!isObject(value)) {
|
|
51
155
|
errors.push(`${path} must be an object`);
|
|
@@ -53,6 +157,29 @@ function requireObject(value, path, errors) {
|
|
|
53
157
|
}
|
|
54
158
|
return value;
|
|
55
159
|
}
|
|
160
|
+
function validateWorkspaceAlias(alias, errors) {
|
|
161
|
+
if (alias === "all") {
|
|
162
|
+
errors.push("workspaces.all alias is reserved");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (alias !== alias.toLowerCase() || !WORKSPACE_ALIAS_RE.test(alias)) {
|
|
166
|
+
errors.push(`workspaces.${alias} alias must be lowercase and use [a-z0-9_]`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function requireContainedPath(value, path, errors) {
|
|
170
|
+
const raw = requireString(value, path, errors);
|
|
171
|
+
if (!raw) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
return (0, workspace_path_1.normalizeContainedWorkspacePath)(raw, path);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
179
|
+
errors.push(message);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
56
183
|
function validateConfigSchema(raw) {
|
|
57
184
|
const errors = [];
|
|
58
185
|
if (!isObject(raw)) {
|
|
@@ -70,50 +197,70 @@ function validateConfigSchema(raw) {
|
|
|
70
197
|
? {
|
|
71
198
|
auto_reindex: requireBoolean(indexRaw.auto_reindex, "index.auto_reindex", errors),
|
|
72
199
|
tolerant: requireBoolean(indexRaw.tolerant, "index.tolerant", errors),
|
|
73
|
-
global_index_path:
|
|
200
|
+
global_index_path: requireContainedPath(indexRaw.global_index_path, "index.global_index_path", errors),
|
|
74
201
|
}
|
|
75
202
|
: undefined;
|
|
76
203
|
const packLimitsRaw = packRaw ? requireObject(packRaw.limits, "pack.limits", errors) : undefined;
|
|
77
204
|
const pack = packRaw
|
|
78
205
|
? {
|
|
79
|
-
default_depth:
|
|
80
|
-
default_edges:
|
|
81
|
-
verbose_core_list_path:
|
|
206
|
+
default_depth: requireNonNegativeInteger(packRaw.default_depth, "pack.default_depth", errors),
|
|
207
|
+
default_edges: requireKnownLowercaseUniqueStringArray(packRaw.default_edges, "pack.default_edges", PACK_EDGE_KEYS, errors, true),
|
|
208
|
+
verbose_core_list_path: requireContainedPath(packRaw.verbose_core_list_path, "pack.verbose_core_list_path", errors),
|
|
82
209
|
limits: packLimitsRaw
|
|
83
210
|
? {
|
|
84
|
-
max_nodes:
|
|
85
|
-
max_bytes:
|
|
211
|
+
max_nodes: requirePositiveInteger(packLimitsRaw.max_nodes, "pack.limits.max_nodes", errors),
|
|
212
|
+
max_bytes: requirePositiveInteger(packLimitsRaw.max_bytes, "pack.limits.max_bytes", errors),
|
|
86
213
|
}
|
|
87
214
|
: undefined,
|
|
88
215
|
}
|
|
89
216
|
: undefined;
|
|
90
217
|
const templates = templatesRaw
|
|
91
218
|
? {
|
|
92
|
-
root_path:
|
|
219
|
+
root_path: requireContainedPath(templatesRaw.root_path, "templates.root_path", errors),
|
|
93
220
|
default_set: requireString(templatesRaw.default_set, "templates.default_set", errors),
|
|
94
221
|
workspace_overrides_enabled: requireBoolean(templatesRaw.workspace_overrides_enabled, "templates.workspace_overrides_enabled", errors),
|
|
95
222
|
}
|
|
96
223
|
: undefined;
|
|
97
224
|
const workNextRaw = workRaw ? requireObject(workRaw.next, "work.next", errors) : undefined;
|
|
98
225
|
const work = workRaw
|
|
99
|
-
? {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
226
|
+
? (() => {
|
|
227
|
+
const statusEnum = requireLowercaseUniqueStringArray(workRaw.status_enum, "work.status_enum", errors);
|
|
228
|
+
const priorityMin = requireInteger(workRaw.priority_min, "work.priority_min", errors);
|
|
229
|
+
const priorityMax = requireInteger(workRaw.priority_max, "work.priority_max", errors);
|
|
230
|
+
if (priorityMin !== undefined &&
|
|
231
|
+
priorityMax !== undefined &&
|
|
232
|
+
priorityMin > priorityMax) {
|
|
233
|
+
errors.push("work.priority_min must be less than or equal to work.priority_max");
|
|
234
|
+
}
|
|
235
|
+
const statusPreference = workNextRaw
|
|
236
|
+
? requireLowercaseUniqueStringArray(workNextRaw.status_preference, "work.next.status_preference", errors)
|
|
237
|
+
: undefined;
|
|
238
|
+
if (statusEnum !== undefined && statusPreference !== undefined) {
|
|
239
|
+
const allowedStatuses = new Set(statusEnum);
|
|
240
|
+
for (let i = 0; i < statusPreference.length; i += 1) {
|
|
241
|
+
if (!allowedStatuses.has(statusPreference[i])) {
|
|
242
|
+
errors.push(`work.next.status_preference[${i}] must be listed in work.status_enum`);
|
|
243
|
+
}
|
|
107
244
|
}
|
|
108
|
-
|
|
109
|
-
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
status_enum: statusEnum,
|
|
248
|
+
priority_min: priorityMin,
|
|
249
|
+
priority_max: priorityMax,
|
|
250
|
+
next: workNextRaw
|
|
251
|
+
? {
|
|
252
|
+
strategy: requireStringInSet(workNextRaw.strategy, "work.next.strategy", NEXT_WORK_STRATEGIES, errors),
|
|
253
|
+
status_preference: statusPreference,
|
|
254
|
+
}
|
|
255
|
+
: undefined,
|
|
256
|
+
};
|
|
257
|
+
})()
|
|
110
258
|
: undefined;
|
|
111
259
|
const workspaces = {};
|
|
260
|
+
const workspaceDocRootOwners = new Map();
|
|
112
261
|
if (workspacesRaw) {
|
|
113
262
|
for (const [alias, entry] of Object.entries(workspacesRaw)) {
|
|
114
|
-
|
|
115
|
-
errors.push(`workspaces.${alias} alias must be lowercase`);
|
|
116
|
-
}
|
|
263
|
+
validateWorkspaceAlias(alias, errors);
|
|
117
264
|
const ws = requireObject(entry, `workspaces.${alias}`, errors);
|
|
118
265
|
if (!ws) {
|
|
119
266
|
continue;
|
|
@@ -122,14 +269,62 @@ function validateConfigSchema(raw) {
|
|
|
122
269
|
const wsEnabled = requireBoolean(ws.enabled, `workspaces.${alias}.enabled`, errors);
|
|
123
270
|
const wsMdkgDir = requireString(ws.mdkg_dir, `workspaces.${alias}.mdkg_dir`, errors);
|
|
124
271
|
if (wsPath && wsEnabled !== undefined && wsMdkgDir) {
|
|
272
|
+
let normalizedPath;
|
|
273
|
+
let normalizedMdkgDir;
|
|
274
|
+
try {
|
|
275
|
+
normalizedPath = (0, workspace_path_1.normalizeContainedWorkspacePath)(wsPath, `workspaces.${alias}.path`);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
279
|
+
errors.push(message);
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
normalizedMdkgDir = (0, workspace_path_1.normalizeContainedWorkspacePath)(wsMdkgDir, `workspaces.${alias}.mdkg_dir`);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
286
|
+
errors.push(message);
|
|
287
|
+
}
|
|
288
|
+
if (!normalizedPath || !normalizedMdkgDir) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (alias !== "root" && (0, workspace_path_1.isRootWorkspacePath)(normalizedPath)) {
|
|
292
|
+
errors.push(`workspaces.${alias}.path must not be "." for non-root workspaces`);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const docRootKey = (0, workspace_path_1.workspaceDocumentRootKey)(normalizedPath, normalizedMdkgDir);
|
|
296
|
+
const existingAlias = workspaceDocRootOwners.get(docRootKey);
|
|
297
|
+
if (existingAlias) {
|
|
298
|
+
errors.push(`workspaces.${alias} document root duplicates workspaces.${existingAlias}`);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
workspaceDocRootOwners.set(docRootKey, alias);
|
|
125
302
|
workspaces[alias] = {
|
|
126
|
-
path:
|
|
303
|
+
path: normalizedPath,
|
|
127
304
|
enabled: wsEnabled,
|
|
128
|
-
mdkg_dir:
|
|
305
|
+
mdkg_dir: normalizedMdkgDir,
|
|
129
306
|
};
|
|
130
307
|
}
|
|
131
308
|
}
|
|
132
309
|
}
|
|
310
|
+
const rootWorkspace = workspaces.root;
|
|
311
|
+
if (root_required !== undefined && root_required !== true) {
|
|
312
|
+
errors.push("root_required must be true");
|
|
313
|
+
}
|
|
314
|
+
if (!rootWorkspace) {
|
|
315
|
+
errors.push("workspaces.root is required");
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
if (rootWorkspace.path !== ".") {
|
|
319
|
+
errors.push('workspaces.root.path must be "."');
|
|
320
|
+
}
|
|
321
|
+
if (rootWorkspace.enabled !== true) {
|
|
322
|
+
errors.push("workspaces.root.enabled must be true");
|
|
323
|
+
}
|
|
324
|
+
if (rootWorkspace.mdkg_dir !== ".mdkg") {
|
|
325
|
+
errors.push('workspaces.root.mdkg_dir must be ".mdkg"');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
133
328
|
if (errors.length > 0) {
|
|
134
329
|
throw new Error(`config validation failed:\n${errors.join("\n")}`);
|
|
135
330
|
}
|
package/dist/core/migrate.js
CHANGED
|
@@ -3,15 +3,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LATEST_SCHEMA_VERSION = void 0;
|
|
4
4
|
exports.migrateConfig = migrateConfig;
|
|
5
5
|
exports.LATEST_SCHEMA_VERSION = 1;
|
|
6
|
-
const
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const LEGACY_SCHEMA_VERSION = 0;
|
|
7
|
+
function isJsonObject(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function getSchemaVersion(raw) {
|
|
11
11
|
const version = raw.schema_version;
|
|
12
|
+
if (version === undefined) {
|
|
13
|
+
return LEGACY_SCHEMA_VERSION;
|
|
14
|
+
}
|
|
12
15
|
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
13
16
|
throw new Error("config schema_version must be an integer");
|
|
14
17
|
}
|
|
18
|
+
if (version < LEGACY_SCHEMA_VERSION) {
|
|
19
|
+
throw new Error("config schema_version must be non-negative");
|
|
20
|
+
}
|
|
21
|
+
return version;
|
|
22
|
+
}
|
|
23
|
+
function migrateLegacyConfig(input) {
|
|
24
|
+
if (!isJsonObject(input)) {
|
|
25
|
+
throw new Error("config must be a JSON object");
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...input,
|
|
29
|
+
schema_version: 1,
|
|
30
|
+
workspaces: input.workspaces === undefined
|
|
31
|
+
? {
|
|
32
|
+
root: {
|
|
33
|
+
path: ".",
|
|
34
|
+
enabled: true,
|
|
35
|
+
mdkg_dir: ".mdkg",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
: input.workspaces,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const MIGRATIONS = {
|
|
42
|
+
[LEGACY_SCHEMA_VERSION]: migrateLegacyConfig,
|
|
43
|
+
};
|
|
44
|
+
function migrateConfig(raw) {
|
|
45
|
+
if (!isJsonObject(raw)) {
|
|
46
|
+
throw new Error("config must be a JSON object");
|
|
47
|
+
}
|
|
48
|
+
const version = getSchemaVersion(raw);
|
|
15
49
|
if (version > exports.LATEST_SCHEMA_VERSION) {
|
|
16
50
|
throw new Error(`config schema_version ${version} is newer than supported ${exports.LATEST_SCHEMA_VERSION}`);
|
|
17
51
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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.normalizeContainedWorkspacePath = normalizeContainedWorkspacePath;
|
|
7
|
+
exports.isRootWorkspacePath = isRootWorkspacePath;
|
|
8
|
+
exports.workspaceDocumentRootKey = workspaceDocumentRootKey;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function isAbsoluteOnSupportedPlatform(value) {
|
|
11
|
+
return path_1.default.isAbsolute(value) || path_1.default.posix.isAbsolute(value) || path_1.default.win32.isAbsolute(value);
|
|
12
|
+
}
|
|
13
|
+
function normalizeContainedWorkspacePath(value, label) {
|
|
14
|
+
const normalized = value.trim();
|
|
15
|
+
if (!normalized) {
|
|
16
|
+
throw new Error(`${label} cannot be empty`);
|
|
17
|
+
}
|
|
18
|
+
if (normalized.includes("\0")) {
|
|
19
|
+
throw new Error(`${label} cannot contain NUL bytes`);
|
|
20
|
+
}
|
|
21
|
+
if (isAbsoluteOnSupportedPlatform(normalized)) {
|
|
22
|
+
throw new Error(`${label} must be relative`);
|
|
23
|
+
}
|
|
24
|
+
if (normalized.split(/[\\/]+/).some((part) => part === "..")) {
|
|
25
|
+
throw new Error(`${label} cannot contain parent-directory components`);
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
function isRootWorkspacePath(value) {
|
|
30
|
+
const parts = value
|
|
31
|
+
.trim()
|
|
32
|
+
.split(/[\\/]+/)
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
return parts.length > 0 && parts.every((part) => part === ".");
|
|
35
|
+
}
|
|
36
|
+
function workspaceDocumentRootKey(workspacePath, mdkgDir) {
|
|
37
|
+
return [workspacePath, mdkgDir]
|
|
38
|
+
.flatMap((value) => value.trim().split(/[\\/]+/))
|
|
39
|
+
.filter((part) => part && part !== ".")
|
|
40
|
+
.join("/");
|
|
41
|
+
}
|