mdkg 0.3.7 → 0.3.8
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 +24 -0
- package/CLI_COMMAND_MATRIX.md +81 -22
- package/README.md +50 -25
- package/dist/cli.js +70 -34
- package/dist/command-contract.json +354 -9
- package/dist/commands/capability.js +1 -0
- package/dist/commands/doctor.js +7 -7
- package/dist/commands/format.js +40 -1
- package/dist/commands/handoff.js +6 -0
- package/dist/commands/new.js +12 -3
- package/dist/commands/spec.js +76 -17
- package/dist/commands/subgraph.js +7 -4
- package/dist/commands/upgrade.js +70 -6
- package/dist/commands/validate.js +106 -3
- package/dist/commands/work.js +12 -5
- package/dist/graph/agent_file_types.js +59 -20
- package/dist/graph/capabilities_indexer.js +45 -3
- package/dist/graph/indexer.js +5 -0
- package/dist/graph/template_schema.js +37 -17
- package/dist/graph/validate_graph.js +11 -5
- package/dist/init/AGENT_START.md +5 -5
- package/dist/init/CLI_COMMAND_MATRIX.md +29 -16
- package/dist/init/README.md +11 -9
- package/dist/init/init-manifest.json +59 -4
- package/dist/init/templates/default/manifest.md +45 -0
- package/dist/init/templates/specs/agent.MANIFEST.md +80 -0
- package/dist/init/templates/specs/api.MANIFEST.md +33 -0
- package/dist/init/templates/specs/base.MANIFEST.md +120 -0
- package/dist/init/templates/specs/capability.MANIFEST.md +45 -0
- package/dist/init/templates/specs/integration.MANIFEST.md +25 -0
- package/dist/init/templates/specs/model.MANIFEST.md +21 -0
- package/dist/init/templates/specs/project.MANIFEST.md +39 -0
- package/dist/init/templates/specs/runtime-agent.MANIFEST.md +49 -0
- package/dist/init/templates/specs/runtime-image.MANIFEST.md +21 -0
- package/dist/init/templates/specs/tool.MANIFEST.md +25 -0
- package/dist/util/argparse.js +3 -0
- package/package.json +17 -3
package/dist/commands/spec.js
CHANGED
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runSpecListCommand = runSpecListCommand;
|
|
4
4
|
exports.runSpecShowCommand = runSpecShowCommand;
|
|
5
5
|
exports.runSpecValidateCommand = runSpecValidateCommand;
|
|
6
|
+
exports.runManifestListCommand = runManifestListCommand;
|
|
7
|
+
exports.runManifestShowCommand = runManifestShowCommand;
|
|
8
|
+
exports.runManifestValidateCommand = runManifestValidateCommand;
|
|
6
9
|
const capability_1 = require("./capability");
|
|
7
10
|
const validate_1 = require("./validate");
|
|
8
11
|
const errors_1 = require("../util/errors");
|
|
@@ -24,6 +27,9 @@ function loadSpecRecords(options) {
|
|
|
24
27
|
};
|
|
25
28
|
return sortSpecRecords((0, capability_1.filterCapabilityRecords)((0, capability_1.loadCapabilityRecords)(listOptions), listOptions));
|
|
26
29
|
}
|
|
30
|
+
function commandSurface(options) {
|
|
31
|
+
return options.surface ?? "spec";
|
|
32
|
+
}
|
|
27
33
|
function matchesSpecRef(record, id) {
|
|
28
34
|
const normalized = id.toLowerCase();
|
|
29
35
|
return (record.id === id ||
|
|
@@ -34,15 +40,15 @@ function matchesSpecRef(record, id) {
|
|
|
34
40
|
record.path.toLowerCase() === normalized ||
|
|
35
41
|
record.aliases.some((alias) => alias.toLowerCase() === normalized));
|
|
36
42
|
}
|
|
37
|
-
function resolveSpecRecord(records, id) {
|
|
43
|
+
function resolveSpecRecord(records, id, surface) {
|
|
38
44
|
const matches = records.filter((record) => matchesSpecRef(record, id));
|
|
39
45
|
if (matches.length === 1) {
|
|
40
46
|
return matches[0];
|
|
41
47
|
}
|
|
42
48
|
if (matches.length > 1) {
|
|
43
|
-
throw new errors_1.UsageError(
|
|
49
|
+
throw new errors_1.UsageError(`${surface === "manifest" ? "manifest capability" : "SPEC"} reference is ambiguous: ${id}`);
|
|
44
50
|
}
|
|
45
|
-
throw new errors_1.NotFoundError(
|
|
51
|
+
throw new errors_1.NotFoundError(`${surface === "manifest" ? "manifest capability" : "SPEC"} not found: ${id}`);
|
|
46
52
|
}
|
|
47
53
|
function stringValue(value) {
|
|
48
54
|
return typeof value === "string" ? value : undefined;
|
|
@@ -50,33 +56,74 @@ function stringValue(value) {
|
|
|
50
56
|
function stringArrayValue(value) {
|
|
51
57
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
52
58
|
}
|
|
53
|
-
function
|
|
59
|
+
function compatibilityLabel(record) {
|
|
60
|
+
const mode = record.manifest?.compatibility_mode;
|
|
61
|
+
if (mode === "legacy") {
|
|
62
|
+
return "legacy SPEC.md";
|
|
63
|
+
}
|
|
64
|
+
if (mode === "transitional") {
|
|
65
|
+
return "transitional MANIFEST.md type: spec";
|
|
66
|
+
}
|
|
67
|
+
return "MANIFEST.md";
|
|
68
|
+
}
|
|
69
|
+
function legacySpecDeprecation() {
|
|
70
|
+
return "mdkg spec is a legacy alias for mdkg manifest during the one-compatibility-release bridge.";
|
|
71
|
+
}
|
|
72
|
+
function printSpecList(records, json, surface = "spec") {
|
|
54
73
|
if (json) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
const receipt = surface === "manifest"
|
|
75
|
+
? {
|
|
76
|
+
kind: "manifest",
|
|
77
|
+
compatibility_kind: "spec",
|
|
78
|
+
count: records.length,
|
|
79
|
+
items: records,
|
|
80
|
+
}
|
|
81
|
+
: {
|
|
82
|
+
kind: "spec",
|
|
83
|
+
canonical_kind: "manifest",
|
|
84
|
+
legacy_alias: true,
|
|
85
|
+
deprecation: legacySpecDeprecation(),
|
|
86
|
+
count: records.length,
|
|
87
|
+
items: records,
|
|
88
|
+
};
|
|
89
|
+
console.log(JSON.stringify(receipt, null, 2));
|
|
60
90
|
return;
|
|
61
91
|
}
|
|
92
|
+
if (surface === "spec") {
|
|
93
|
+
console.log(legacySpecDeprecation());
|
|
94
|
+
}
|
|
62
95
|
if (records.length === 0) {
|
|
63
|
-
console.log("no SPEC.md capabilities found");
|
|
96
|
+
console.log("no MANIFEST.md/SPEC.md capabilities found");
|
|
64
97
|
return;
|
|
65
98
|
}
|
|
66
|
-
console.log(
|
|
99
|
+
console.log(`${surface === "manifest" ? "MANIFEST.md" : "MANIFEST.md/SPEC.md"} capabilities: ${records.length}`);
|
|
67
100
|
for (const record of records) {
|
|
68
101
|
const kind = stringValue(record.spec?.spec_kind);
|
|
69
102
|
const kindLabel = kind ? ` | ${kind}` : "";
|
|
70
|
-
console.log(`${record.qid} | ${record.visibility}${kindLabel} | ${record.title}`);
|
|
103
|
+
console.log(`${record.qid} | ${record.visibility}${kindLabel} | ${compatibilityLabel(record)} | ${record.title}`);
|
|
71
104
|
}
|
|
72
105
|
}
|
|
73
|
-
function printSpec(record, json) {
|
|
106
|
+
function printSpec(record, json, surface = "spec") {
|
|
74
107
|
if (json) {
|
|
75
|
-
|
|
108
|
+
const receipt = surface === "manifest"
|
|
109
|
+
? { kind: "manifest", compatibility_kind: "spec", item: record }
|
|
110
|
+
: {
|
|
111
|
+
kind: "spec",
|
|
112
|
+
canonical_kind: "manifest",
|
|
113
|
+
legacy_alias: true,
|
|
114
|
+
deprecation: legacySpecDeprecation(),
|
|
115
|
+
item: record,
|
|
116
|
+
};
|
|
117
|
+
console.log(JSON.stringify(receipt, null, 2));
|
|
76
118
|
return;
|
|
77
119
|
}
|
|
120
|
+
if (surface === "spec") {
|
|
121
|
+
console.log(legacySpecDeprecation());
|
|
122
|
+
}
|
|
78
123
|
console.log(`${record.qid} | ${record.visibility}`);
|
|
79
124
|
console.log(`title: ${record.title}`);
|
|
125
|
+
console.log(`semantic_kind: ${record.manifest?.semantic_kind ?? "manifest"}`);
|
|
126
|
+
console.log(`compatibility: ${compatibilityLabel(record)}`);
|
|
80
127
|
const specKind = stringValue(record.spec?.spec_kind);
|
|
81
128
|
if (specKind) {
|
|
82
129
|
console.log(`spec_kind: ${specKind}`);
|
|
@@ -88,14 +135,26 @@ function printSpec(record, json) {
|
|
|
88
135
|
}
|
|
89
136
|
}
|
|
90
137
|
function runSpecListCommand(options) {
|
|
91
|
-
|
|
138
|
+
const surface = commandSurface(options);
|
|
139
|
+
printSpecList(loadSpecRecords(options), options.json, surface);
|
|
92
140
|
}
|
|
93
141
|
function runSpecShowCommand(options) {
|
|
94
|
-
|
|
142
|
+
const surface = commandSurface(options);
|
|
143
|
+
printSpec(resolveSpecRecord(loadSpecRecords(options), options.id, surface), options.json, surface);
|
|
95
144
|
}
|
|
96
145
|
function runSpecValidateCommand(options) {
|
|
146
|
+
const surface = commandSurface(options);
|
|
97
147
|
if (options.id) {
|
|
98
|
-
resolveSpecRecord(loadSpecRecords(options), options.id);
|
|
148
|
+
resolveSpecRecord(loadSpecRecords(options), options.id, surface);
|
|
99
149
|
}
|
|
100
150
|
(0, validate_1.runValidateCommand)({ root: options.root, json: options.json });
|
|
101
151
|
}
|
|
152
|
+
function runManifestListCommand(options) {
|
|
153
|
+
runSpecListCommand({ ...options, surface: "manifest" });
|
|
154
|
+
}
|
|
155
|
+
function runManifestShowCommand(options) {
|
|
156
|
+
runSpecShowCommand({ ...options, surface: "manifest" });
|
|
157
|
+
}
|
|
158
|
+
function runManifestValidateCommand(options) {
|
|
159
|
+
runSpecValidateCommand({ ...options, surface: "manifest" });
|
|
160
|
+
}
|
|
@@ -323,6 +323,9 @@ function runSubgraphVerifyCommand(options) {
|
|
|
323
323
|
throw new errors_1.ValidationError("subgraph verify failed");
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
|
+
function dirtySourceGuidance(paths) {
|
|
327
|
+
return `source_path has dirty tracked changes: ${paths.join(", ")}; commit or stash the child repo first, then refresh the root-owned subgraph bundle from the clean accepted child commit`;
|
|
328
|
+
}
|
|
326
329
|
function pushAuditCheck(receipt, check) {
|
|
327
330
|
receipt.checks.push(check);
|
|
328
331
|
if (!check.ok && check.severity === "error") {
|
|
@@ -349,7 +352,7 @@ function auditOneAlias(options) {
|
|
|
349
352
|
source_repo: options.health.source_repo,
|
|
350
353
|
capability_summary: {
|
|
351
354
|
node_count: options.nodeTypes.length,
|
|
352
|
-
spec_count: options.nodeTypes.filter((type) => type === "spec").length,
|
|
355
|
+
spec_count: options.nodeTypes.filter((type) => type === "manifest" || type === "spec").length,
|
|
353
356
|
work_count: options.nodeTypes.filter((type) => type === "work").length,
|
|
354
357
|
skill_count: options.nodeTypes.filter((type) => type === "skill").length,
|
|
355
358
|
},
|
|
@@ -392,7 +395,7 @@ function auditOneAlias(options) {
|
|
|
392
395
|
ok: !gitState.dirtyTracked,
|
|
393
396
|
severity: "warning",
|
|
394
397
|
message: gitState.dirtyTracked
|
|
395
|
-
?
|
|
398
|
+
? dirtySourceGuidance(gitState.dirtyTrackedPaths)
|
|
396
399
|
: "source_path has no dirty tracked changes",
|
|
397
400
|
path: options.subgraph.source_path,
|
|
398
401
|
details: { dirty_tracked_paths: gitState.dirtyTrackedPaths },
|
|
@@ -539,7 +542,7 @@ function upgradePlanForAlias(options) {
|
|
|
539
542
|
blockers.push(sourceGitError.message);
|
|
540
543
|
}
|
|
541
544
|
if (options.audit.dirty_tracked) {
|
|
542
|
-
blockers.push(
|
|
545
|
+
blockers.push(dirtySourceGuidance(options.audit.dirty_tracked_paths ?? []));
|
|
543
546
|
}
|
|
544
547
|
const rootOwnedErrors = options.audit.checks.filter((check) => check.id === "subgraph.bundle.root_owned" && !check.ok);
|
|
545
548
|
blockers.push(...rootOwnedErrors.map((check) => check.message));
|
|
@@ -705,7 +708,7 @@ function inspectSourcePath(root, alias, subgraph, allowDirty) {
|
|
|
705
708
|
? status.stdout.split(/\r?\n/).map((line) => line.slice(3)).filter(Boolean).sort()
|
|
706
709
|
: [];
|
|
707
710
|
if (dirtyTrackedPaths.length > 0 && !allowDirty) {
|
|
708
|
-
throw new errors_1.UsageError(`subgraph ${alias}
|
|
711
|
+
throw new errors_1.UsageError(`subgraph ${alias} ${dirtySourceGuidance(dirtyTrackedPaths)}`);
|
|
709
712
|
}
|
|
710
713
|
return {
|
|
711
714
|
sourceRoot,
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -15,6 +15,8 @@ const project_db_1 = require("../core/project_db");
|
|
|
15
15
|
const errors_1 = require("../util/errors");
|
|
16
16
|
const frontmatter_1 = require("../graph/frontmatter");
|
|
17
17
|
const workspace_files_1 = require("../graph/workspace_files");
|
|
18
|
+
const agent_file_types_1 = require("../graph/agent_file_types");
|
|
19
|
+
const atomic_1 = require("../util/atomic");
|
|
18
20
|
const init_manifest_1 = require("./init_manifest");
|
|
19
21
|
const skill_support_1 = require("./skill_support");
|
|
20
22
|
const skill_mirror_1 = require("./skill_mirror");
|
|
@@ -65,6 +67,13 @@ function writeFile(filePath, content) {
|
|
|
65
67
|
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
66
68
|
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
67
69
|
}
|
|
70
|
+
function repoRelativePath(root, filePath) {
|
|
71
|
+
return path_1.default.relative(root, filePath).split(path_1.default.sep).join("/");
|
|
72
|
+
}
|
|
73
|
+
function renderFrontmatterDocument(frontmatter, body) {
|
|
74
|
+
const frontmatterBlock = ["---", ...(0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER), "---"].join("\n");
|
|
75
|
+
return body.length > 0 ? `${frontmatterBlock}\n${body}` : frontmatterBlock;
|
|
76
|
+
}
|
|
68
77
|
function createSummary() {
|
|
69
78
|
return {
|
|
70
79
|
created: 0,
|
|
@@ -323,9 +332,59 @@ function migrateClosedGoalActiveNodes(root, dryRun, summary, changes) {
|
|
|
323
332
|
frontmatter.last_active_node = activeNode;
|
|
324
333
|
}
|
|
325
334
|
delete frontmatter.active_node;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
335
|
+
writeFile(filePath, renderFrontmatterDocument(frontmatter, parsed.body.length > 0 ? parsed.body : ""));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (planned === 0) {
|
|
340
|
+
summary.unchanged += 1;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function migrateLegacySpecManifests(root, dryRun, summary, changes) {
|
|
344
|
+
const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
|
|
345
|
+
const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(root, config);
|
|
346
|
+
let planned = 0;
|
|
347
|
+
for (const files of Object.values(filesByAlias)) {
|
|
348
|
+
for (const filePath of files) {
|
|
349
|
+
if (path_1.default.basename(filePath) !== agent_file_types_1.LEGACY_SPEC_BASENAME) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
353
|
+
let parsed;
|
|
354
|
+
try {
|
|
355
|
+
parsed = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (parsed.frontmatter.type !== "spec") {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
planned += 1;
|
|
364
|
+
const targetPath = path_1.default.join(path_1.default.dirname(filePath), agent_file_types_1.CANONICAL_MANIFEST_BASENAME);
|
|
365
|
+
const relativePath = repoRelativePath(root, filePath);
|
|
366
|
+
const relativeTargetPath = repoRelativePath(root, targetPath);
|
|
367
|
+
if (fs_1.default.existsSync(targetPath)) {
|
|
368
|
+
record(summary, changes, {
|
|
369
|
+
path: relativePath,
|
|
370
|
+
target_path: relativeTargetPath,
|
|
371
|
+
category: "manifest_migration",
|
|
372
|
+
action: "conflict",
|
|
373
|
+
reason: "sibling MANIFEST.md already exists; legacy SPEC.md content preserved",
|
|
374
|
+
});
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
record(summary, changes, {
|
|
378
|
+
path: relativePath,
|
|
379
|
+
target_path: relativeTargetPath,
|
|
380
|
+
category: "manifest_migration",
|
|
381
|
+
action: "migrate",
|
|
382
|
+
reason: "rename legacy SPEC.md to MANIFEST.md and normalize type: spec to type: manifest",
|
|
383
|
+
});
|
|
384
|
+
if (!dryRun) {
|
|
385
|
+
const frontmatter = { ...parsed.frontmatter, type: "manifest" };
|
|
386
|
+
(0, atomic_1.atomicWriteFile)(filePath, renderFrontmatterDocument(frontmatter, parsed.body));
|
|
387
|
+
fs_1.default.renameSync(filePath, targetPath);
|
|
329
388
|
}
|
|
330
389
|
}
|
|
331
390
|
}
|
|
@@ -422,6 +481,9 @@ function ensureArchiveIgnorePolicy(root, dryRun, summary, changes) {
|
|
|
422
481
|
function isWritableChange(change) {
|
|
423
482
|
return change.action === "create" || change.action === "update" || change.action === "migrate" || change.action === "sync";
|
|
424
483
|
}
|
|
484
|
+
function writablePathForChange(change) {
|
|
485
|
+
return change.target_path ?? change.path;
|
|
486
|
+
}
|
|
425
487
|
function buildApplySideEffects(options) {
|
|
426
488
|
const hasDirectWrites = options.changes.some(isWritableChange);
|
|
427
489
|
if (!hasDirectWrites && options.existingManifest) {
|
|
@@ -468,7 +530,8 @@ function emitHumanReceipt(receipt) {
|
|
|
468
530
|
}
|
|
469
531
|
else {
|
|
470
532
|
for (const change of receipt.changes) {
|
|
471
|
-
|
|
533
|
+
const target = change.target_path ? ` -> ${change.target_path}` : "";
|
|
534
|
+
console.log(` ${change.action}: ${change.path}${target} (${change.reason})`);
|
|
472
535
|
}
|
|
473
536
|
}
|
|
474
537
|
if (receipt.apply_side_effects.length > 0) {
|
|
@@ -501,6 +564,7 @@ function runUpgradeCommand(options) {
|
|
|
501
564
|
const managedCurrentFiles = [];
|
|
502
565
|
migrateConfigIfNeeded(root, dryRun, summary, changes);
|
|
503
566
|
migrateClosedGoalActiveNodes(root, dryRun, summary, changes);
|
|
567
|
+
migrateLegacySpecManifests(root, dryRun, summary, changes);
|
|
504
568
|
for (const file of currentManifest.files) {
|
|
505
569
|
if (!shouldIncludeFile(file, agentWorkspace)) {
|
|
506
570
|
continue;
|
|
@@ -544,8 +608,8 @@ function runUpgradeCommand(options) {
|
|
|
544
608
|
}
|
|
545
609
|
}
|
|
546
610
|
const preservedCustomizations = changes.filter((change) => change.action === "conflict");
|
|
547
|
-
const blockingConflicts =
|
|
548
|
-
const willWritePaths = changes.filter(isWritableChange).map(
|
|
611
|
+
const blockingConflicts = changes.filter((change) => change.action === "conflict" && change.category === "manifest_migration");
|
|
612
|
+
const willWritePaths = changes.filter(isWritableChange).map(writablePathForChange);
|
|
549
613
|
const receipt = {
|
|
550
614
|
action: "upgrade",
|
|
551
615
|
dry_run: dryRun,
|
|
@@ -129,6 +129,20 @@ function collectRawContentWarnings(qid, node) {
|
|
|
129
129
|
}
|
|
130
130
|
return warnings;
|
|
131
131
|
}
|
|
132
|
+
function collectManifestCompatibilityWarnings(qid, filePath, node) {
|
|
133
|
+
const basename = path_1.default.basename(filePath);
|
|
134
|
+
if (basename === agent_file_types_1.LEGACY_SPEC_BASENAME && node.type === "spec") {
|
|
135
|
+
return [
|
|
136
|
+
`${qid}: manifest.compat.spec_legacy warning: SPEC.md is legacy; MANIFEST.md is the canonical manifest filename. Rename this file before the compatibility release closes.`,
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
if (basename === agent_file_types_1.CANONICAL_MANIFEST_BASENAME && node.type === "spec") {
|
|
140
|
+
return [
|
|
141
|
+
`${qid}: manifest.compat.type_spec warning: MANIFEST.md uses legacy type: spec; use type: manifest before the compatibility release closes.`,
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
132
146
|
function collectChangedPaths(root) {
|
|
133
147
|
const result = (0, child_process_1.spawnSync)("git", ["-C", root, "status", "--porcelain", "--", ".mdkg"], {
|
|
134
148
|
encoding: "utf8",
|
|
@@ -166,6 +180,7 @@ function warningPath(message, nodes) {
|
|
|
166
180
|
}
|
|
167
181
|
function warningDiagnostic(message, nodes) {
|
|
168
182
|
const qid = qidFromWarning(message);
|
|
183
|
+
const nodeType = qid && nodes[qid] ? nodes[qid].type : undefined;
|
|
169
184
|
const pathValue = warningPath(message, nodes);
|
|
170
185
|
const rawMatch = /raw-content\.([a-z_]+)/.exec(message);
|
|
171
186
|
if (rawMatch) {
|
|
@@ -175,6 +190,7 @@ function warningDiagnostic(message, nodes) {
|
|
|
175
190
|
severity: "warning",
|
|
176
191
|
message,
|
|
177
192
|
qid,
|
|
193
|
+
node_type: nodeType,
|
|
178
194
|
path: pathValue,
|
|
179
195
|
ref: qid,
|
|
180
196
|
remediation: "Replace raw secrets, prompts, tokens, or payloads with refs, hashes, redacted summaries, or archive/artifact links.",
|
|
@@ -187,6 +203,7 @@ function warningDiagnostic(message, nodes) {
|
|
|
187
203
|
severity: "warning",
|
|
188
204
|
message,
|
|
189
205
|
qid,
|
|
206
|
+
node_type: nodeType,
|
|
190
207
|
path: pathValue,
|
|
191
208
|
ref: qid,
|
|
192
209
|
remediation: "Run mdkg format --headings --dry-run to review missing heading additions, then --apply if acceptable.",
|
|
@@ -198,16 +215,32 @@ function warningDiagnostic(message, nodes) {
|
|
|
198
215
|
category: "templates",
|
|
199
216
|
severity: "warning",
|
|
200
217
|
message,
|
|
218
|
+
node_type: nodeType,
|
|
201
219
|
path: pathValue,
|
|
202
220
|
remediation: "Run mdkg upgrade --apply to vendor missing built-in template schemas when the managed asset update is safe.",
|
|
203
221
|
};
|
|
204
222
|
}
|
|
223
|
+
const manifestCompatMatch = /manifest\.compat\.([a-z_]+)/.exec(message);
|
|
224
|
+
if (manifestCompatMatch) {
|
|
225
|
+
return {
|
|
226
|
+
id: `manifest.compat.${manifestCompatMatch[1]}`,
|
|
227
|
+
category: "manifest-compatibility",
|
|
228
|
+
severity: "warning",
|
|
229
|
+
message,
|
|
230
|
+
qid,
|
|
231
|
+
node_type: nodeType,
|
|
232
|
+
path: pathValue,
|
|
233
|
+
ref: qid,
|
|
234
|
+
remediation: "Rename legacy SPEC.md files to MANIFEST.md and update transitional type: spec frontmatter to type: manifest before the compatibility release closes.",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
205
237
|
if (message.includes("sqlite") || message.includes("index")) {
|
|
206
238
|
return {
|
|
207
239
|
id: "cache.index",
|
|
208
240
|
category: "cache",
|
|
209
241
|
severity: "warning",
|
|
210
242
|
message,
|
|
243
|
+
node_type: nodeType,
|
|
211
244
|
path: pathValue,
|
|
212
245
|
remediation: "Run mdkg index or mdkg db index rebuild when generated cache state should be refreshed.",
|
|
213
246
|
};
|
|
@@ -218,6 +251,7 @@ function warningDiagnostic(message, nodes) {
|
|
|
218
251
|
category: "skills",
|
|
219
252
|
severity: "warning",
|
|
220
253
|
message,
|
|
254
|
+
node_type: nodeType,
|
|
221
255
|
path: pathValue,
|
|
222
256
|
remediation: "Run mdkg skill sync after reviewing managed skill mirror drift.",
|
|
223
257
|
};
|
|
@@ -228,6 +262,7 @@ function warningDiagnostic(message, nodes) {
|
|
|
228
262
|
category: "subgraph",
|
|
229
263
|
severity: "warning",
|
|
230
264
|
message,
|
|
265
|
+
node_type: nodeType,
|
|
231
266
|
path: pathValue,
|
|
232
267
|
remediation: "Run mdkg subgraph verify or refresh the source bundle after reviewing child graph freshness.",
|
|
233
268
|
};
|
|
@@ -238,11 +273,67 @@ function warningDiagnostic(message, nodes) {
|
|
|
238
273
|
severity: "warning",
|
|
239
274
|
message,
|
|
240
275
|
qid,
|
|
276
|
+
node_type: nodeType,
|
|
241
277
|
path: pathValue,
|
|
242
278
|
ref: qid,
|
|
243
279
|
remediation: "Review the warning and apply the focused mdkg command suggested by the message when appropriate.",
|
|
244
280
|
};
|
|
245
281
|
}
|
|
282
|
+
function countBuckets(values) {
|
|
283
|
+
const counts = new Map();
|
|
284
|
+
for (const value of values) {
|
|
285
|
+
if (!value) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
289
|
+
}
|
|
290
|
+
return Array.from(counts.entries())
|
|
291
|
+
.map(([key, count]) => ({ key, count }))
|
|
292
|
+
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
293
|
+
}
|
|
294
|
+
function normalizeLimit(limit) {
|
|
295
|
+
if (limit === undefined) {
|
|
296
|
+
return 50;
|
|
297
|
+
}
|
|
298
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
299
|
+
throw new errors_1.ValidationError("--limit must be a non-negative integer");
|
|
300
|
+
}
|
|
301
|
+
return limit;
|
|
302
|
+
}
|
|
303
|
+
function buildWarningSummary(diagnostics, limit) {
|
|
304
|
+
const effectiveLimit = limit === null || limit === undefined ? diagnostics.length : limit;
|
|
305
|
+
const emitted = Math.min(diagnostics.length, effectiveLimit);
|
|
306
|
+
const truncated = emitted < diagnostics.length;
|
|
307
|
+
return {
|
|
308
|
+
total: diagnostics.length,
|
|
309
|
+
emitted,
|
|
310
|
+
truncated,
|
|
311
|
+
omitted_count: diagnostics.length - emitted,
|
|
312
|
+
limit: limit === undefined ? null : limit,
|
|
313
|
+
affected_file_count: new Set(diagnostics.map((diagnostic) => diagnostic.path).filter(Boolean)).size,
|
|
314
|
+
by_id: countBuckets(diagnostics.map((diagnostic) => diagnostic.id)),
|
|
315
|
+
by_category: countBuckets(diagnostics.map((diagnostic) => diagnostic.category)),
|
|
316
|
+
by_node_type: countBuckets(diagnostics.map((diagnostic) => diagnostic.node_type)),
|
|
317
|
+
top_qids: countBuckets(diagnostics.map((diagnostic) => diagnostic.qid)).slice(0, 25),
|
|
318
|
+
top_paths: countBuckets(diagnostics.map((diagnostic) => diagnostic.path)).slice(0, 25),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function shapeValidateReceiptForSummary(receipt, limit) {
|
|
322
|
+
const effectiveLimit = normalizeLimit(limit);
|
|
323
|
+
const warningDiagnostics = receipt.warning_diagnostics.slice(0, effectiveLimit);
|
|
324
|
+
return {
|
|
325
|
+
...receipt,
|
|
326
|
+
warnings: warningDiagnostics.map((warning) => warning.message),
|
|
327
|
+
warning_diagnostics: warningDiagnostics,
|
|
328
|
+
warning_summary: buildWarningSummary(receipt.warning_diagnostics, effectiveLimit),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function writeJsonReceipt(root, jsonOut, receipt) {
|
|
332
|
+
const outPath = path_1.default.resolve(root, jsonOut);
|
|
333
|
+
fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
|
|
334
|
+
fs_1.default.writeFileSync(outPath, `${JSON.stringify(receipt, null, 2)}\n`, "utf8");
|
|
335
|
+
return outPath;
|
|
336
|
+
}
|
|
246
337
|
function isCoreListFile(filePath) {
|
|
247
338
|
return path_1.default.basename(filePath) === "core.md" && path_1.default.basename(path_1.default.dirname(filePath)) === "core";
|
|
248
339
|
}
|
|
@@ -412,6 +503,7 @@ function collectValidateReceipt(options) {
|
|
|
412
503
|
const idsByWorkspace = {};
|
|
413
504
|
for (const [alias, files] of Object.entries(filesByAlias)) {
|
|
414
505
|
idsByWorkspace[alias] = new Map();
|
|
506
|
+
errors.push(...(0, agent_file_types_1.collectManifestSiblingConflicts)(files, (dirPath) => path_1.default.relative(options.root, dirPath).split(path_1.default.sep).join("/") || "."));
|
|
415
507
|
for (const filePath of files) {
|
|
416
508
|
if (isCoreListFile(filePath)) {
|
|
417
509
|
continue;
|
|
@@ -450,6 +542,7 @@ function collectValidateReceipt(options) {
|
|
|
450
542
|
}
|
|
451
543
|
}
|
|
452
544
|
warnings.push(...collectRawContentWarnings(qid, node));
|
|
545
|
+
warnings.push(...collectManifestCompatibilityWarnings(qid, filePath, node));
|
|
453
546
|
}
|
|
454
547
|
catch (err) {
|
|
455
548
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
@@ -546,6 +639,7 @@ function collectValidateReceipt(options) {
|
|
|
546
639
|
error_count: uniqueErrors.length,
|
|
547
640
|
warnings: uniqueWarnings,
|
|
548
641
|
warning_diagnostics: filteredWarningDiagnostics,
|
|
642
|
+
warning_summary: buildWarningSummary(filteredWarningDiagnostics),
|
|
549
643
|
errors: uniqueErrors,
|
|
550
644
|
...(outPath ? { report_path: outPath } : {}),
|
|
551
645
|
...(options.changedOnly
|
|
@@ -555,18 +649,27 @@ function collectValidateReceipt(options) {
|
|
|
555
649
|
return receipt;
|
|
556
650
|
}
|
|
557
651
|
function runValidateCommand(options) {
|
|
558
|
-
|
|
652
|
+
let receipt = collectValidateReceipt(options);
|
|
653
|
+
if (options.jsonOut) {
|
|
654
|
+
const jsonOutPath = path_1.default.resolve(options.root, options.jsonOut);
|
|
655
|
+
receipt = { ...receipt, json_receipt_path: jsonOutPath };
|
|
656
|
+
writeJsonReceipt(options.root, options.jsonOut, receipt);
|
|
657
|
+
}
|
|
658
|
+
const displayReceipt = options.summary ? shapeValidateReceiptForSummary(receipt, options.limit) : receipt;
|
|
559
659
|
if (options.json) {
|
|
560
|
-
console.log(JSON.stringify(
|
|
660
|
+
console.log(JSON.stringify(displayReceipt, null, 2));
|
|
561
661
|
if (receipt.error_count > 0) {
|
|
562
662
|
throw new errors_1.ValidationError(`validation failed with ${receipt.error_count} error(s)`);
|
|
563
663
|
}
|
|
564
664
|
return;
|
|
565
665
|
}
|
|
566
666
|
if (!options.quiet) {
|
|
567
|
-
for (const warning of
|
|
667
|
+
for (const warning of displayReceipt.warnings) {
|
|
568
668
|
console.error(`warning: ${warning}`);
|
|
569
669
|
}
|
|
670
|
+
if (displayReceipt.warning_summary.truncated) {
|
|
671
|
+
console.error(`warning summary: omitted ${displayReceipt.warning_summary.omitted_count} warning(s); rerun without --summary or use --json-out <path> for full diagnostics`);
|
|
672
|
+
}
|
|
570
673
|
}
|
|
571
674
|
if (receipt.error_count > 0) {
|
|
572
675
|
if (receipt.report_path) {
|
package/dist/commands/work.js
CHANGED
|
@@ -247,6 +247,10 @@ function workflowDiagnosticCode(message) {
|
|
|
247
247
|
if (message.includes("missing recommended heading")) {
|
|
248
248
|
return "heading.missing";
|
|
249
249
|
}
|
|
250
|
+
const manifestCompatMatch = /manifest\.compat\.([a-z_]+)/.exec(message);
|
|
251
|
+
if (manifestCompatMatch) {
|
|
252
|
+
return `manifest.compat.${manifestCompatMatch[1]}`;
|
|
253
|
+
}
|
|
250
254
|
if (message.includes("references missing") || message.includes("references missing node")) {
|
|
251
255
|
return "reference.missing";
|
|
252
256
|
}
|
|
@@ -384,8 +388,8 @@ function resolveTriggerWorkNode(index, ws, refRaw) {
|
|
|
384
388
|
if (node.type === "work") {
|
|
385
389
|
return { workNode: node };
|
|
386
390
|
}
|
|
387
|
-
if (node.type
|
|
388
|
-
throw new errors_1.UsageError(`work trigger requires a WORK.md or SPEC.md ref, got ${node.type}: ${node.qid}`);
|
|
391
|
+
if (!(0, agent_file_types_1.isManifestSemanticType)(node.type)) {
|
|
392
|
+
throw new errors_1.UsageError(`work trigger requires a WORK.md or MANIFEST.md/SPEC.md ref, got ${node.type}: ${node.qid}`);
|
|
389
393
|
}
|
|
390
394
|
const candidates = new Map();
|
|
391
395
|
const specDir = path_1.default.posix.dirname(node.path);
|
|
@@ -411,11 +415,12 @@ function resolveTriggerWorkNode(index, ws, refRaw) {
|
|
|
411
415
|
}
|
|
412
416
|
}
|
|
413
417
|
const workNodes = Array.from(candidates.values()).sort((a, b) => a.qid.localeCompare(b.qid));
|
|
418
|
+
const manifestLabel = node.type === "spec" ? "legacy SPEC.md" : "MANIFEST.md";
|
|
414
419
|
if (workNodes.length === 0) {
|
|
415
|
-
throw new errors_1.NotFoundError(
|
|
420
|
+
throw new errors_1.NotFoundError(`${manifestLabel} ${node.qid} has no resolvable WORK.md contract`);
|
|
416
421
|
}
|
|
417
422
|
if (workNodes.length > 1) {
|
|
418
|
-
throw new errors_1.UsageError(
|
|
423
|
+
throw new errors_1.UsageError(`${manifestLabel} ${node.qid} has multiple work contracts; trigger one explicitly: ${workNodes
|
|
419
424
|
.map((workNode) => workNode.qid)
|
|
420
425
|
.join(", ")}`);
|
|
421
426
|
}
|
|
@@ -757,7 +762,9 @@ function runWorkContractNewCommandLocked(options) {
|
|
|
757
762
|
const agentId = normalizePortableIdRef(options.agentId, "--agent-id");
|
|
758
763
|
const kind = options.kind.toLowerCase();
|
|
759
764
|
const resolvedAgent = (0, qid_1.resolveQid)(index, agentId, ws);
|
|
760
|
-
const relates = resolvedAgent.status === "ok" && index.nodes[resolvedAgent.qid]?.type
|
|
765
|
+
const relates = resolvedAgent.status === "ok" && (0, agent_file_types_1.isManifestSemanticType)(index.nodes[resolvedAgent.qid]?.type ?? "")
|
|
766
|
+
? [agentId]
|
|
767
|
+
: [];
|
|
761
768
|
const requiredCapabilities = parseCsvList(options.requiredCapabilities);
|
|
762
769
|
const receipt = createAgentWorkflowNode({
|
|
763
770
|
root: options.root,
|