mdkg 0.1.3 → 0.1.4

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 CHANGED
@@ -4,7 +4,33 @@ All notable changes to mdkg are documented here.
4
4
 
5
5
  This project follows a pragmatic changelog style inspired by Keep a Changelog. Versions use npm package versions.
6
6
 
7
- ## 0.1.3 - Unreleased
7
+ mdkg is pre-v1 public alpha software. Command, graph, cache, bundle, and DAL contracts may change quickly while the project converges on a stable v1 surface.
8
+
9
+ ## 0.1.4 - Unreleased
10
+
11
+ ### Added
12
+
13
+ - Added `mdkg subgraph add/list/show/rm/enable/disable/verify/refresh` as the public read-only child graph orchestration command family.
14
+ - Added `subgraphs` config with multi-source bundle transport, advisory visibility, read permissions, source metadata, and a default 60 minute freshness policy.
15
+ - Added `.mdkg/index/subgraphs.json` as the derived subgraph projection and health cache.
16
+ - Added `mdkg capability resolve [query] [--requires <capability>] [--fresh-only] [--json]` for deterministic local plus subgraph capability ranking.
17
+ - Added packed-package `smoke:subgraph` coverage for root, child, and grandchild orchestration flows.
18
+
19
+ ### Changed
20
+
21
+ - Replaced the public `mdkg bundle import ...` surface with `mdkg subgraph ...`; legacy calls now exit with migration guidance.
22
+ - `mdkg upgrade --apply` migrates legacy `bundle_imports` config into `subgraphs`.
23
+ - Read commands, `pack`, and capability discovery now project enabled child bundles as read-only subgraph qids such as `child_repo:work.example`.
24
+ - `mdkg index`, SQLite cache rebuilds, `doctor`, and `validate` now use subgraph naming and metadata instead of bundle-import naming.
25
+ - Stale subgraphs remain usable for planning reads with warnings, fail `mdkg subgraph verify`, and are excluded from `capability resolve --fresh-only`.
26
+ - Public/internal subgraphs require public bundle profiles and public bundle creation fails closed on private/internal subgraph references.
27
+
28
+ ### Fixed
29
+
30
+ - Mutation commands now reject subgraph qids with explicit guidance to update the source workspace for the owning subgraph.
31
+ - Seeded init docs, command matrix, and release skills now teach `subgraph` and `capability resolve` instead of onboarding users through `bundle import`.
32
+
33
+ ## 0.1.3 - 2026-05-20
8
34
 
9
35
  ### Added
10
36
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ mdkg stays deliberately boring:
14
14
  - first-class rebuildable SQLite cache through built-in `node:sqlite`
15
15
  - no daemon, hosted index, or vector DB
16
16
 
17
- Current package version in source: `0.1.3`
17
+ Current package version in source: `0.1.4`
18
18
 
19
19
  mdkg is still pre-v1 public alpha software. The public package is usable, but graph, cache, bundle, and DAL contracts may continue to change quickly while the project converges on a stable v1 surface.
20
20
 
@@ -110,20 +110,21 @@ mdkg bundle verify .mdkg/bundles/private/all.mdkg.zip
110
110
  mdkg bundle list --json
111
111
  ```
112
112
 
113
- Bundles are explicit graph transport artifacts, separate from task context packs. Before a commit in repos that track archives or bundles, refresh compressed archive caches first, then create the private bundle so the committed graph state is self-consistent. Private bundles are the default and may be committed in private repos when configured. Public bundles require at least one selected workspace with `visibility: public` and include only public workspace content and public archive sidecars; bundle creation fails if public content points at private graph, archive, or imported bundle records.
113
+ Bundles are explicit graph transport artifacts, separate from task context packs. Before a commit in repos that track archives or bundles, refresh compressed archive caches first, then create the private bundle so the committed graph state is self-consistent. Private bundles are the default and may be committed in private repos when configured. Public bundles require at least one selected workspace with `visibility: public` and include only public workspace content and public archive sidecars; bundle creation fails if public content points at private graph, archive, or subgraph records.
114
114
 
115
- Import a child repo bundle as a read-only planning view:
115
+ Register a child repo bundle as a read-only subgraph planning view:
116
116
 
117
117
  ```bash
118
- mdkg bundle import add child_repo child-repo/.mdkg/bundles/private/all.mdkg.zip --source-path child-repo
119
- mdkg bundle import list --json
118
+ mdkg subgraph add child_repo child-repo/.mdkg/bundles/private/all.mdkg.zip --source-path child-repo
119
+ mdkg subgraph list --json
120
120
  mdkg search "child capability"
121
121
  mdkg show child_repo:work.example
122
122
  mdkg pack child_repo:work.example --dry-run --stats
123
- mdkg bundle import verify child_repo --json
123
+ mdkg capability resolve "child capability" --json
124
+ mdkg subgraph verify child_repo --json
124
125
  ```
125
126
 
126
- Imported bundle nodes are projected under the import alias, for example `child_repo:task-1`. They are available to `list`, `search`, `show`, `pack`, and capability discovery, but remain read-only; mutate the child repo and refresh its bundle to change imported content. Stale imports warn during planning reads and fail `mdkg bundle import verify`. Public or internal imports must be backed by public bundle profiles; private imports stay private planning context.
127
+ Subgraph nodes are projected under the subgraph alias, for example `child_repo:task-1`. They are available to `list`, `search`, `show`, `pack`, capability discovery, and `capability resolve`, but remain read-only; mutate the child repo and refresh its bundle to change subgraph content. Stale subgraphs warn during planning reads and fail `mdkg subgraph verify`. Public or internal subgraphs must be backed by public bundle profiles; private subgraphs stay private planning context.
127
128
 
128
129
  Validate before handoff or commit:
129
130
 
@@ -158,7 +159,7 @@ mdkg work artifact add receipt.generate-image-1 ./outputs/image.png --id archive
158
159
  ```
159
160
 
160
161
  Receipt statuses are `recorded`, `verified`, `rejected`, and `superseded`.
161
- Update and artifact commands accept local ids or local qids; imported bundle qids are read-only and must be changed in their source workspace.
162
+ Update and artifact commands accept local ids or local qids; subgraph qids are read-only and must be changed in their source workspace.
162
163
 
163
164
  Update structured task state and evidence while keeping body and narrative edits in markdown:
164
165
 
@@ -206,7 +207,7 @@ mdkg lives under a hidden root directory:
206
207
  - `.mdkg/archive/` sidecar metadata plus deterministic compressed source/artifact caches
207
208
  - `.mdkg/bundles/` optional committed full graph snapshot bundles
208
209
  - `.mdkg/index/mdkg.sqlite` optional committed, rebuildable SQLite access cache
209
- - `.mdkg/index/imports.json` generated read-only bundle import cache
210
+ - `.mdkg/index/subgraphs.json` generated read-only subgraph projection cache
210
211
  - `.agents/skills/` Codex/OpenAI-facing mirrored skills
211
212
  - `.claude/skills/` Claude-facing mirrored skills
212
213
  - `.mdkg/index/*.json` generated JSON compatibility cache files
@@ -300,7 +301,7 @@ Capability records aggregate enabled registered workspaces and include determini
300
301
 
301
302
  Fresh `mdkg init` workspaces default to `index.backend: sqlite`, which writes `.mdkg/index/mdkg.sqlite` as a rebuildable access cache using Node's built-in `node:sqlite`. Existing workspaces that are migrated from older configs default to `index.backend: json` until they opt in. Markdown files, archive sidecars, bundle manifests, and config remain source of truth in both modes.
302
303
 
303
- `mdkg index` still writes JSON compatibility caches (`global.json`, `skills.json`, `capabilities.json`, and import projections when configured). In SQLite mode it also rebuilds the SQLite cache with nodes, edges, skills, capabilities, archive metadata, bundle imports, source hashes, and schema metadata. Deleting the SQLite file is recoverable with `mdkg index`.
304
+ `mdkg index` still writes JSON compatibility caches (`global.json`, `skills.json`, `capabilities.json`, and subgraph projections when configured). In SQLite mode it also rebuilds the SQLite cache with nodes, edges, skills, capabilities, archive metadata, subgraphs, source hashes, and schema metadata. Deleting the SQLite file is recoverable with `mdkg index`.
304
305
 
305
306
  Mutating commands use a workspace mutation lock plus atomic writes. SQLite mode additionally reserves numeric ids in a SQLite transaction before writing Markdown so parallel `mdkg new` and checkpoint calls avoid naming conflicts. Skipped ids after failed writes are acceptable because Markdown remains canonical.
306
307
 
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ const doctor_1 = require("./commands/doctor");
21
21
  const capability_1 = require("./commands/capability");
22
22
  const archive_1 = require("./commands/archive");
23
23
  const bundle_1 = require("./commands/bundle");
24
- const bundle_import_1 = require("./commands/bundle_import");
24
+ const subgraph_1 = require("./commands/subgraph");
25
25
  const checkpoint_1 = require("./commands/checkpoint");
26
26
  const init_1 = require("./commands/init");
27
27
  const new_1 = require("./commands/new");
@@ -60,9 +60,10 @@ function printUsage(log) {
60
60
  log(" search Search nodes by query");
61
61
  log(" pack Generate a context pack");
62
62
  log(" skill Create, list, show, search, and validate skills");
63
- log(" capability List, search, and show cached capability surfaces");
63
+ log(" capability List, search, show, and resolve cached capability surfaces");
64
64
  log(" archive Add, list, show, verify, and compress archive sidecars");
65
65
  log(" bundle Create, list, show, and verify full graph snapshot bundles");
66
+ log(" subgraph Register and verify read-only child graph snapshots");
66
67
  log(" work Create and update work contracts, orders, receipts, and artifacts");
67
68
  log(" task Start, update, and complete task-like nodes");
68
69
  log(" next Suggest the next work item");
@@ -171,7 +172,7 @@ function printIndexHelp(log) {
171
172
  log(" - .mdkg/index/global.json");
172
173
  log(" - .mdkg/index/skills.json");
173
174
  log(" - .mdkg/index/capabilities.json");
174
- log(" - .mdkg/index/imports.json when bundle imports are configured");
175
+ log(" - .mdkg/index/subgraphs.json when subgraphs are configured");
175
176
  log(" - .mdkg/index/mdkg.sqlite when index.backend is sqlite");
176
177
  printGlobalOptions(log);
177
178
  }
@@ -322,14 +323,23 @@ function printCapabilityHelp(log, subcommand) {
322
323
  log(" mdkg capability show <id-or-qid-or-slug> [--json]");
323
324
  printGlobalOptions(log);
324
325
  return;
326
+ case "resolve":
327
+ log("Usage:");
328
+ log(' mdkg capability resolve [query] [--requires <capability>] [--fresh-only] [--json]');
329
+ log("\nNotes:");
330
+ log(" Resolves local and subgraph capabilities with deterministic ranking.");
331
+ printGlobalOptions(log);
332
+ return;
325
333
  default:
326
334
  log("Usage:");
327
335
  log(" mdkg capability list [--kind <kind>] [--visibility <level>] [--json]");
328
336
  log(' mdkg capability search "<query>" [--kind <kind>] [--visibility <level>] [--json]');
329
337
  log(" mdkg capability show <id-or-qid-or-slug> [--json]");
338
+ log(' mdkg capability resolve [query] [--requires <capability>] [--fresh-only] [--json]');
330
339
  log("\nNotes:");
331
340
  log(" Capability records are deterministic cache projections from Markdown.");
332
341
  log(" Cached kinds: skill, spec, work, core, design.");
342
+ log(" Resolve includes read-only subgraph capability records when configured.");
333
343
  printGlobalOptions(log);
334
344
  }
335
345
  }
@@ -375,12 +385,8 @@ function printBundleHelp(log, subcommand) {
375
385
  switch ((subcommand ?? "").toLowerCase()) {
376
386
  case "import":
377
387
  log("Usage:");
378
- log(" mdkg bundle import add <alias> <bundle-path> [--visibility private|internal|public] [--profile private|public] [--source-path <path>] [--source-repo <ref>] [--max-stale-seconds <seconds>] [--json]");
379
- log(" mdkg bundle import list [--json]");
380
- log(" mdkg bundle import rm <alias> [--json]");
381
- log(" mdkg bundle import enable <alias> [--json]");
382
- log(" mdkg bundle import disable <alias> [--json]");
383
- log(" mdkg bundle import verify [alias|--all] [--json]");
388
+ log(" mdkg subgraph add/list/show/rm/enable/disable/verify/refresh ...");
389
+ log("\n`mdkg bundle import` has been replaced by `mdkg subgraph`.");
384
390
  break;
385
391
  case "create":
386
392
  log("Usage:");
@@ -404,15 +410,66 @@ function printBundleHelp(log, subcommand) {
404
410
  log(" mdkg bundle verify [bundle-path] [--json]");
405
411
  log(" mdkg bundle show <bundle-path> [--json]");
406
412
  log(" mdkg bundle list [--json]");
407
- log(" mdkg bundle import add/list/rm/enable/disable/verify ...");
408
413
  log("\nNotes:");
409
414
  log(" - bundles are explicit full .mdkg graph snapshots, not task context packs");
410
- log(" - bundle imports are read-only graph views projected under their import alias");
415
+ log(" - use `mdkg subgraph ...` to register bundle snapshots as read-only planning views");
411
416
  log(" - private is the default profile; public bundles fail closed on private refs");
412
417
  log(" - .mdkg/bundles/ is commit-eligible when your repo tracks snapshot bundles");
413
418
  }
414
419
  printGlobalOptions(log);
415
420
  }
421
+ function printSubgraphHelp(log, subcommand) {
422
+ switch ((subcommand ?? "").toLowerCase()) {
423
+ case "add":
424
+ log("Usage:");
425
+ log(" mdkg subgraph add <alias> <bundle-path> [--visibility private|internal|public] [--profile private|public] [--source-path <path>] [--source-repo <ref>] [--max-stale-seconds <seconds>] [--json]");
426
+ break;
427
+ case "list":
428
+ log("Usage:");
429
+ log(" mdkg subgraph list [--json]");
430
+ break;
431
+ case "show":
432
+ log("Usage:");
433
+ log(" mdkg subgraph show <alias> [--json]");
434
+ break;
435
+ case "rm":
436
+ case "remove":
437
+ log("Usage:");
438
+ log(" mdkg subgraph rm <alias> [--json]");
439
+ break;
440
+ case "enable":
441
+ log("Usage:");
442
+ log(" mdkg subgraph enable <alias> [--json]");
443
+ break;
444
+ case "disable":
445
+ log("Usage:");
446
+ log(" mdkg subgraph disable <alias> [--json]");
447
+ break;
448
+ case "verify":
449
+ log("Usage:");
450
+ log(" mdkg subgraph verify [alias|--all] [--json]");
451
+ break;
452
+ case "refresh":
453
+ log("Usage:");
454
+ log(" mdkg subgraph refresh [alias|--all] [--json]");
455
+ break;
456
+ default:
457
+ log("Usage:");
458
+ log(" mdkg subgraph add <alias> <bundle-path> [--visibility private|internal|public] [--profile private|public] [--source-path <path>] [--source-repo <ref>] [--max-stale-seconds <seconds>] [--json]");
459
+ log(" mdkg subgraph list [--json]");
460
+ log(" mdkg subgraph show <alias> [--json]");
461
+ log(" mdkg subgraph rm <alias> [--json]");
462
+ log(" mdkg subgraph enable <alias> [--json]");
463
+ log(" mdkg subgraph disable <alias> [--json]");
464
+ log(" mdkg subgraph verify [alias|--all] [--json]");
465
+ log(" mdkg subgraph refresh [alias|--all] [--json]");
466
+ log("\nNotes:");
467
+ log(" - subgraphs are read-only graph views backed by explicit bundle snapshots");
468
+ log(" - default permissions are read-only and default freshness is 3600 seconds");
469
+ log(" - refresh reloads configured bundle sources only; it does not build child bundles");
470
+ }
471
+ printGlobalOptions(log);
472
+ }
416
473
  function printWorkHelp(log, subcommand) {
417
474
  switch ((subcommand ?? "").toLowerCase()) {
418
475
  case "contract":
@@ -444,7 +501,7 @@ function printWorkHelp(log, subcommand) {
444
501
  log(" - production order, receipt, feedback, dispute, payment, ledger, marketplace inventory, fulfillment, and execution state remains canonical outside mdkg");
445
502
  log(" - do not store raw secrets, credentials, live payment state, ledger mutations, or canonical marketplace state in work mirrors");
446
503
  log(" - artifact:// refs identify external/runtime-managed artifacts; archive:// refs identify committed mdkg archive sidecars");
447
- log(" - update and artifact commands accept local ids or local qids; imported bundle qids are read-only");
504
+ log(" - update and artifact commands accept local ids or local qids; subgraph qids are read-only");
448
505
  }
449
506
  printGlobalOptions(log);
450
507
  }
@@ -599,6 +656,9 @@ function printCommandHelp(log, command, subcommand) {
599
656
  case "bundle":
600
657
  printBundleHelp(log, subcommand);
601
658
  return;
659
+ case "subgraph":
660
+ printSubgraphHelp(log, subcommand);
661
+ return;
602
662
  case "work":
603
663
  printWorkHelp(log, subcommand);
604
664
  return;
@@ -871,8 +931,20 @@ function runCapabilitySubcommand(parsed, root) {
871
931
  (0, capability_1.runCapabilityShowCommand)({ root, id, json, noCache, noReindex });
872
932
  return 0;
873
933
  }
934
+ case "resolve": {
935
+ const query = parsed.positionals.slice(2).join(" ") || undefined;
936
+ const kind = requireFlagValue("--kind", parsed.flags["--kind"]);
937
+ const visibility = requireFlagValue("--visibility", parsed.flags["--visibility"]);
938
+ const requires = requireFlagValue("--requires", parsed.flags["--requires"]);
939
+ const freshOnly = parseBooleanFlag("--fresh-only", parsed.flags["--fresh-only"]);
940
+ const json = parseBooleanFlag("--json", parsed.flags["--json"]);
941
+ const noCache = parseBooleanFlag("--no-cache", parsed.flags["--no-cache"]);
942
+ const noReindex = parseBooleanFlag("--no-reindex", parsed.flags["--no-reindex"]);
943
+ (0, capability_1.runCapabilityResolveCommand)({ root, query, kind, visibility, requires, freshOnly, json, noCache, noReindex });
944
+ return 0;
945
+ }
874
946
  default:
875
- throw new errors_1.UsageError("capability requires list/search/show");
947
+ throw new errors_1.UsageError("capability requires list/search/show/resolve");
876
948
  }
877
949
  }
878
950
  function runArchiveSubcommand(parsed, root) {
@@ -944,78 +1016,7 @@ function runBundleSubcommand(parsed, root) {
944
1016
  const subcommand = (parsed.positionals[1] ?? "").toLowerCase();
945
1017
  switch (subcommand) {
946
1018
  case "import": {
947
- const action = (parsed.positionals[2] ?? "").toLowerCase();
948
- const json = parseBooleanFlag("--json", parsed.flags["--json"]);
949
- switch (action) {
950
- case "add": {
951
- const alias = parsed.positionals[3];
952
- const bundlePath = parsed.positionals[4];
953
- if (!alias || !bundlePath || parsed.positionals.length > 5) {
954
- throw new errors_1.UsageError("bundle import add requires <alias> <bundle-path>");
955
- }
956
- const visibility = requireFlagValue("--visibility", parsed.flags["--visibility"]);
957
- const profile = requireFlagValue("--profile", parsed.flags["--pack-profile"]);
958
- const sourcePath = requireFlagValue("--source-path", parsed.flags["--source-path"]);
959
- const sourceRepo = requireFlagValue("--source-repo", parsed.flags["--source-repo"]);
960
- const maxStaleRaw = requireFlagValue("--max-stale-seconds", parsed.flags["--max-stale-seconds"]);
961
- const maxStaleSeconds = maxStaleRaw === undefined ? undefined : Number.parseInt(maxStaleRaw, 10);
962
- (0, bundle_import_1.runBundleImportAddCommand)({
963
- root,
964
- alias,
965
- bundlePath,
966
- visibility,
967
- profile,
968
- sourcePath,
969
- sourceRepo,
970
- maxStaleSeconds,
971
- json,
972
- });
973
- return 0;
974
- }
975
- case "list": {
976
- if (parsed.positionals.length > 3) {
977
- throw new errors_1.UsageError("bundle import list does not accept positional arguments");
978
- }
979
- (0, bundle_import_1.runBundleImportListCommand)({ root, json });
980
- return 0;
981
- }
982
- case "rm":
983
- case "remove": {
984
- const alias = parsed.positionals[3];
985
- if (!alias || parsed.positionals.length > 4) {
986
- throw new errors_1.UsageError("bundle import rm requires <alias>");
987
- }
988
- (0, bundle_import_1.runBundleImportRemoveCommand)({ root, alias, json });
989
- return 0;
990
- }
991
- case "enable": {
992
- const alias = parsed.positionals[3];
993
- if (!alias || parsed.positionals.length > 4) {
994
- throw new errors_1.UsageError("bundle import enable requires <alias>");
995
- }
996
- (0, bundle_import_1.runBundleImportEnableCommand)({ root, alias, json });
997
- return 0;
998
- }
999
- case "disable": {
1000
- const alias = parsed.positionals[3];
1001
- if (!alias || parsed.positionals.length > 4) {
1002
- throw new errors_1.UsageError("bundle import disable requires <alias>");
1003
- }
1004
- (0, bundle_import_1.runBundleImportDisableCommand)({ root, alias, json });
1005
- return 0;
1006
- }
1007
- case "verify": {
1008
- if (parsed.positionals.length > 4) {
1009
- throw new errors_1.UsageError("bundle import verify accepts at most one alias");
1010
- }
1011
- const alias = parsed.positionals[3];
1012
- const all = parseBooleanFlag("--all", parsed.flags["--all"]);
1013
- (0, bundle_import_1.runBundleImportVerifyCommand)({ root, alias, all, json });
1014
- return 0;
1015
- }
1016
- default:
1017
- throw new errors_1.UsageError("bundle import requires add/list/rm/enable/disable/verify");
1018
- }
1019
+ throw new errors_1.UsageError("mdkg bundle import has been replaced by mdkg subgraph; run `mdkg upgrade --apply` to migrate legacy bundle_imports config");
1019
1020
  }
1020
1021
  case "create": {
1021
1022
  if (parsed.positionals.length > 2) {
@@ -1055,7 +1056,98 @@ function runBundleSubcommand(parsed, root) {
1055
1056
  return 0;
1056
1057
  }
1057
1058
  default:
1058
- throw new errors_1.UsageError("bundle requires create/list/show/verify/import");
1059
+ throw new errors_1.UsageError("bundle requires create/list/show/verify");
1060
+ }
1061
+ }
1062
+ function runSubgraphSubcommand(parsed, root) {
1063
+ const subcommand = (parsed.positionals[1] ?? "").toLowerCase();
1064
+ const json = parseBooleanFlag("--json", parsed.flags["--json"]);
1065
+ switch (subcommand) {
1066
+ case "add": {
1067
+ const alias = parsed.positionals[2];
1068
+ const bundlePath = parsed.positionals[3];
1069
+ if (!alias || !bundlePath || parsed.positionals.length > 4) {
1070
+ throw new errors_1.UsageError("subgraph add requires <alias> <bundle-path>");
1071
+ }
1072
+ const visibility = requireFlagValue("--visibility", parsed.flags["--visibility"]);
1073
+ const profile = requireFlagValue("--profile", parsed.flags["--pack-profile"]);
1074
+ const sourcePath = requireFlagValue("--source-path", parsed.flags["--source-path"]);
1075
+ const sourceRepo = requireFlagValue("--source-repo", parsed.flags["--source-repo"]);
1076
+ const maxStaleRaw = requireFlagValue("--max-stale-seconds", parsed.flags["--max-stale-seconds"]);
1077
+ const maxStaleSeconds = maxStaleRaw === undefined ? undefined : Number.parseInt(maxStaleRaw, 10);
1078
+ (0, subgraph_1.runSubgraphAddCommand)({
1079
+ root,
1080
+ alias,
1081
+ bundlePath,
1082
+ visibility,
1083
+ profile,
1084
+ sourcePath,
1085
+ sourceRepo,
1086
+ maxStaleSeconds,
1087
+ json,
1088
+ });
1089
+ return 0;
1090
+ }
1091
+ case "list": {
1092
+ if (parsed.positionals.length > 2) {
1093
+ throw new errors_1.UsageError("subgraph list does not accept positional arguments");
1094
+ }
1095
+ (0, subgraph_1.runSubgraphListCommand)({ root, json });
1096
+ return 0;
1097
+ }
1098
+ case "show": {
1099
+ const alias = parsed.positionals[2];
1100
+ if (!alias || parsed.positionals.length > 3) {
1101
+ throw new errors_1.UsageError("subgraph show requires <alias>");
1102
+ }
1103
+ (0, subgraph_1.runSubgraphShowCommand)({ root, alias, json });
1104
+ return 0;
1105
+ }
1106
+ case "rm":
1107
+ case "remove": {
1108
+ const alias = parsed.positionals[2];
1109
+ if (!alias || parsed.positionals.length > 3) {
1110
+ throw new errors_1.UsageError("subgraph rm requires <alias>");
1111
+ }
1112
+ (0, subgraph_1.runSubgraphRemoveCommand)({ root, alias, json });
1113
+ return 0;
1114
+ }
1115
+ case "enable": {
1116
+ const alias = parsed.positionals[2];
1117
+ if (!alias || parsed.positionals.length > 3) {
1118
+ throw new errors_1.UsageError("subgraph enable requires <alias>");
1119
+ }
1120
+ (0, subgraph_1.runSubgraphEnableCommand)({ root, alias, json });
1121
+ return 0;
1122
+ }
1123
+ case "disable": {
1124
+ const alias = parsed.positionals[2];
1125
+ if (!alias || parsed.positionals.length > 3) {
1126
+ throw new errors_1.UsageError("subgraph disable requires <alias>");
1127
+ }
1128
+ (0, subgraph_1.runSubgraphDisableCommand)({ root, alias, json });
1129
+ return 0;
1130
+ }
1131
+ case "verify": {
1132
+ if (parsed.positionals.length > 3) {
1133
+ throw new errors_1.UsageError("subgraph verify accepts at most one alias");
1134
+ }
1135
+ const alias = parsed.positionals[2];
1136
+ const all = parseBooleanFlag("--all", parsed.flags["--all"]);
1137
+ (0, subgraph_1.runSubgraphVerifyCommand)({ root, alias, all, json });
1138
+ return 0;
1139
+ }
1140
+ case "refresh": {
1141
+ if (parsed.positionals.length > 3) {
1142
+ throw new errors_1.UsageError("subgraph refresh accepts at most one alias");
1143
+ }
1144
+ const alias = parsed.positionals[2];
1145
+ const all = parseBooleanFlag("--all", parsed.flags["--all"]);
1146
+ (0, subgraph_1.runSubgraphRefreshCommand)({ root, alias, all, json });
1147
+ return 0;
1148
+ }
1149
+ default:
1150
+ throw new errors_1.UsageError("subgraph requires add/list/show/rm/enable/disable/verify/refresh");
1059
1151
  }
1060
1152
  }
1061
1153
  function runWorkSubcommand(parsed, root) {
@@ -1567,6 +1659,8 @@ function runCommand(parsed, root, runtime) {
1567
1659
  return runArchiveSubcommand(parsed, root);
1568
1660
  case "bundle":
1569
1661
  return runBundleSubcommand(parsed, root);
1662
+ case "subgraph":
1663
+ return runSubgraphSubcommand(parsed, root);
1570
1664
  case "work":
1571
1665
  return runWorkSubcommand(parsed, root);
1572
1666
  case "task":
@@ -13,7 +13,7 @@ const path_1 = __importDefault(require("path"));
13
13
  const child_process_1 = require("child_process");
14
14
  const config_1 = require("../core/config");
15
15
  const capabilities_indexer_1 = require("../graph/capabilities_indexer");
16
- const bundle_imports_1 = require("../graph/bundle_imports");
16
+ const subgraphs_1 = require("../graph/subgraphs");
17
17
  const indexer_1 = require("../graph/indexer");
18
18
  const skills_indexer_1 = require("../graph/skills_indexer");
19
19
  const zip_1 = require("../util/zip");
@@ -386,11 +386,11 @@ function collectStringValues(value, out) {
386
386
  }
387
387
  }
388
388
  }
389
- function publicImportReferenceErrors(config, index, includedQids) {
390
- const privateImportAliases = new Set(Object.entries(config.bundle_imports)
389
+ function publicSubgraphReferenceErrors(config, index, includedQids) {
390
+ const privateSubgraphAliases = new Set(Object.entries(config.subgraphs)
391
391
  .filter(([, entry]) => entry.enabled && entry.visibility !== "public")
392
392
  .map(([alias]) => alias));
393
- if (privateImportAliases.size === 0) {
393
+ if (privateSubgraphAliases.size === 0) {
394
394
  return [];
395
395
  }
396
396
  const errors = [];
@@ -404,8 +404,8 @@ function publicImportReferenceErrors(config, index, includedQids) {
404
404
  values.push(...node.links, ...node.artifacts, ...node.refs, ...node.aliases);
405
405
  for (const value of values) {
406
406
  const [alias] = value.split(":");
407
- if (alias && privateImportAliases.has(alias)) {
408
- errors.push(`${node.qid} references private bundle import ${value}`);
407
+ if (alias && privateSubgraphAliases.has(alias)) {
408
+ errors.push(`${node.qid} references private subgraph ${value}`);
409
409
  }
410
410
  }
411
411
  }
@@ -465,7 +465,7 @@ function buildBundle(options) {
465
465
  const filteredIndex = filterIndex(index, config, selectedSet, profile);
466
466
  if (profile === "public") {
467
467
  const includedQids = new Set(Object.keys(filteredIndex.nodes));
468
- const mergedIndex = (0, bundle_imports_1.mergeBundleImportsIntoIndex)(index, (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config));
468
+ const mergedIndex = (0, subgraphs_1.mergeSubgraphsIntoIndex)(index, (0, subgraphs_1.buildSubgraphsIndex)(options.root, config));
469
469
  const errors = (0, visibility_1.visibilityViolationMessages)((0, visibility_1.collectVisibilityViolations)(mergedIndex, config, {
470
470
  includedQids,
471
471
  scope: "public",
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCapabilityListCommand = runCapabilityListCommand;
4
4
  exports.runCapabilitySearchCommand = runCapabilitySearchCommand;
5
5
  exports.runCapabilityShowCommand = runCapabilityShowCommand;
6
+ exports.runCapabilityResolveCommand = runCapabilityResolveCommand;
6
7
  const config_1 = require("../core/config");
7
8
  const capabilities_indexer_1 = require("../graph/capabilities_indexer");
8
9
  const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
9
- const bundle_imports_1 = require("../graph/bundle_imports");
10
+ const subgraphs_1 = require("../graph/subgraphs");
10
11
  const errors_1 = require("../util/errors");
11
12
  function normalizeKind(value) {
12
13
  if (value === undefined) {
@@ -39,11 +40,11 @@ function loadRecords(options) {
39
40
  if (stale && !rebuilt && !options.noCache) {
40
41
  console.error("warning: capabilities index is stale; run mdkg index to refresh");
41
42
  }
42
- const imported = (0, bundle_imports_1.buildImportedCapabilityRecords)(options.root, config);
43
- for (const warning of imported.warnings) {
43
+ const subgraph = (0, subgraphs_1.buildSubgraphCapabilityRecords)(options.root, config);
44
+ for (const warning of subgraph.warnings) {
44
45
  console.error(`warning: ${warning}`);
45
46
  }
46
- return [...index.records, ...imported.records];
47
+ return [...index.records, ...subgraph.records];
47
48
  }
48
49
  function applyFilters(records, options) {
49
50
  const kind = normalizeKind(options.kind);
@@ -95,6 +96,89 @@ function matchesQuery(record, query) {
95
96
  const text = capabilitySearchText(record);
96
97
  return terms.every((term) => text.includes(term));
97
98
  }
99
+ function recordSource(record) {
100
+ const source = record.source;
101
+ return source ?? {};
102
+ }
103
+ function isStale(record) {
104
+ return recordSource(record).stale === true;
105
+ }
106
+ function hasReadPermission(record) {
107
+ const permissions = recordSource(record).permissions;
108
+ return !Array.isArray(permissions) || permissions.includes("read");
109
+ }
110
+ function requirementMatch(record, required) {
111
+ if (!required) {
112
+ return 0;
113
+ }
114
+ const normalized = required.toLowerCase();
115
+ const haystack = [
116
+ ...record.refs,
117
+ ...record.tags,
118
+ JSON.stringify(record.spec ?? {}),
119
+ JSON.stringify(record.work ?? {}),
120
+ JSON.stringify(record.skill ?? {}),
121
+ ]
122
+ .join(" ")
123
+ .toLowerCase();
124
+ return haystack.includes(normalized) ? 1 : 0;
125
+ }
126
+ function linkageScore(record) {
127
+ let score = 0;
128
+ if (record.kind === "work") {
129
+ score += 2;
130
+ }
131
+ if (record.kind === "spec") {
132
+ score += 1;
133
+ }
134
+ if (record.work || record.spec) {
135
+ score += 1;
136
+ }
137
+ return score;
138
+ }
139
+ function resolveScore(record, options) {
140
+ let score = 0;
141
+ if (record.workspace === "root") {
142
+ score += 1000;
143
+ }
144
+ if (!isStale(record)) {
145
+ score += 500;
146
+ }
147
+ if (hasReadPermission(record)) {
148
+ score += 100;
149
+ }
150
+ if (options.query && matchesQuery(record, options.query)) {
151
+ score += 50;
152
+ }
153
+ score += requirementMatch(record, options.requires) * 25;
154
+ score += linkageScore(record);
155
+ return score;
156
+ }
157
+ function resolveCapabilities(records, options) {
158
+ const filtered = records.filter((record) => {
159
+ if (options.freshOnly && isStale(record)) {
160
+ return false;
161
+ }
162
+ if (options.query && !matchesQuery(record, options.query)) {
163
+ return false;
164
+ }
165
+ if (options.requires && requirementMatch(record, options.requires) === 0) {
166
+ return false;
167
+ }
168
+ return true;
169
+ });
170
+ return filtered.sort((a, b) => {
171
+ const scoreDelta = resolveScore(b, options) - resolveScore(a, options);
172
+ if (scoreDelta !== 0) {
173
+ return scoreDelta;
174
+ }
175
+ const qidDelta = a.qid.localeCompare(b.qid);
176
+ if (qidDelta !== 0) {
177
+ return qidDelta;
178
+ }
179
+ return a.path.localeCompare(b.path);
180
+ });
181
+ }
98
182
  function printCapabilityList(records, json, query) {
99
183
  if (json) {
100
184
  console.log(JSON.stringify({
@@ -160,3 +244,33 @@ function runCapabilityShowCommand(options) {
160
244
  const records = loadRecords(options);
161
245
  printCapability(resolveCapability(records, options.id), options.json);
162
246
  }
247
+ function runCapabilityResolveCommand(options) {
248
+ const records = applyFilters(loadRecords(options), options);
249
+ const items = resolveCapabilities(records, options).map((record, rank) => ({
250
+ rank: rank + 1,
251
+ score: resolveScore(record, options),
252
+ stale: isStale(record),
253
+ item: record,
254
+ }));
255
+ if (options.json) {
256
+ console.log(JSON.stringify({
257
+ kind: "capability.resolve",
258
+ query: options.query,
259
+ requires: options.requires,
260
+ fresh_only: options.freshOnly === true,
261
+ count: items.length,
262
+ items,
263
+ }, null, 2));
264
+ return;
265
+ }
266
+ if (items.length === 0) {
267
+ console.log("no capabilities resolved");
268
+ return;
269
+ }
270
+ console.log(`resolved capabilities: ${items.length}`);
271
+ for (const result of items) {
272
+ const record = result.item;
273
+ const stale = result.stale ? " stale" : "";
274
+ console.log(`${result.rank}. ${record.qid} | score ${result.score}${stale} | ${record.kind} | ${record.title}`);
275
+ }
276
+ }