mdkg 0.3.0 → 0.3.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 +90 -1
- package/CLI_COMMAND_MATRIX.md +1188 -0
- package/README.md +86 -5
- package/dist/cli.js +130 -10
- package/dist/command-contract.json +7473 -0
- package/dist/commands/doctor.js +370 -86
- package/dist/commands/fix.js +934 -0
- package/dist/commands/format.js +8 -2
- package/dist/commands/goal.js +1 -1
- package/dist/commands/next.js +2 -2
- package/dist/commands/skill.js +13 -3
- package/dist/commands/skill_support.js +3 -3
- package/dist/commands/status.js +270 -0
- package/dist/commands/subgraph.js +300 -0
- package/dist/commands/task.js +2 -2
- package/dist/commands/validate.js +12 -1
- package/dist/commands/workspace.js +19 -7
- package/dist/graph/goal_scope.js +1 -1
- package/dist/graph/node.js +2 -1
- package/dist/init/CLI_COMMAND_MATRIX.md +20 -2
- package/dist/init/README.md +35 -2
- package/dist/init/core/rule-5-release-and-versioning.md +13 -4
- package/dist/init/init-manifest.json +9 -4
- package/dist/init/templates/default/spike.md +81 -0
- package/dist/pack/order.js +3 -2
- package/dist/util/argparse.js +1 -0
- package/package.json +11 -3
package/dist/commands/doctor.js
CHANGED
|
@@ -15,10 +15,35 @@ const template_schema_1 = require("../graph/template_schema");
|
|
|
15
15
|
const visibility_1 = require("../graph/visibility");
|
|
16
16
|
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
17
17
|
const project_db_1 = require("../core/project_db");
|
|
18
|
+
const project_db_migrations_1 = require("../core/project_db_migrations");
|
|
18
19
|
const errors_1 = require("../util/errors");
|
|
19
20
|
const REQUIRED_NODE_MAJOR = 24;
|
|
20
21
|
const REQUIRED_NODE_MINOR = 15;
|
|
21
22
|
const ARCHIVE_RAW_ALLOWED_DIRS = new Set(["source"]);
|
|
23
|
+
const SELECTED_GOAL_STATE_PATH = path_1.default.join(".mdkg", "state", "selected-goal.json");
|
|
24
|
+
function makeCheck(input) {
|
|
25
|
+
const level = input.level ?? (input.ok ? undefined : "fail");
|
|
26
|
+
const effectiveLevel = level ?? "ok";
|
|
27
|
+
const status = !input.ok || effectiveLevel === "fail" ? "fail" : effectiveLevel === "warn" ? "warn" : "pass";
|
|
28
|
+
const severity = status === "fail" ? "error" : status === "warn" ? "warning" : "info";
|
|
29
|
+
return {
|
|
30
|
+
id: input.id,
|
|
31
|
+
name: input.name,
|
|
32
|
+
ok: input.ok,
|
|
33
|
+
level,
|
|
34
|
+
detail: input.detail,
|
|
35
|
+
status,
|
|
36
|
+
severity,
|
|
37
|
+
message: input.message ?? input.detail,
|
|
38
|
+
remediation: input.remediation ?? "No action required.",
|
|
39
|
+
refs: input.refs,
|
|
40
|
+
strictFail: input.strictFail,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function publicCheck(result) {
|
|
44
|
+
const { strictFail: _strictFail, ...publicResult } = result;
|
|
45
|
+
return publicResult;
|
|
46
|
+
}
|
|
22
47
|
function parseNodeVersion(version) {
|
|
23
48
|
const [majorRaw, minorRaw, patchRaw] = version.split(".");
|
|
24
49
|
const major = Number.parseInt(majorRaw ?? "", 10);
|
|
@@ -33,55 +58,67 @@ function runNodeVersionCheck() {
|
|
|
33
58
|
const nodeVersion = process.versions.node;
|
|
34
59
|
const parsed = parseNodeVersion(nodeVersion);
|
|
35
60
|
if (parsed === null) {
|
|
36
|
-
return {
|
|
61
|
+
return makeCheck({
|
|
62
|
+
id: "runtime.node_version",
|
|
37
63
|
name: "node-version",
|
|
38
64
|
ok: false,
|
|
39
65
|
detail: `unable to parse Node.js version: ${nodeVersion}`,
|
|
40
|
-
|
|
66
|
+
remediation: `Run mdkg with Node.js >=${REQUIRED_NODE_MAJOR}.${REQUIRED_NODE_MINOR}.0.`,
|
|
67
|
+
});
|
|
41
68
|
}
|
|
42
69
|
if (parsed.major < REQUIRED_NODE_MAJOR ||
|
|
43
70
|
(parsed.major === REQUIRED_NODE_MAJOR && parsed.minor < REQUIRED_NODE_MINOR)) {
|
|
44
|
-
return {
|
|
71
|
+
return makeCheck({
|
|
72
|
+
id: "runtime.node_version",
|
|
45
73
|
name: "node-version",
|
|
46
74
|
ok: false,
|
|
47
75
|
detail: `Node.js ${nodeVersion} is unsupported (requires >=${REQUIRED_NODE_MAJOR}.${REQUIRED_NODE_MINOR}.0)`,
|
|
48
|
-
|
|
76
|
+
remediation: `Install Node.js >=${REQUIRED_NODE_MAJOR}.${REQUIRED_NODE_MINOR}.0 and rerun mdkg.`,
|
|
77
|
+
});
|
|
49
78
|
}
|
|
50
|
-
return {
|
|
79
|
+
return makeCheck({
|
|
80
|
+
id: "runtime.node_version",
|
|
51
81
|
name: "node-version",
|
|
52
82
|
ok: true,
|
|
53
83
|
detail: `Node.js ${nodeVersion} (ok)`,
|
|
54
|
-
};
|
|
84
|
+
});
|
|
55
85
|
}
|
|
56
86
|
function runSqliteCheck(root, config) {
|
|
57
87
|
if (!(0, sqlite_index_1.isSqliteBackend)(config)) {
|
|
58
|
-
return {
|
|
88
|
+
return makeCheck({
|
|
89
|
+
id: "graph.sqlite_cache",
|
|
59
90
|
name: "sqlite-cache",
|
|
60
91
|
ok: true,
|
|
61
92
|
detail: "SQLite backend disabled; JSON cache backend active",
|
|
62
|
-
};
|
|
93
|
+
});
|
|
63
94
|
}
|
|
64
95
|
const health = (0, sqlite_index_1.sqliteHealth)(root, config);
|
|
65
96
|
if (health.errors.length > 0) {
|
|
66
|
-
return {
|
|
97
|
+
return makeCheck({
|
|
98
|
+
id: "graph.sqlite_cache",
|
|
67
99
|
name: "sqlite-cache",
|
|
68
100
|
ok: false,
|
|
69
101
|
detail: health.errors.join("; "),
|
|
70
|
-
|
|
102
|
+
remediation: "Run `mdkg index` to rebuild the SQLite graph cache.",
|
|
103
|
+
});
|
|
71
104
|
}
|
|
72
105
|
if (health.warnings.length > 0) {
|
|
73
|
-
return {
|
|
106
|
+
return makeCheck({
|
|
107
|
+
id: "graph.sqlite_cache",
|
|
74
108
|
name: "sqlite-cache",
|
|
75
109
|
ok: true,
|
|
76
110
|
level: "warn",
|
|
77
111
|
detail: health.warnings.join("; "),
|
|
78
|
-
|
|
112
|
+
remediation: "Run `mdkg index` to refresh the SQLite graph cache.",
|
|
113
|
+
strictFail: true,
|
|
114
|
+
});
|
|
79
115
|
}
|
|
80
|
-
return {
|
|
116
|
+
return makeCheck({
|
|
117
|
+
id: "graph.sqlite_cache",
|
|
81
118
|
name: "sqlite-cache",
|
|
82
119
|
ok: true,
|
|
83
120
|
detail: `.mdkg SQLite cache ok (${health.size} bytes)`,
|
|
84
|
-
};
|
|
121
|
+
});
|
|
85
122
|
}
|
|
86
123
|
function walkFiles(root) {
|
|
87
124
|
if (!fs_1.default.existsSync(root)) {
|
|
@@ -115,26 +152,31 @@ function runArchiveStorageCheck(root) {
|
|
|
115
152
|
.map((filePath) => path_1.default.relative(root, filePath).split(path_1.default.sep).join("/"))
|
|
116
153
|
.sort();
|
|
117
154
|
if (strayRaw.length === 0) {
|
|
118
|
-
return {
|
|
155
|
+
return makeCheck({
|
|
156
|
+
id: "archive.storage",
|
|
119
157
|
name: "archive-storage",
|
|
120
158
|
ok: true,
|
|
121
159
|
detail: ".mdkg/archive has no stray raw files outside managed source directories",
|
|
122
|
-
};
|
|
160
|
+
});
|
|
123
161
|
}
|
|
124
|
-
return {
|
|
162
|
+
return makeCheck({
|
|
163
|
+
id: "archive.storage",
|
|
125
164
|
name: "archive-storage",
|
|
126
165
|
ok: true,
|
|
127
166
|
level: "warn",
|
|
128
167
|
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`,
|
|
129
|
-
|
|
168
|
+
remediation: "Run `mdkg archive add <file>` or move raw files under a managed archive source directory.",
|
|
169
|
+
refs: strayRaw,
|
|
170
|
+
});
|
|
130
171
|
}
|
|
131
172
|
function runArchiveLargeCacheCheck(root, warningBytes) {
|
|
132
173
|
if (warningBytes === 0) {
|
|
133
|
-
return {
|
|
174
|
+
return makeCheck({
|
|
175
|
+
id: "archive.large_cache",
|
|
134
176
|
name: "archive-large-cache",
|
|
135
177
|
ok: true,
|
|
136
178
|
detail: "archive large-cache warning disabled",
|
|
137
|
-
};
|
|
179
|
+
});
|
|
138
180
|
}
|
|
139
181
|
const archiveRoot = path_1.default.join(root, ".mdkg", "archive");
|
|
140
182
|
const largeCaches = walkFiles(archiveRoot)
|
|
@@ -146,103 +188,130 @@ function runArchiveLargeCacheCheck(root, warningBytes) {
|
|
|
146
188
|
.filter((entry) => entry.size > warningBytes)
|
|
147
189
|
.sort((a, b) => a.path.localeCompare(b.path));
|
|
148
190
|
if (largeCaches.length === 0) {
|
|
149
|
-
return {
|
|
191
|
+
return makeCheck({
|
|
192
|
+
id: "archive.large_cache",
|
|
150
193
|
name: "archive-large-cache",
|
|
151
194
|
ok: true,
|
|
152
195
|
detail: `no archive ZIP cache exceeds ${warningBytes} bytes`,
|
|
153
|
-
};
|
|
196
|
+
});
|
|
154
197
|
}
|
|
155
|
-
return {
|
|
198
|
+
return makeCheck({
|
|
199
|
+
id: "archive.large_cache",
|
|
156
200
|
name: "archive-large-cache",
|
|
157
201
|
ok: true,
|
|
158
202
|
level: "warn",
|
|
159
203
|
detail: `archive ZIP cache(s) exceed ${warningBytes} bytes: ${largeCaches
|
|
160
204
|
.map((entry) => `${entry.path} (${entry.size} bytes)`)
|
|
161
205
|
.join(", ")}; keep large caches private or move bulky originals to repo policy-managed storage`,
|
|
162
|
-
|
|
206
|
+
remediation: "Keep large caches private or move bulky originals to repo policy-managed storage.",
|
|
207
|
+
refs: largeCaches.map((entry) => entry.path),
|
|
208
|
+
});
|
|
163
209
|
}
|
|
164
210
|
function runBundleStorageCheck(root, outputDir) {
|
|
165
211
|
const bundleRoot = path_1.default.resolve(root, outputDir);
|
|
166
212
|
if (!fs_1.default.existsSync(bundleRoot)) {
|
|
167
|
-
return {
|
|
213
|
+
return makeCheck({
|
|
214
|
+
id: "bundle.storage",
|
|
168
215
|
name: "bundle-storage",
|
|
169
216
|
ok: true,
|
|
170
217
|
detail: `no bundle directory found; run \`mdkg bundle create --profile private\` when a snapshot should be tracked`,
|
|
171
|
-
|
|
218
|
+
remediation: "Run `mdkg bundle create --profile private` when a snapshot should be tracked.",
|
|
219
|
+
});
|
|
172
220
|
}
|
|
173
221
|
const bundles = walkFiles(bundleRoot)
|
|
174
222
|
.filter((filePath) => filePath.endsWith(".mdkg.zip"))
|
|
175
223
|
.map((filePath) => path_1.default.relative(root, filePath).split(path_1.default.sep).join("/"))
|
|
176
224
|
.sort();
|
|
177
225
|
if (bundles.length === 0) {
|
|
178
|
-
return {
|
|
226
|
+
return makeCheck({
|
|
227
|
+
id: "bundle.storage",
|
|
179
228
|
name: "bundle-storage",
|
|
180
229
|
ok: true,
|
|
181
230
|
detail: `bundle directory has no .mdkg.zip files; run \`mdkg bundle create --profile private\` when a snapshot should be tracked`,
|
|
182
|
-
|
|
231
|
+
remediation: "Run `mdkg bundle create --profile private` when a snapshot should be tracked.",
|
|
232
|
+
});
|
|
183
233
|
}
|
|
184
|
-
return {
|
|
234
|
+
return makeCheck({
|
|
235
|
+
id: "bundle.storage",
|
|
185
236
|
name: "bundle-storage",
|
|
186
237
|
ok: true,
|
|
187
238
|
detail: `${bundles.length} bundle(s) found; run \`mdkg bundle verify <path>\` to check freshness before handoff`,
|
|
188
|
-
|
|
239
|
+
remediation: "Run `mdkg bundle verify <path>` to check freshness before handoff.",
|
|
240
|
+
refs: bundles,
|
|
241
|
+
});
|
|
189
242
|
}
|
|
190
243
|
function runProjectDbRuntimePolicyCheck(root) {
|
|
191
244
|
const files = (0, project_db_1.listProjectDbRuntimePolicyFiles)(root);
|
|
192
245
|
if (files.length === 0) {
|
|
193
|
-
return {
|
|
246
|
+
return makeCheck({
|
|
247
|
+
id: "db.runtime_transient_files",
|
|
194
248
|
name: "project-db-runtime",
|
|
195
249
|
ok: true,
|
|
196
250
|
detail: "no active project DB runtime or transient files found",
|
|
197
|
-
};
|
|
251
|
+
});
|
|
198
252
|
}
|
|
199
|
-
return {
|
|
253
|
+
return makeCheck({
|
|
254
|
+
id: "db.runtime_transient_files",
|
|
200
255
|
name: "project-db-runtime",
|
|
201
256
|
ok: true,
|
|
202
257
|
level: "warn",
|
|
203
258
|
detail: `active project DB runtime/transient file(s) are local-only and should not be committed: ${files.join(", ")}`,
|
|
204
|
-
|
|
259
|
+
remediation: "Keep runtime DB and transient files ignored; commit sealed state only by explicit repo policy.",
|
|
260
|
+
refs: files,
|
|
261
|
+
});
|
|
205
262
|
}
|
|
206
263
|
function runSubgraphChecks(root, config) {
|
|
207
264
|
const projection = (0, subgraphs_1.buildSubgraphsIndex)(root, config);
|
|
208
265
|
if (projection.index.subgraphs.length === 0) {
|
|
209
266
|
return [
|
|
210
|
-
{
|
|
267
|
+
makeCheck({
|
|
268
|
+
id: "subgraph.configured_state",
|
|
211
269
|
name: "subgraphs",
|
|
212
270
|
ok: true,
|
|
213
271
|
detail: "no subgraphs configured",
|
|
214
|
-
},
|
|
272
|
+
}),
|
|
215
273
|
];
|
|
216
274
|
}
|
|
217
275
|
return projection.index.subgraphs.map((item) => {
|
|
218
276
|
if (!item.enabled) {
|
|
219
|
-
return {
|
|
277
|
+
return makeCheck({
|
|
278
|
+
id: "subgraph.configured_state",
|
|
220
279
|
name: `subgraph:${item.alias}`,
|
|
221
280
|
ok: true,
|
|
222
281
|
level: "warn",
|
|
223
282
|
detail: "disabled subgraph",
|
|
224
|
-
|
|
283
|
+
remediation: `Run \`mdkg subgraph enable ${item.alias}\` if this subgraph should participate in graph views.`,
|
|
284
|
+
refs: [item.alias],
|
|
285
|
+
});
|
|
225
286
|
}
|
|
226
287
|
if (item.error_count > 0) {
|
|
227
|
-
return {
|
|
288
|
+
return makeCheck({
|
|
289
|
+
id: "subgraph.configured_state",
|
|
228
290
|
name: `subgraph:${item.alias}`,
|
|
229
291
|
ok: false,
|
|
230
292
|
detail: item.errors.join("; "),
|
|
231
|
-
|
|
293
|
+
remediation: `Run \`mdkg subgraph verify ${item.alias} --json\` and refresh or remove the failing bundle source.`,
|
|
294
|
+
refs: [item.alias],
|
|
295
|
+
});
|
|
232
296
|
}
|
|
233
297
|
if (item.stale || item.warning_count > 0) {
|
|
234
|
-
return {
|
|
298
|
+
return makeCheck({
|
|
299
|
+
id: "subgraph.configured_state",
|
|
235
300
|
name: `subgraph:${item.alias}`,
|
|
236
301
|
ok: true,
|
|
237
302
|
level: "warn",
|
|
238
303
|
detail: `subgraph is stale or has warnings; run \`mdkg subgraph verify ${item.alias}\` (${item.warnings.join("; ")})`,
|
|
239
|
-
|
|
304
|
+
remediation: `Run \`mdkg subgraph verify ${item.alias}\` and refresh stale sources if needed.`,
|
|
305
|
+
refs: [item.alias],
|
|
306
|
+
});
|
|
240
307
|
}
|
|
241
|
-
return {
|
|
308
|
+
return makeCheck({
|
|
309
|
+
id: "subgraph.configured_state",
|
|
242
310
|
name: `subgraph:${item.alias}`,
|
|
243
311
|
ok: true,
|
|
244
312
|
detail: `subgraph loaded from ${item.sources.map((source) => source.path).join(", ")}`,
|
|
245
|
-
|
|
313
|
+
refs: [item.alias],
|
|
314
|
+
});
|
|
246
315
|
});
|
|
247
316
|
}
|
|
248
317
|
function runVisibilityPolicyCheck(root, config, options) {
|
|
@@ -251,184 +320,399 @@ function runVisibilityPolicyCheck(root, config, options) {
|
|
|
251
320
|
root,
|
|
252
321
|
config,
|
|
253
322
|
useCache: !options.noCache,
|
|
254
|
-
allowReindex: !options.noReindex,
|
|
323
|
+
allowReindex: !options.noReindex && !options.strict,
|
|
255
324
|
});
|
|
256
325
|
const messages = (0, visibility_1.visibilityViolationMessages)((0, visibility_1.collectVisibilityViolations)(index, config));
|
|
257
326
|
if (messages.length === 0) {
|
|
258
|
-
return {
|
|
327
|
+
return makeCheck({
|
|
328
|
+
id: "visibility.policy",
|
|
259
329
|
name: "visibility-policy",
|
|
260
330
|
ok: true,
|
|
261
331
|
detail: "public/internal records do not reference less-visible mdkg records",
|
|
262
|
-
};
|
|
332
|
+
});
|
|
263
333
|
}
|
|
264
|
-
return {
|
|
334
|
+
return makeCheck({
|
|
335
|
+
id: "visibility.policy",
|
|
265
336
|
name: "visibility-policy",
|
|
266
337
|
ok: false,
|
|
267
338
|
detail: `${messages.length} violation(s): ${messages.join("; ")}`,
|
|
268
|
-
|
|
339
|
+
remediation: "Adjust visibility metadata or remove references from more-visible records to less-visible records.",
|
|
340
|
+
});
|
|
269
341
|
}
|
|
270
342
|
catch (err) {
|
|
271
343
|
const message = err instanceof Error ? err.message : String(err);
|
|
272
|
-
return {
|
|
344
|
+
return makeCheck({
|
|
345
|
+
id: "visibility.policy",
|
|
273
346
|
name: "visibility-policy",
|
|
274
347
|
ok: false,
|
|
275
348
|
detail: message,
|
|
276
|
-
|
|
349
|
+
remediation: "Run `mdkg validate --json` for graph visibility diagnostics.",
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function readSelectedGoalState(root) {
|
|
354
|
+
const filePath = path_1.default.join(root, SELECTED_GOAL_STATE_PATH);
|
|
355
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
356
|
+
return {};
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(filePath, "utf8"));
|
|
360
|
+
if (typeof parsed.qid === "string" &&
|
|
361
|
+
typeof parsed.id === "string" &&
|
|
362
|
+
typeof parsed.ws === "string" &&
|
|
363
|
+
typeof parsed.selected_at === "string") {
|
|
364
|
+
return {
|
|
365
|
+
state: {
|
|
366
|
+
qid: parsed.qid.toLowerCase(),
|
|
367
|
+
id: parsed.id.toLowerCase(),
|
|
368
|
+
ws: parsed.ws.toLowerCase(),
|
|
369
|
+
selected_at: parsed.selected_at,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return { warning: "selected goal state is malformed" };
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return { warning: "selected goal state is unreadable" };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function runSelectedGoalChecks(root, config, options) {
|
|
380
|
+
const selected = readSelectedGoalState(root);
|
|
381
|
+
if (selected.warning) {
|
|
382
|
+
return [
|
|
383
|
+
makeCheck({
|
|
384
|
+
id: "goal.selected_missing",
|
|
385
|
+
name: "selected-goal",
|
|
386
|
+
ok: true,
|
|
387
|
+
level: "warn",
|
|
388
|
+
detail: selected.warning,
|
|
389
|
+
remediation: "Run `mdkg goal select <goal-id>` or `mdkg goal clear`.",
|
|
390
|
+
strictFail: true,
|
|
391
|
+
}),
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
if (!selected.state) {
|
|
395
|
+
return [
|
|
396
|
+
makeCheck({
|
|
397
|
+
id: "goal.selected_missing",
|
|
398
|
+
name: "selected-goal",
|
|
399
|
+
ok: true,
|
|
400
|
+
detail: "no selected goal state found",
|
|
401
|
+
remediation: "Run `mdkg goal select <goal-id>` when pursuing a long-running goal.",
|
|
402
|
+
}),
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const { index } = (0, index_cache_1.loadIndex)({
|
|
407
|
+
root,
|
|
408
|
+
config,
|
|
409
|
+
useCache: !options.noCache,
|
|
410
|
+
allowReindex: !options.noReindex && !options.strict,
|
|
411
|
+
});
|
|
412
|
+
const node = index.nodes[selected.state.qid];
|
|
413
|
+
if (!node) {
|
|
414
|
+
return [
|
|
415
|
+
makeCheck({
|
|
416
|
+
id: "goal.selected_missing",
|
|
417
|
+
name: "selected-goal",
|
|
418
|
+
ok: true,
|
|
419
|
+
level: "warn",
|
|
420
|
+
detail: `selected goal ${selected.state.qid} is missing from the graph`,
|
|
421
|
+
remediation: "Run `mdkg goal select <active-goal>` or `mdkg goal clear`.",
|
|
422
|
+
refs: [selected.state.qid],
|
|
423
|
+
strictFail: true,
|
|
424
|
+
}),
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
const achieved = node.status === "done" || String(node.attributes.goal_state ?? "") === "achieved";
|
|
428
|
+
if (achieved) {
|
|
429
|
+
return [
|
|
430
|
+
makeCheck({
|
|
431
|
+
id: "goal.selected_achieved",
|
|
432
|
+
name: "selected-goal",
|
|
433
|
+
ok: true,
|
|
434
|
+
level: "warn",
|
|
435
|
+
detail: `selected goal ${selected.state.qid} is achieved but still current`,
|
|
436
|
+
remediation: "Run `mdkg goal select <active-goal>` or `mdkg goal clear`.",
|
|
437
|
+
refs: [selected.state.qid],
|
|
438
|
+
strictFail: true,
|
|
439
|
+
}),
|
|
440
|
+
];
|
|
441
|
+
}
|
|
442
|
+
return [
|
|
443
|
+
makeCheck({
|
|
444
|
+
id: "goal.selected_achieved",
|
|
445
|
+
name: "selected-goal",
|
|
446
|
+
ok: true,
|
|
447
|
+
detail: `selected goal ${selected.state.qid} is active`,
|
|
448
|
+
refs: [selected.state.qid],
|
|
449
|
+
}),
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
454
|
+
return [
|
|
455
|
+
makeCheck({
|
|
456
|
+
id: "goal.selected_missing",
|
|
457
|
+
name: "selected-goal",
|
|
458
|
+
ok: true,
|
|
459
|
+
level: "warn",
|
|
460
|
+
detail: `selected goal could not be checked: ${message}`,
|
|
461
|
+
remediation: "Run `mdkg index` and `mdkg goal current --json` to inspect selected-goal state.",
|
|
462
|
+
refs: [selected.state.qid],
|
|
463
|
+
strictFail: true,
|
|
464
|
+
}),
|
|
465
|
+
];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function runProjectDbVerifyCheck(root, config) {
|
|
469
|
+
if (!config.db.enabled) {
|
|
470
|
+
return makeCheck({
|
|
471
|
+
id: "db.project_verify",
|
|
472
|
+
name: "project-db-verify",
|
|
473
|
+
ok: true,
|
|
474
|
+
detail: "project DB is disabled",
|
|
475
|
+
remediation: "Run `mdkg db init` only when project DB state is needed.",
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
const verification = (0, project_db_migrations_1.verifyProjectDb)(root, config);
|
|
479
|
+
if (verification.ok) {
|
|
480
|
+
return makeCheck({
|
|
481
|
+
id: "db.project_verify",
|
|
482
|
+
name: "project-db-verify",
|
|
483
|
+
ok: true,
|
|
484
|
+
detail: `project DB verified (${verification.database})`,
|
|
485
|
+
refs: [verification.database],
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return makeCheck({
|
|
489
|
+
id: "db.project_verify",
|
|
490
|
+
name: "project-db-verify",
|
|
491
|
+
ok: true,
|
|
492
|
+
level: "warn",
|
|
493
|
+
detail: verification.errors.join("; "),
|
|
494
|
+
remediation: "Run `mdkg db verify --json`, then `mdkg db init` or `mdkg db migrate` as directed.",
|
|
495
|
+
refs: [verification.database],
|
|
496
|
+
strictFail: true,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function applyStrict(results, strict) {
|
|
500
|
+
if (!strict) {
|
|
501
|
+
return results;
|
|
277
502
|
}
|
|
503
|
+
return results.map((result) => {
|
|
504
|
+
if (!result.strictFail || !result.ok) {
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
...result,
|
|
509
|
+
ok: false,
|
|
510
|
+
level: "fail",
|
|
511
|
+
status: "fail",
|
|
512
|
+
severity: "error",
|
|
513
|
+
};
|
|
514
|
+
});
|
|
278
515
|
}
|
|
279
516
|
function runDoctorCommand(options) {
|
|
280
517
|
const results = [];
|
|
518
|
+
const strict = options.strict ?? false;
|
|
281
519
|
results.push(runNodeVersionCheck());
|
|
282
520
|
const configPath = path_1.default.resolve(options.root, ".mdkg", "config.json");
|
|
283
521
|
if (!fs_1.default.existsSync(configPath)) {
|
|
284
|
-
results.push({
|
|
522
|
+
results.push(makeCheck({
|
|
523
|
+
id: "repo.root_config",
|
|
285
524
|
name: "config",
|
|
286
525
|
ok: false,
|
|
287
526
|
detail: `missing config at ${configPath}`,
|
|
288
|
-
|
|
527
|
+
remediation: "Run from a repo root, pass `--root <path>`, or run `mdkg init`.",
|
|
528
|
+
refs: [path_1.default.relative(options.root, configPath).split(path_1.default.sep).join("/")],
|
|
529
|
+
}));
|
|
289
530
|
}
|
|
290
531
|
else {
|
|
291
|
-
results.push({
|
|
532
|
+
results.push(makeCheck({
|
|
533
|
+
id: "repo.root_config",
|
|
292
534
|
name: "config",
|
|
293
535
|
ok: true,
|
|
294
536
|
detail: `found ${configPath}`,
|
|
295
|
-
|
|
537
|
+
refs: [path_1.default.relative(options.root, configPath).split(path_1.default.sep).join("/")],
|
|
538
|
+
}));
|
|
296
539
|
}
|
|
297
540
|
let config;
|
|
298
541
|
try {
|
|
299
542
|
config = (0, config_1.loadConfig)(options.root);
|
|
300
|
-
results.push({
|
|
543
|
+
results.push(makeCheck({
|
|
544
|
+
id: "repo.root_config",
|
|
301
545
|
name: "config-schema",
|
|
302
546
|
ok: true,
|
|
303
547
|
detail: "config schema valid",
|
|
304
|
-
});
|
|
548
|
+
}));
|
|
305
549
|
}
|
|
306
550
|
catch (err) {
|
|
307
551
|
const message = err instanceof Error ? err.message : String(err);
|
|
308
|
-
results.push({
|
|
552
|
+
results.push(makeCheck({
|
|
553
|
+
id: "repo.root_config",
|
|
309
554
|
name: "config-schema",
|
|
310
555
|
ok: false,
|
|
311
556
|
detail: message,
|
|
312
|
-
|
|
557
|
+
remediation: "Fix `.mdkg/config.json` and rerun `mdkg doctor`.",
|
|
558
|
+
refs: [".mdkg/config.json"],
|
|
559
|
+
}));
|
|
313
560
|
}
|
|
314
561
|
if (config) {
|
|
315
562
|
results.push(runArchiveStorageCheck(options.root));
|
|
316
563
|
results.push(runArchiveLargeCacheCheck(options.root, config.archive.large_cache_warning_bytes));
|
|
317
564
|
results.push(runBundleStorageCheck(options.root, config.bundles.output_dir));
|
|
318
565
|
results.push(runProjectDbRuntimePolicyCheck(options.root));
|
|
566
|
+
results.push(runProjectDbVerifyCheck(options.root, config));
|
|
319
567
|
results.push(runSqliteCheck(options.root, config));
|
|
320
568
|
results.push(...runSubgraphChecks(options.root, config));
|
|
321
569
|
results.push(runVisibilityPolicyCheck(options.root, config, options));
|
|
570
|
+
results.push(...runSelectedGoalChecks(options.root, config, options));
|
|
322
571
|
try {
|
|
323
572
|
const templateSchemaInfo = (0, template_schema_1.loadTemplateSchemasWithInfo)(options.root, config, node_1.ALLOWED_TYPES);
|
|
324
|
-
results.push({
|
|
573
|
+
results.push(makeCheck({
|
|
574
|
+
id: "repo.templates",
|
|
325
575
|
name: "templates",
|
|
326
576
|
ok: true,
|
|
327
577
|
detail: "template schema set loaded",
|
|
328
|
-
});
|
|
578
|
+
}));
|
|
329
579
|
if (templateSchemaInfo.fallbackTypes.length > 0) {
|
|
330
|
-
results.push({
|
|
580
|
+
results.push(makeCheck({
|
|
581
|
+
id: "repo.templates",
|
|
331
582
|
name: "local-templates",
|
|
332
583
|
ok: true,
|
|
333
584
|
level: "warn",
|
|
334
585
|
detail: `missing local template schema(s) covered by bundled fallback: ${templateSchemaInfo.fallbackTypes.join(", ")}; run \`mdkg upgrade --apply\` to vendor them`,
|
|
335
|
-
|
|
586
|
+
remediation: "Run `mdkg upgrade --apply` to vendor missing managed template schemas.",
|
|
587
|
+
refs: templateSchemaInfo.fallbackTypes,
|
|
588
|
+
}));
|
|
336
589
|
}
|
|
337
590
|
}
|
|
338
591
|
catch (err) {
|
|
339
592
|
const message = err instanceof Error ? err.message : String(err);
|
|
340
|
-
results.push({
|
|
593
|
+
results.push(makeCheck({
|
|
594
|
+
id: "repo.templates",
|
|
341
595
|
name: "templates",
|
|
342
596
|
ok: false,
|
|
343
597
|
detail: message,
|
|
344
|
-
|
|
598
|
+
remediation: "Repair `.mdkg/templates` or run `mdkg upgrade --apply` when managed assets should be restored.",
|
|
599
|
+
}));
|
|
345
600
|
}
|
|
346
601
|
try {
|
|
347
602
|
const { rebuilt, stale } = (0, index_cache_1.loadIndex)({
|
|
348
603
|
root: options.root,
|
|
349
604
|
config,
|
|
350
605
|
useCache: !options.noCache,
|
|
351
|
-
allowReindex: !options.noReindex,
|
|
606
|
+
allowReindex: !options.noReindex && !strict,
|
|
352
607
|
});
|
|
353
608
|
if (rebuilt) {
|
|
354
|
-
results.push({
|
|
609
|
+
results.push(makeCheck({
|
|
610
|
+
id: "graph.index_cache",
|
|
355
611
|
name: "index",
|
|
356
612
|
ok: true,
|
|
357
613
|
detail: "index cache rebuilt and loaded",
|
|
358
|
-
|
|
614
|
+
remediation: "No action required; non-strict doctor rebuilt the cache.",
|
|
615
|
+
}));
|
|
359
616
|
}
|
|
360
617
|
else if (stale) {
|
|
361
|
-
results.push({
|
|
618
|
+
results.push(makeCheck({
|
|
619
|
+
id: "graph.index_cache",
|
|
362
620
|
name: "index",
|
|
363
621
|
ok: true,
|
|
622
|
+
level: "warn",
|
|
364
623
|
detail: "index cache is stale (run mdkg index to refresh)",
|
|
365
|
-
|
|
624
|
+
remediation: "Run `mdkg index` to refresh generated graph caches.",
|
|
625
|
+
strictFail: true,
|
|
626
|
+
}));
|
|
366
627
|
}
|
|
367
628
|
else {
|
|
368
|
-
results.push({
|
|
629
|
+
results.push(makeCheck({
|
|
630
|
+
id: "graph.index_cache",
|
|
369
631
|
name: "index",
|
|
370
632
|
ok: true,
|
|
371
633
|
detail: "index cache loaded",
|
|
372
|
-
});
|
|
634
|
+
}));
|
|
373
635
|
}
|
|
374
636
|
}
|
|
375
637
|
catch (err) {
|
|
376
638
|
const message = err instanceof Error ? err.message : String(err);
|
|
377
|
-
results.push({
|
|
639
|
+
results.push(makeCheck({
|
|
640
|
+
id: "graph.validate",
|
|
378
641
|
name: "index",
|
|
379
642
|
ok: false,
|
|
380
643
|
detail: message,
|
|
381
|
-
|
|
644
|
+
remediation: "Run `mdkg validate --json` and repair graph errors, then run `mdkg index`.",
|
|
645
|
+
}));
|
|
382
646
|
}
|
|
383
647
|
try {
|
|
384
648
|
const { rebuilt, stale } = (0, capabilities_index_cache_1.loadCapabilitiesIndex)({
|
|
385
649
|
root: options.root,
|
|
386
650
|
config,
|
|
387
651
|
useCache: !options.noCache,
|
|
388
|
-
allowReindex: !options.noReindex,
|
|
652
|
+
allowReindex: !options.noReindex && !strict,
|
|
389
653
|
});
|
|
390
654
|
if (rebuilt) {
|
|
391
|
-
results.push({
|
|
655
|
+
results.push(makeCheck({
|
|
656
|
+
id: "graph.capability_cache",
|
|
392
657
|
name: "capabilities-index",
|
|
393
658
|
ok: true,
|
|
394
659
|
detail: "capabilities cache rebuilt and loaded",
|
|
395
|
-
|
|
660
|
+
remediation: "No action required; non-strict doctor rebuilt the cache.",
|
|
661
|
+
}));
|
|
396
662
|
}
|
|
397
663
|
else if (stale) {
|
|
398
|
-
results.push({
|
|
664
|
+
results.push(makeCheck({
|
|
665
|
+
id: "graph.capability_cache",
|
|
399
666
|
name: "capabilities-index",
|
|
400
667
|
ok: true,
|
|
668
|
+
level: "warn",
|
|
401
669
|
detail: "capabilities cache is stale (run mdkg index to refresh)",
|
|
402
|
-
|
|
670
|
+
remediation: "Run `mdkg index` to refresh generated capability caches.",
|
|
671
|
+
strictFail: true,
|
|
672
|
+
}));
|
|
403
673
|
}
|
|
404
674
|
else {
|
|
405
|
-
results.push({
|
|
675
|
+
results.push(makeCheck({
|
|
676
|
+
id: "graph.capability_cache",
|
|
406
677
|
name: "capabilities-index",
|
|
407
678
|
ok: true,
|
|
408
679
|
detail: "capabilities cache loaded",
|
|
409
|
-
});
|
|
680
|
+
}));
|
|
410
681
|
}
|
|
411
682
|
}
|
|
412
683
|
catch (err) {
|
|
413
684
|
const message = err instanceof Error ? err.message : String(err);
|
|
414
|
-
results.push({
|
|
685
|
+
results.push(makeCheck({
|
|
686
|
+
id: "graph.capability_cache",
|
|
415
687
|
name: "capabilities-index",
|
|
416
688
|
ok: false,
|
|
417
689
|
detail: message,
|
|
418
|
-
|
|
690
|
+
remediation: "Run `mdkg index` to rebuild generated capability caches.",
|
|
691
|
+
}));
|
|
419
692
|
}
|
|
420
693
|
}
|
|
421
|
-
const
|
|
694
|
+
const finalResults = applyStrict(results, strict);
|
|
695
|
+
const publicResults = finalResults.map(publicCheck);
|
|
696
|
+
const summary = {
|
|
697
|
+
ok: finalResults.every((result) => result.ok),
|
|
698
|
+
errors: finalResults.filter((result) => result.severity === "error").length,
|
|
699
|
+
warnings: finalResults.filter((result) => result.severity === "warning").length,
|
|
700
|
+
infos: finalResults.filter((result) => result.severity === "info").length,
|
|
701
|
+
};
|
|
702
|
+
const failures = finalResults.filter((result) => !result.ok);
|
|
422
703
|
if (options.json) {
|
|
423
704
|
const payload = {
|
|
705
|
+
action: "doctor",
|
|
424
706
|
ok: failures.length === 0,
|
|
425
|
-
|
|
707
|
+
strict,
|
|
708
|
+
checks: publicResults,
|
|
709
|
+
summary,
|
|
426
710
|
failure_count: failures.length,
|
|
427
711
|
};
|
|
428
712
|
console.log(JSON.stringify(payload, null, 2));
|
|
429
713
|
}
|
|
430
714
|
else {
|
|
431
|
-
for (const result of
|
|
715
|
+
for (const result of finalResults) {
|
|
432
716
|
const prefix = result.ok ? result.level ?? "ok" : "fail";
|
|
433
717
|
console.log(`${prefix}: ${result.name} - ${result.detail}`);
|
|
434
718
|
}
|