mdkg 0.2.0 → 0.3.1
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 +87 -1
- package/CLI_COMMAND_MATRIX.md +1176 -0
- package/README.md +58 -5
- package/dist/cli.js +267 -12
- package/dist/command-contract.json +7473 -0
- package/dist/commands/capability.js +13 -8
- package/dist/commands/doctor.js +370 -86
- package/dist/commands/fix.js +924 -0
- package/dist/commands/format.js +9 -3
- package/dist/commands/skill.js +13 -3
- package/dist/commands/skill_support.js +3 -3
- package/dist/commands/spec.js +101 -0
- package/dist/commands/status.js +270 -0
- package/dist/commands/subgraph.js +300 -0
- package/dist/commands/validate.js +1 -1
- package/dist/commands/work.js +569 -20
- package/dist/commands/workspace.js +19 -7
- package/dist/graph/agent_file_types.js +95 -7
- package/dist/graph/capabilities_indexer.js +89 -2
- package/dist/graph/frontmatter.js +6 -0
- package/dist/graph/node.js +8 -2
- package/dist/init/AGENT_START.md +5 -1
- package/dist/init/CLI_COMMAND_MATRIX.md +36 -0
- package/dist/init/README.md +41 -2
- package/dist/init/init-manifest.json +20 -20
- package/dist/init/templates/default/receipt.md +12 -1
- package/dist/init/templates/default/spec.md +8 -6
- package/dist/init/templates/default/work.md +5 -1
- package/dist/init/templates/default/work_order.md +11 -0
- package/dist/init/templates/specs/agent.SPEC.md +45 -4
- package/dist/init/templates/specs/api.SPEC.md +1 -0
- package/dist/init/templates/specs/base.SPEC.md +45 -12
- package/dist/init/templates/specs/capability.SPEC.md +16 -3
- package/dist/init/templates/specs/integration.SPEC.md +1 -0
- package/dist/init/templates/specs/model.SPEC.md +1 -0
- package/dist/init/templates/specs/project.SPEC.md +14 -1
- package/dist/init/templates/specs/{omniruntime-agent.SPEC.md → runtime-agent.SPEC.md} +13 -3
- package/dist/init/templates/specs/runtime-image.SPEC.md +1 -0
- package/dist/init/templates/specs/tool.SPEC.md +1 -0
- package/dist/util/argparse.js +9 -0
- package/package.json +12 -3
|
@@ -10,6 +10,8 @@ exports.runSubgraphRemoveCommand = runSubgraphRemoveCommand;
|
|
|
10
10
|
exports.runSubgraphEnableCommand = runSubgraphEnableCommand;
|
|
11
11
|
exports.runSubgraphDisableCommand = runSubgraphDisableCommand;
|
|
12
12
|
exports.runSubgraphVerifyCommand = runSubgraphVerifyCommand;
|
|
13
|
+
exports.runSubgraphAuditCommand = runSubgraphAuditCommand;
|
|
14
|
+
exports.runSubgraphUpgradePlanCommand = runSubgraphUpgradePlanCommand;
|
|
13
15
|
exports.runSubgraphRefreshCommand = runSubgraphRefreshCommand;
|
|
14
16
|
exports.runSubgraphSyncCommand = runSubgraphSyncCommand;
|
|
15
17
|
exports.runSubgraphMaterializeCommand = runSubgraphMaterializeCommand;
|
|
@@ -321,6 +323,304 @@ function runSubgraphVerifyCommand(options) {
|
|
|
321
323
|
throw new errors_1.ValidationError("subgraph verify failed");
|
|
322
324
|
}
|
|
323
325
|
}
|
|
326
|
+
function pushAuditCheck(receipt, check) {
|
|
327
|
+
receipt.checks.push(check);
|
|
328
|
+
if (!check.ok && check.severity === "error") {
|
|
329
|
+
receipt.errors.push(`${check.id}: ${check.message}`);
|
|
330
|
+
}
|
|
331
|
+
else if (!check.ok && check.severity === "warning") {
|
|
332
|
+
receipt.warnings.push(`${check.id}: ${check.message}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function inspectSourcePathForAudit(root, alias, subgraph) {
|
|
336
|
+
try {
|
|
337
|
+
return inspectSourcePath(root, alias, subgraph, true);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function auditOneAlias(options) {
|
|
344
|
+
const receipt = {
|
|
345
|
+
alias: options.alias,
|
|
346
|
+
enabled: options.health.enabled,
|
|
347
|
+
visibility: options.health.visibility,
|
|
348
|
+
source_path: options.health.source_path,
|
|
349
|
+
source_repo: options.health.source_repo,
|
|
350
|
+
capability_summary: {
|
|
351
|
+
node_count: options.nodeTypes.length,
|
|
352
|
+
spec_count: options.nodeTypes.filter((type) => type === "spec").length,
|
|
353
|
+
work_count: options.nodeTypes.filter((type) => type === "work").length,
|
|
354
|
+
skill_count: options.nodeTypes.filter((type) => type === "skill").length,
|
|
355
|
+
},
|
|
356
|
+
checks: [],
|
|
357
|
+
warnings: [],
|
|
358
|
+
errors: [],
|
|
359
|
+
ok: true,
|
|
360
|
+
};
|
|
361
|
+
pushAuditCheck(receipt, {
|
|
362
|
+
id: "subgraph.enabled",
|
|
363
|
+
ok: options.subgraph.enabled,
|
|
364
|
+
severity: options.subgraph.enabled ? "info" : "warning",
|
|
365
|
+
message: options.subgraph.enabled ? "subgraph is enabled" : "subgraph is disabled",
|
|
366
|
+
});
|
|
367
|
+
if (!options.subgraph.source_path) {
|
|
368
|
+
pushAuditCheck(receipt, {
|
|
369
|
+
id: "subgraph.source_path.configured",
|
|
370
|
+
ok: false,
|
|
371
|
+
severity: "warning",
|
|
372
|
+
message: "source_path is not configured; sync and upgrade planning cannot inspect child Git state",
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
let gitState;
|
|
377
|
+
try {
|
|
378
|
+
gitState = inspectSourcePath(options.root, options.alias, options.subgraph, true);
|
|
379
|
+
receipt.source_git_head = gitState.head;
|
|
380
|
+
receipt.source_repo_current = gitState.sourceRepo;
|
|
381
|
+
receipt.dirty_tracked = gitState.dirtyTracked;
|
|
382
|
+
receipt.dirty_tracked_paths = gitState.dirtyTrackedPaths;
|
|
383
|
+
pushAuditCheck(receipt, {
|
|
384
|
+
id: "subgraph.source_path.git_root",
|
|
385
|
+
ok: true,
|
|
386
|
+
severity: "info",
|
|
387
|
+
message: "source_path is a contained child Git repo root with .mdkg",
|
|
388
|
+
path: options.subgraph.source_path,
|
|
389
|
+
});
|
|
390
|
+
pushAuditCheck(receipt, {
|
|
391
|
+
id: "subgraph.source_path.clean",
|
|
392
|
+
ok: !gitState.dirtyTracked,
|
|
393
|
+
severity: "warning",
|
|
394
|
+
message: gitState.dirtyTracked
|
|
395
|
+
? `source_path has dirty tracked changes: ${gitState.dirtyTrackedPaths.join(", ")}`
|
|
396
|
+
: "source_path has no dirty tracked changes",
|
|
397
|
+
path: options.subgraph.source_path,
|
|
398
|
+
details: { dirty_tracked_paths: gitState.dirtyTrackedPaths },
|
|
399
|
+
});
|
|
400
|
+
if (options.subgraph.source_repo && options.subgraph.source_repo !== gitState.sourceRepo) {
|
|
401
|
+
pushAuditCheck(receipt, {
|
|
402
|
+
id: "subgraph.source_repo.current",
|
|
403
|
+
ok: false,
|
|
404
|
+
severity: "warning",
|
|
405
|
+
message: `configured source_repo differs from child repo head: ${options.subgraph.source_repo} -> ${gitState.sourceRepo}`,
|
|
406
|
+
details: { configured: options.subgraph.source_repo, current: gitState.sourceRepo },
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
pushAuditCheck(receipt, {
|
|
412
|
+
id: "subgraph.source_path.git_root",
|
|
413
|
+
ok: false,
|
|
414
|
+
severity: "error",
|
|
415
|
+
message: err instanceof Error ? err.message : String(err),
|
|
416
|
+
path: options.subgraph.source_path,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (gitState) {
|
|
420
|
+
for (const source of options.subgraph.sources) {
|
|
421
|
+
const bundlePath = path_1.default.resolve(options.root, source.path);
|
|
422
|
+
pushAuditCheck(receipt, {
|
|
423
|
+
id: "subgraph.bundle.root_owned",
|
|
424
|
+
ok: !isPathWithin(gitState.sourceRoot, bundlePath),
|
|
425
|
+
severity: "error",
|
|
426
|
+
message: isPathWithin(gitState.sourceRoot, bundlePath)
|
|
427
|
+
? `bundle path must be root-owned and outside source_path: ${source.path}`
|
|
428
|
+
: `bundle path is root-owned and outside source_path: ${source.path}`,
|
|
429
|
+
path: source.path,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
for (const source of options.health.sources) {
|
|
435
|
+
pushAuditCheck(receipt, {
|
|
436
|
+
id: "subgraph.bundle.enabled",
|
|
437
|
+
ok: source.enabled,
|
|
438
|
+
severity: source.enabled ? "info" : "warning",
|
|
439
|
+
message: source.enabled ? `source is enabled: ${source.path}` : `source is disabled: ${source.path}`,
|
|
440
|
+
path: source.path,
|
|
441
|
+
});
|
|
442
|
+
pushAuditCheck(receipt, {
|
|
443
|
+
id: "subgraph.bundle.valid",
|
|
444
|
+
ok: source.error_count === 0,
|
|
445
|
+
severity: "error",
|
|
446
|
+
message: source.error_count === 0
|
|
447
|
+
? `bundle source is valid: ${source.path}`
|
|
448
|
+
: `bundle source has errors: ${source.errors.join("; ")}`,
|
|
449
|
+
path: source.path,
|
|
450
|
+
details: { errors: source.errors },
|
|
451
|
+
});
|
|
452
|
+
pushAuditCheck(receipt, {
|
|
453
|
+
id: "subgraph.bundle.fresh",
|
|
454
|
+
ok: !source.stale,
|
|
455
|
+
severity: "warning",
|
|
456
|
+
message: source.stale
|
|
457
|
+
? `bundle source is stale: ${source.warnings.join("; ")}`
|
|
458
|
+
: `bundle source is fresh: ${source.path}`,
|
|
459
|
+
path: source.path,
|
|
460
|
+
details: { warnings: source.warnings },
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (options.targetRoot) {
|
|
464
|
+
const outputDir = path_1.default.join(options.targetRoot, options.alias);
|
|
465
|
+
const markerPath = path_1.default.join(outputDir, ".mdkg-materialized.json");
|
|
466
|
+
const exists = fs_1.default.existsSync(outputDir);
|
|
467
|
+
const hasMarker = fs_1.default.existsSync(markerPath);
|
|
468
|
+
pushAuditCheck(receipt, {
|
|
469
|
+
id: "subgraph.materialize.target_safe",
|
|
470
|
+
ok: !exists || hasMarker,
|
|
471
|
+
severity: "error",
|
|
472
|
+
message: !exists
|
|
473
|
+
? `materialize target is available: ${relativeToRoot(options.root, outputDir)}`
|
|
474
|
+
: hasMarker
|
|
475
|
+
? `materialize target has mdkg marker: ${relativeToRoot(options.root, outputDir)}`
|
|
476
|
+
: `materialize target exists without mdkg marker: ${relativeToRoot(options.root, outputDir)}`,
|
|
477
|
+
path: relativeToRoot(options.root, outputDir),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
receipt.ok = receipt.errors.length === 0;
|
|
481
|
+
return receipt;
|
|
482
|
+
}
|
|
483
|
+
function runSubgraphAuditCommand(options) {
|
|
484
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
485
|
+
const aliases = selectAliases(config, options.alias, options.all);
|
|
486
|
+
const projection = (0, subgraphs_1.buildSubgraphsIndex)(options.root, config).index;
|
|
487
|
+
const targetRoot = options.target ? path_1.default.resolve(options.root, normalizeContained(options.target, "--target")) : undefined;
|
|
488
|
+
const results = aliases.map((alias) => {
|
|
489
|
+
const health = projection.subgraphs.find((item) => item.alias === alias);
|
|
490
|
+
if (!health) {
|
|
491
|
+
throw new errors_1.NotFoundError(`subgraph not found: ${alias}`);
|
|
492
|
+
}
|
|
493
|
+
const nodeTypes = Object.values(projection.nodes)
|
|
494
|
+
.filter((node) => node.ws === alias)
|
|
495
|
+
.map((node) => node.type);
|
|
496
|
+
return auditOneAlias({ root: options.root, alias, subgraph: config.subgraphs[alias], health, nodeTypes, targetRoot });
|
|
497
|
+
});
|
|
498
|
+
const errors = results.flatMap((item) => item.errors.map((error) => `${item.alias}: ${error}`));
|
|
499
|
+
const warnings = results.flatMap((item) => item.warnings.map((warning) => `${item.alias}: ${warning}`));
|
|
500
|
+
const receipt = {
|
|
501
|
+
action: "audited",
|
|
502
|
+
ok: errors.length === 0,
|
|
503
|
+
count: results.length,
|
|
504
|
+
target: targetRoot ? relativeToRoot(options.root, targetRoot) : undefined,
|
|
505
|
+
errors,
|
|
506
|
+
warnings,
|
|
507
|
+
subgraphs: results,
|
|
508
|
+
};
|
|
509
|
+
if (options.json) {
|
|
510
|
+
writeJson(receipt);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
console.log(`subgraphs audited: ${results.length}`);
|
|
514
|
+
for (const item of results) {
|
|
515
|
+
const status = item.errors.length > 0 ? "error" : item.warnings.length > 0 ? "warning" : "ok";
|
|
516
|
+
console.log(`${item.alias} | ${status} | checks:${item.checks.length}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (!receipt.ok) {
|
|
520
|
+
throw new errors_1.ValidationError("subgraph audit failed");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function upgradePlanForAlias(options) {
|
|
524
|
+
const actions = [];
|
|
525
|
+
const blockers = [];
|
|
526
|
+
if (!options.subgraph.enabled) {
|
|
527
|
+
actions.push({
|
|
528
|
+
action: "none",
|
|
529
|
+
status: "skipped",
|
|
530
|
+
reason: "subgraph is disabled",
|
|
531
|
+
});
|
|
532
|
+
return { alias: options.alias, ok: true, capability_summary: options.audit.capability_summary, actions, blockers };
|
|
533
|
+
}
|
|
534
|
+
if (!options.subgraph.source_path) {
|
|
535
|
+
blockers.push("source_path is required for upgrade planning");
|
|
536
|
+
}
|
|
537
|
+
const sourceGitError = options.audit.checks.find((check) => check.id === "subgraph.source_path.git_root" && !check.ok && check.severity === "error");
|
|
538
|
+
if (sourceGitError) {
|
|
539
|
+
blockers.push(sourceGitError.message);
|
|
540
|
+
}
|
|
541
|
+
if (options.audit.dirty_tracked) {
|
|
542
|
+
blockers.push(`source_path has dirty tracked changes: ${options.audit.dirty_tracked_paths?.join(", ")}`);
|
|
543
|
+
}
|
|
544
|
+
const rootOwnedErrors = options.audit.checks.filter((check) => check.id === "subgraph.bundle.root_owned" && !check.ok);
|
|
545
|
+
blockers.push(...rootOwnedErrors.map((check) => check.message));
|
|
546
|
+
const bundleErrors = options.health.sources.flatMap((source) => source.errors.map((error) => `${source.path}: ${error}`));
|
|
547
|
+
blockers.push(...bundleErrors);
|
|
548
|
+
const needsSync = options.health.stale ||
|
|
549
|
+
!options.subgraph.source_repo ||
|
|
550
|
+
(options.audit.source_repo_current !== undefined && options.subgraph.source_repo !== options.audit.source_repo_current);
|
|
551
|
+
if (blockers.length > 0) {
|
|
552
|
+
actions.push({
|
|
553
|
+
action: "subgraph.sync",
|
|
554
|
+
status: "blocked",
|
|
555
|
+
command: `mdkg subgraph sync ${options.alias} --dry-run --json`,
|
|
556
|
+
blockers,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
else if (needsSync) {
|
|
560
|
+
actions.push({
|
|
561
|
+
action: "subgraph.sync",
|
|
562
|
+
status: "planned",
|
|
563
|
+
command: `mdkg subgraph sync ${options.alias} --dry-run --json`,
|
|
564
|
+
apply_command: `mdkg subgraph sync ${options.alias} --json`,
|
|
565
|
+
reason: options.health.stale ? "bundle is stale or source HEAD changed" : "configured source_repo is missing or behind current child head",
|
|
566
|
+
current_source_repo: options.audit.source_repo_current,
|
|
567
|
+
configured_source_repo: options.subgraph.source_repo,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
actions.push({
|
|
572
|
+
action: "subgraph.verify",
|
|
573
|
+
status: "planned",
|
|
574
|
+
command: `mdkg subgraph verify ${options.alias} --json`,
|
|
575
|
+
reason: "bundle snapshot is current; verify before downstream use",
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
actions.push({
|
|
579
|
+
action: "subgraph.materialize",
|
|
580
|
+
status: "optional",
|
|
581
|
+
command: `mdkg subgraph materialize ${options.alias} --target .mdkg/subgraphs --gitignore --json`,
|
|
582
|
+
reason: "generate an ignored read-only inspection tree when human review needs file-level child graph context",
|
|
583
|
+
});
|
|
584
|
+
return { alias: options.alias, ok: blockers.length === 0, capability_summary: options.audit.capability_summary, actions, blockers };
|
|
585
|
+
}
|
|
586
|
+
function runSubgraphUpgradePlanCommand(options) {
|
|
587
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
588
|
+
const aliases = selectAliases(config, options.alias, options.all);
|
|
589
|
+
const projection = (0, subgraphs_1.buildSubgraphsIndex)(options.root, config).index;
|
|
590
|
+
const plans = aliases.map((alias) => {
|
|
591
|
+
const health = projection.subgraphs.find((item) => item.alias === alias);
|
|
592
|
+
if (!health) {
|
|
593
|
+
throw new errors_1.NotFoundError(`subgraph not found: ${alias}`);
|
|
594
|
+
}
|
|
595
|
+
const nodeTypes = Object.values(projection.nodes)
|
|
596
|
+
.filter((node) => node.ws === alias)
|
|
597
|
+
.map((node) => node.type);
|
|
598
|
+
const audit = auditOneAlias({ root: options.root, alias, subgraph: config.subgraphs[alias], health, nodeTypes });
|
|
599
|
+
return upgradePlanForAlias({ root: options.root, alias, subgraph: config.subgraphs[alias], audit, health });
|
|
600
|
+
});
|
|
601
|
+
const blockers = plans.flatMap((plan) => plan.blockers.map((blocker) => `${plan.alias}: ${blocker}`));
|
|
602
|
+
const receipt = {
|
|
603
|
+
action: "upgrade_plan",
|
|
604
|
+
ok: blockers.length === 0,
|
|
605
|
+
count: plans.length,
|
|
606
|
+
apply_supported: false,
|
|
607
|
+
mutation_policy: "read_only_plan",
|
|
608
|
+
blockers,
|
|
609
|
+
subgraphs: plans,
|
|
610
|
+
};
|
|
611
|
+
if (options.json) {
|
|
612
|
+
writeJson(receipt);
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
console.log(`subgraph upgrade plan: ${plans.length}`);
|
|
616
|
+
for (const plan of plans) {
|
|
617
|
+
console.log(`${plan.alias} | ${plan.ok ? "ok" : "blocked"} | actions:${plan.actions.length}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (!receipt.ok) {
|
|
621
|
+
throw new errors_1.ValidationError("subgraph upgrade plan blocked");
|
|
622
|
+
}
|
|
623
|
+
}
|
|
324
624
|
function runSubgraphRefreshCommand(options) {
|
|
325
625
|
withSubgraphLock(options.root, () => {
|
|
326
626
|
const config = (0, config_1.loadConfig)(options.root);
|
|
@@ -274,7 +274,7 @@ function runValidateCommand(options) {
|
|
|
274
274
|
});
|
|
275
275
|
if (idsByWorkspace[alias].has(node.id)) {
|
|
276
276
|
const firstPath = idsByWorkspace[alias].get(node.id);
|
|
277
|
-
errors.push(`${filePath}: duplicate id ${node.id} in workspace ${alias} (also in ${firstPath})`);
|
|
277
|
+
errors.push(`${path_1.default.relative(options.root, filePath).split(path_1.default.sep).join("/")}: duplicate id ${node.id} in workspace ${alias} (also in ${firstPath ? path_1.default.relative(options.root, firstPath).split(path_1.default.sep).join("/") : "unknown"})`);
|
|
278
278
|
continue;
|
|
279
279
|
}
|
|
280
280
|
idsByWorkspace[alias].set(node.id, filePath);
|