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.
@@ -21,7 +21,7 @@
21
21
  "output_dir": ".mdkg/bundles",
22
22
  "default_profile": "private"
23
23
  },
24
- "bundle_imports": {},
24
+ "subgraphs": {},
25
25
  "pack": {
26
26
  "default_depth": 2,
27
27
  "default_edges": [
@@ -69,9 +69,9 @@ Workspaces are registered in `.mdkg/config.json`.
69
69
 
70
70
  Qualified IDs may be used as input:
71
71
  - `<ws>:<id>` (example: `e2e:task-12`)
72
- - Imported bundle nodes use the same qualified form with the import alias:
73
- - `<import-alias>:<id>` (example: `agent_image:work.generate-image`)
74
- - imported nodes are read-only planning context and MUST NOT be selected by local mutation commands
72
+ - Subgraph nodes use the same qualified form with the subgraph alias:
73
+ - `<subgraph-alias>:<id>` (example: `agent_image:work.generate-image`)
74
+ - subgraph nodes are read-only planning context and MUST NOT be selected by local mutation commands
75
75
 
76
76
  If a user provides an unqualified ID and it is ambiguous globally:
77
77
  - mdkg MUST error and suggest qualified IDs.
@@ -145,7 +145,7 @@ If a user provides an unqualified ID and it is ambiguous globally:
145
145
  - rebuild global cache `.mdkg/index/global.json`
146
146
  - rebuild skills cache `.mdkg/index/skills.json` from `.mdkg/skills/<slug>/SKILL.md`
147
147
  - rebuild capability cache `.mdkg/index/capabilities.json` from skills, `SPEC.md`, `WORK.md`, core docs, and design docs
148
- - rebuild bundle import projection cache `.mdkg/index/imports.json` when bundle imports are configured
148
+ - rebuild subgraph projection cache `.mdkg/index/subgraphs.json` when subgraphs are configured
149
149
  - rebuild SQLite access cache `.mdkg/index/mdkg.sqlite` when `index.backend` is `sqlite`
150
150
  - tolerate `.mdkg/skills/<slug>/SKILLS.md` on read with warning
151
151
  - fail validation if both `SKILL.md` and `SKILLS.md` exist in one skill directory
@@ -183,10 +183,10 @@ Common flags:
183
183
  - `mdkg search "<query>" [--type <type>] [--status <status>] [--ws <alias>] [--tags <tag,tag,...>] [--tags-mode any|all] [--json|--xml|--toon|--md]`
184
184
  - search SHOULD match on IDs, titles, tags, path tokens, and searchable frontmatter lists (`links`, `artifacts`, `refs`, `aliases`)
185
185
  - `mdkg list [--type <type>] [--status <status>] [--ws <alias>] [--epic <id>] [--blocked] [--priority <n>] [--tags <tag,tag,...>] [--tags-mode any|all] [--json|--xml|--toon|--md]`
186
- - enabled bundle imports are included in `show`, `search`, `list`, `pack`, and `capability` reads by default:
187
- - imported nodes surface `source.imported: true` in JSON output
188
- - human output labels imported nodes as read-only and stale when applicable
189
- - stale imports warn during planning reads but remain usable
186
+ - enabled subgraphs are included in `show`, `search`, `list`, `pack`, and `capability` reads by default:
187
+ - subgraph nodes surface `source.imported: true` and `source.subgraph_alias` in JSON output
188
+ - human output labels subgraph nodes as read-only and stale when applicable
189
+ - stale subgraphs warn during planning reads but remain usable
190
190
  - skills are first-class under `mdkg skill ...` only:
191
191
  - `mdkg skill list [--tags <tag,tag,...>] [--tags-mode any|all] [--json|--xml|--toon|--md]`
192
192
  - `mdkg skill show <slug> [--meta] [--json|--xml|--toon|--md]`
@@ -197,7 +197,9 @@ Common flags:
197
197
  - `mdkg capability list [--kind <skill|spec|work|core|design>] [--visibility <private|internal|public>] [--json]`
198
198
  - `mdkg capability search "<query>" [--kind <kind>] [--visibility <level>] [--json]`
199
199
  - `mdkg capability show <id-or-qid-or-slug> [--json]`
200
+ - `mdkg capability resolve [query] [--requires <capability>] [--fresh-only] [--json]`
200
201
  - capability records are read-only derived cache entries, not source of truth
202
+ - `resolve` ranks local and subgraph capability candidates deterministically and degrades stale subgraphs unless `--fresh-only` is supplied
201
203
  - normal task, epic, feat, bug, test, and checkpoint nodes are not capability records
202
204
  - archives are first-class sidecar nodes under `mdkg archive ...`:
203
205
  - `mdkg archive add <file> [--id <archive.id>] [--kind source|artifact] [--visibility private|internal|public] [--title <title>] [--refs <...>] [--relates <...>] [--json]`
@@ -215,21 +217,26 @@ Common flags:
215
217
  - `mdkg bundle verify [bundle-path] [--json]`
216
218
  - `mdkg bundle show <bundle-path> [--json]`
217
219
  - `mdkg bundle list [--json]`
218
- - `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]`
219
- - `mdkg bundle import list [--json]`
220
- - `mdkg bundle import rm <alias> [--json]`
221
- - `mdkg bundle import enable <alias> [--json]`
222
- - `mdkg bundle import disable <alias> [--json]`
223
- - `mdkg bundle import verify [alias|--all] [--json]`
224
220
  - bundles are explicit transport artifacts and are not rewritten by `mdkg index`
225
221
  - default output is `.mdkg/bundles/<profile>/<workspace-or-all>.mdkg.zip`
226
222
  - public bundles must fail closed when public records reference private graph or archive records
227
- - public bundles must fail closed when public records reference private/internal imported graph records
228
- - bundle imports are read-only projected graph views; child repos remain owners of real mutations and commits
229
- - `bundle import verify` exits nonzero for stale, missing, corrupt, profile-mismatched, or duplicate-id imports
230
- - public bundle creation must not re-export imported child graph content and must fail if public local nodes reference private/internal imports
231
- - public/internal imports require `expected_profile: public`; private bundle profiles cannot be promoted through import visibility
223
+ - public bundles must fail closed when public records reference private/internal subgraph records
224
+ - public bundle creation must not re-export subgraph content and must fail if public local nodes reference private/internal subgraphs
232
225
  - `mdkg pack --visibility public|internal|private` records explicit pack visibility and filters public/internal packs through the same fail-closed policy
226
+ - subgraph orchestration lives under `mdkg subgraph ...`:
227
+ - `mdkg subgraph add <alias> <bundle-path> [--visibility private|internal|public] [--profile private|public] [--source-path <path>] [--source-repo <ref>] [--max-stale-seconds <seconds>] [--json]`
228
+ - `mdkg subgraph list [--json]`
229
+ - `mdkg subgraph show <alias> [--json]`
230
+ - `mdkg subgraph rm <alias> [--json]`
231
+ - `mdkg subgraph enable <alias> [--json]`
232
+ - `mdkg subgraph disable <alias> [--json]`
233
+ - `mdkg subgraph verify [alias|--all] [--json]`
234
+ - `mdkg subgraph refresh [alias|--all] [--json]`
235
+ - subgraphs are read-only projected graph views; child repos remain owners of real mutations and commits
236
+ - `subgraph refresh` reloads configured bundle sources only and never builds or mutates child repos
237
+ - `subgraph verify` exits nonzero for stale, missing, corrupt, profile-mismatched, or duplicate-id subgraphs
238
+ - public/internal subgraphs require `expected_profile: public`; private bundle profiles cannot be promoted through subgraph visibility
239
+ - legacy `mdkg bundle import ...` exits with guidance to run `mdkg upgrade --apply` and use `mdkg subgraph ...`
233
240
  - work lifecycle helpers live under `mdkg work ...`:
234
241
  - `mdkg work contract new "<title>" --id <work.id> --agent-id <agent.id> --kind <kind> --inputs <...> --outputs <...> [--required-capabilities <...>] [--pricing-model <...>] [--json]`
235
242
  - `mdkg work order new "<title>" --id <order.id> --work-id <work.id> --requester <ref> [--request-ref <ref>] [--input-refs <...>] [--requested-outputs <...>] [--json]`
@@ -240,7 +247,7 @@ Common flags:
240
247
  - these commands mutate mdkg semantic mirror files only; production order, receipt, feedback, dispute, payment, ledger, marketplace inventory, fulfillment, and execution state remains canonical outside mdkg
241
248
  - work mirrors must not store raw secrets, credentials, live payment state, ledger mutations, or canonical marketplace state
242
249
  - `artifact://...` refs identify external/runtime-managed artifacts; `archive://...` refs identify committed mdkg archive sidecars
243
- - update and artifact commands accept local ids or local qids; imported bundle qids are read-only and must be changed in their source workspace
250
+ - update and artifact commands accept local ids or local qids; subgraph qids are read-only and must be changed in their source workspace
244
251
  - discovery/show output flags are mutually exclusive; text mode remains the default when none are supplied
245
252
 
246
253
  ### Task lifecycle mutation
@@ -251,7 +258,7 @@ Common flags:
251
258
  - supports additive list mutation for `artifacts`, `links`, `refs`, `skills`, `tags`, and `blocked_by`
252
259
  - supports scalar replacement for `status` and `priority`
253
260
  - `--clear-blocked-by` resets blockers before optional re-add
254
- - imported bundle qids fail with an explicit read-only import error
261
+ - subgraph qids fail with an explicit read-only subgraph error
255
262
  - `mdkg task done <id-or-qid> [--checkpoint "<title>"] [...]`
256
263
  - supports `task`, `bug`, and `test` nodes only
257
264
  - sets `status: done`
@@ -308,8 +315,8 @@ Common flags:
308
315
  - `mdkg validate`
309
316
  - strict frontmatter + graph integrity checks (exit code 2 on failure)
310
317
  - validates optional node->skill references
311
- - validates configured bundle imports and fails on missing/corrupt enabled bundles, malformed import config, duplicate projected ids, and invalid import refs
312
- - warns, but does not fail, on stale imports
318
+ - validates configured subgraphs and fails on missing/corrupt enabled bundles, malformed subgraph config, duplicate projected ids, and invalid subgraph refs
319
+ - warns, but does not fail, on stale subgraphs
313
320
  - validates optional `.mdkg/work/events/events.jsonl` record shape when file exists
314
321
  - warns when `.agents/skills/` or `.claude/skills/` drift from canonical `.mdkg/skills/`
315
322
  - `mdkg format`
@@ -108,7 +108,7 @@ Explicit flags remain available and take precedence:
108
108
 
109
109
  - `.mdkg/bundles/` stores explicit snapshot artifacts and is not ignored by default.
110
110
  - Private bundles may include sensitive authored mdkg content and should stay in private repos.
111
- - Public bundles must be created with `mdkg bundle create --profile public` so private graph, archive, and imported bundle refs fail closed.
111
+ - Public bundles must be created with `mdkg bundle create --profile public` so private graph, archive, and subgraph refs fail closed.
112
112
  - Public-safe packs must be created with `mdkg pack <id> --visibility public`; internal-safe packs use `--visibility internal`. These filters do not redact Markdown body text.
113
113
  - Bundle ZIPs must exclude `.mdkg/pack/`, existing `.mdkg/index/`, nested `.mdkg/bundles/`, and raw `.mdkg/archive/**/source/` files.
114
114
  - Repos that track archive caches or bundles should refresh in this order before commit: `mdkg archive compress --all`, `mdkg archive verify --json`, `mdkg bundle create --profile private`, then bundle verify.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "tool": "mdkg",
4
- "mdkg_version": "0.1.3",
4
+ "mdkg_version": "0.1.4",
5
5
  "files": [
6
6
  {
7
7
  "path": ".mdkg/config.json",
8
8
  "category": "config",
9
- "sha256": "0319f2e92ae0030d67816c025802be50ec5eecc3274e75a4e0ded3e753d945b3"
9
+ "sha256": "5d2cf5e773353a59178fe86f8528413a00a73e2879fb1e020d01b49c0715a9ce"
10
10
  },
11
11
  {
12
12
  "path": ".mdkg/core/core.md",
@@ -36,12 +36,12 @@
36
36
  {
37
37
  "path": ".mdkg/core/rule-3-cli-contract.md",
38
38
  "category": "core",
39
- "sha256": "4ff22c60e3b4093492f00c8ddd738f7241192c7b1edbfdcbdc4ed09d95192aa6"
39
+ "sha256": "4ec25c6936eefe0857a2946b47f0b65c9481bd65f1159fd5456bec6cce9de7ea"
40
40
  },
41
41
  {
42
42
  "path": ".mdkg/core/rule-4-repo-safety-and-ignores.md",
43
43
  "category": "core",
44
- "sha256": "4edf0efa29ec470e96cad3e0abc7b8f44e2e718d7a9959f9cefc5e1db7e02b6c"
44
+ "sha256": "2374e8684dfb32d24d560c83c99c33ff655cfdfe6811372dc3959ec93318a6db"
45
45
  },
46
46
  {
47
47
  "path": ".mdkg/core/rule-5-release-and-versioning.md",
@@ -61,7 +61,7 @@
61
61
  {
62
62
  "path": ".mdkg/README.md",
63
63
  "category": "mdkg_doc",
64
- "sha256": "6ff69157098b5cc780f063ffadabff36b70e8567cec7fc406d7550385c467d0e"
64
+ "sha256": "5ee6c141c0052e78671762e69c111f7f0a5d7f79b35c8f78c5b3b41901bd6959"
65
65
  },
66
66
  {
67
67
  "path": ".mdkg/skills/build-pack-and-execute-task/SKILL.md",
@@ -76,7 +76,7 @@
76
76
  {
77
77
  "path": ".mdkg/skills/verify-close-and-checkpoint/SKILL.md",
78
78
  "category": "default_skill",
79
- "sha256": "cf9d7f01eb78a3cf669b6303c2f22c7c08529fe0181e008a7b45c5e5b70ceb49"
79
+ "sha256": "67fc3d7e3c59b53add62306624391c1a416d0c66077729d79e1be0a303538f42"
80
80
  },
81
81
  {
82
82
  "path": ".mdkg/templates/default/archive.md",
@@ -176,7 +176,7 @@
176
176
  {
177
177
  "path": "AGENT_START.md",
178
178
  "category": "startup_doc",
179
- "sha256": "e5bd54a4321443216645b11fd5f27aff2e8f6c760f21dad12b6d0dbe634b0986"
179
+ "sha256": "abde8671d34fcc7abd8570cf8c10b940cbe8f339edb369bd046045aa5626c0fc"
180
180
  },
181
181
  {
182
182
  "path": "AGENTS.md",
@@ -191,7 +191,7 @@
191
191
  {
192
192
  "path": "CLI_COMMAND_MATRIX.md",
193
193
  "category": "startup_doc",
194
- "sha256": "6e347620120c93eb9f330d137087f9e6b3279c629fcc24b65af561bd3cf385a0"
194
+ "sha256": "14deecded057a99284d2dc147855d12dedffec049da51fed3b1ebdae3105d537"
195
195
  },
196
196
  {
197
197
  "path": "llms.txt",
@@ -46,7 +46,7 @@ Use this local repo-only checklist before publishing mdkg:
46
46
 
47
47
  1. Confirm package intent and version in `package.json`, `package-lock.json`, `README.md`, `CLI_COMMAND_MATRIX.md`, and `CHANGELOG.md`.
48
48
  2. Use a clean npm cache: `export NPM_CONFIG_CACHE=/private/tmp/mdkg-npm-cache`.
49
- 3. Run `npm ci`, `npm run build`, `node scripts/assert-publish-ready.js`, `npm run test`, `npm run cli:check`, `node dist/cli.js validate`, `npm run smoke:consumer`, `npm run smoke:matrix`, `npm run smoke:upgrade`, `npm run smoke:init`, `npm run smoke:capabilities`, `npm run smoke:archive-work`, `npm run smoke:bundle`, `npm run smoke:bundle-import`, and `npm run smoke:visibility`.
49
+ 3. Run `npm ci`, `npm run build`, `node scripts/assert-publish-ready.js`, `npm run test`, `npm run cli:check`, `node dist/cli.js validate`, `npm run smoke:consumer`, `npm run smoke:matrix`, `npm run smoke:upgrade`, `npm run smoke:init`, `npm run smoke:capabilities`, `npm run smoke:archive-work`, `npm run smoke:bundle`, `npm run smoke:subgraph`, and `npm run smoke:visibility`.
50
50
  4. Run `npm pack --dry-run --json` and confirm the tarball includes `dist/cli.js`, compiled folders, `dist/init/`, release docs, and `scripts/postinstall.js`.
51
51
  5. Confirm registry state with `npm view mdkg version --registry=https://registry.npmjs.org/`.
52
52
  6. Publish only after the registry still shows the previous version and npm auth is known to have write access.
@@ -83,6 +83,7 @@ const VALUE_FLAGS = new Set([
83
83
  "--source-path",
84
84
  "--source-repo",
85
85
  "--max-stale-seconds",
86
+ "--requires",
86
87
  ]);
87
88
  const BOOLEAN_FLAGS = new Set([
88
89
  "--tolerant",
@@ -116,6 +117,7 @@ const BOOLEAN_FLAGS = new Set([
116
117
  "--with-scripts",
117
118
  "--clear-blocked-by",
118
119
  "--all",
120
+ "--fresh-only",
119
121
  ]);
120
122
  const FLAG_ALIASES = {
121
123
  "--o": "--out",
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "mdkg",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Markdown Knowledge Graph",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "mdkg": "dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsc -p tsconfig.build.json && node scripts/add-shebang.js && node scripts/copy-init-assets.js",
11
- "build:test": "tsc -p tsconfig.test.json",
10
+ "build": "node scripts/clean-build-output.js && tsc -p tsconfig.build.json && node scripts/add-shebang.js && node scripts/copy-init-assets.js",
11
+ "build:test": "node scripts/clean-build-output.js tests && tsc -p tsconfig.test.json",
12
12
  "test": "npm run build && npm run build:test && node --test dist/tests/**/*.test.js",
13
13
  "test:coverage": "npm run build && npm run build:test && node --test --experimental-test-coverage dist/tests/**/*.test.js",
14
14
  "smoke:consumer": "npm run build && node scripts/smoke-consumer.js",
@@ -18,15 +18,16 @@
18
18
  "smoke:capabilities": "npm run build && node scripts/smoke-capabilities.js",
19
19
  "smoke:archive-work": "npm run build && node scripts/smoke-archive-work.js",
20
20
  "smoke:bundle": "npm run build && node scripts/smoke-bundle.js",
21
- "smoke:bundle-import": "npm run build && node scripts/smoke-bundle-import.js",
21
+ "smoke:bundle-import": "npm run smoke:subgraph",
22
22
  "smoke:visibility": "npm run build && node scripts/smoke-visibility.js",
23
23
  "smoke:sqlite": "npm run build && node scripts/smoke-sqlite.js",
24
24
  "smoke:parallel": "npm run build && node scripts/smoke-parallel.js",
25
25
  "cli:snapshot": "npm run build && node scripts/cli_help_snapshot.js",
26
26
  "cli:check": "npm run build && node scripts/cli_help_snapshot.js --check",
27
27
  "prepack": "npm run build && node scripts/assert-publish-ready.js",
28
- "prepublishOnly": "npm run test && npm run cli:check && node dist/cli.js validate && npm run smoke:consumer && npm run smoke:matrix && npm run smoke:upgrade && npm run smoke:init && npm run smoke:capabilities && npm run smoke:archive-work && npm run smoke:bundle && npm run smoke:bundle-import && npm run smoke:visibility && npm run smoke:sqlite && npm run smoke:parallel && node scripts/assert-publish-ready.js",
29
- "postinstall": "node scripts/postinstall.js"
28
+ "prepublishOnly": "npm run test && npm run cli:check && node dist/cli.js validate && npm run smoke:consumer && npm run smoke:matrix && npm run smoke:upgrade && npm run smoke:init && npm run smoke:capabilities && npm run smoke:archive-work && npm run smoke:bundle && npm run smoke:subgraph && npm run smoke:visibility && npm run smoke:sqlite && npm run smoke:parallel && node scripts/assert-publish-ready.js",
29
+ "postinstall": "node scripts/postinstall.js",
30
+ "smoke:subgraph": "npm run build && node scripts/smoke-subgraph.js"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^24.0.0",
@@ -1,255 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.runBundleImportAddCommand = runBundleImportAddCommand;
7
- exports.runBundleImportListCommand = runBundleImportListCommand;
8
- exports.runBundleImportRemoveCommand = runBundleImportRemoveCommand;
9
- exports.runBundleImportEnableCommand = runBundleImportEnableCommand;
10
- exports.runBundleImportDisableCommand = runBundleImportDisableCommand;
11
- exports.runBundleImportVerifyCommand = runBundleImportVerifyCommand;
12
- const fs_1 = __importDefault(require("fs"));
13
- const path_1 = __importDefault(require("path"));
14
- const config_1 = require("../core/config");
15
- const migrate_1 = require("../core/migrate");
16
- const workspace_path_1 = require("../core/workspace_path");
17
- const bundle_imports_1 = require("../graph/bundle_imports");
18
- const errors_1 = require("../util/errors");
19
- const atomic_1 = require("../util/atomic");
20
- const lock_1 = require("../util/lock");
21
- const ALIAS_RE = /^[a-z][a-z0-9_]*$/;
22
- function writeJson(value) {
23
- console.log(JSON.stringify(value, null, 2));
24
- }
25
- function normalizeAlias(alias) {
26
- if (alias === "all") {
27
- throw new errors_1.UsageError("bundle import alias cannot be 'all'");
28
- }
29
- if (alias !== alias.toLowerCase() || !ALIAS_RE.test(alias)) {
30
- throw new errors_1.UsageError("bundle import alias must be lowercase and use [a-z0-9_]");
31
- }
32
- return alias;
33
- }
34
- function normalizeVisibility(value) {
35
- const normalized = (value ?? "private").toLowerCase();
36
- if (normalized === "private" || normalized === "internal" || normalized === "public") {
37
- return normalized;
38
- }
39
- throw new errors_1.UsageError("--visibility must be private, internal, or public");
40
- }
41
- function normalizeProfile(value) {
42
- const normalized = (value ?? "private").toLowerCase();
43
- if (normalized === "private" || normalized === "public") {
44
- return normalized;
45
- }
46
- throw new errors_1.UsageError("--profile must be private or public");
47
- }
48
- function normalizeContained(value, label) {
49
- try {
50
- return (0, workspace_path_1.normalizeContainedWorkspacePath)(value, label);
51
- }
52
- catch (err) {
53
- throw new errors_1.UsageError(err instanceof Error ? err.message : String(err));
54
- }
55
- }
56
- function readRawConfig(root) {
57
- const configPath = path_1.default.join(root, ".mdkg", "config.json");
58
- if (!fs_1.default.existsSync(configPath)) {
59
- throw new errors_1.NotFoundError(`config not found at ${configPath}`);
60
- }
61
- let parsed;
62
- try {
63
- parsed = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
64
- }
65
- catch (err) {
66
- throw new errors_1.UsageError(`failed to read config: ${err instanceof Error ? err.message : String(err)}`);
67
- }
68
- const migrated = (0, migrate_1.migrateConfig)(parsed).config;
69
- (0, config_1.validateConfigSchema)(migrated);
70
- if (typeof migrated !== "object" || migrated === null || Array.isArray(migrated)) {
71
- throw new errors_1.UsageError("config must be a JSON object");
72
- }
73
- return { configPath, raw: migrated };
74
- }
75
- function writeRawConfig(configPath, raw) {
76
- (0, atomic_1.atomicWriteFile)(configPath, `${JSON.stringify(raw, null, 2)}\n`);
77
- }
78
- function getImports(raw) {
79
- const imports = raw.bundle_imports;
80
- if (imports === undefined) {
81
- raw.bundle_imports = {};
82
- return raw.bundle_imports;
83
- }
84
- if (typeof imports !== "object" || imports === null || Array.isArray(imports)) {
85
- throw new errors_1.UsageError("config.bundle_imports must be an object");
86
- }
87
- return imports;
88
- }
89
- function receiptForHealth(action, health) {
90
- return {
91
- action,
92
- import: health,
93
- };
94
- }
95
- function healthByAlias(root, alias) {
96
- const config = (0, config_1.loadConfig)(root);
97
- const health = (0, bundle_imports_1.buildBundleImportsIndex)(root, config).index.imports.find((item) => item.alias === alias);
98
- if (!health) {
99
- throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
100
- }
101
- return health;
102
- }
103
- function withBundleImportLock(root, fn) {
104
- const config = (0, config_1.loadConfig)(root);
105
- return (0, lock_1.withMutationLock)(root, config.index.lock_timeout_ms, fn);
106
- }
107
- function runBundleImportAddCommandLocked(options) {
108
- const alias = normalizeAlias(options.alias);
109
- const bundlePath = normalizeContained(options.bundlePath, "bundle import path");
110
- const visibility = normalizeVisibility(options.visibility);
111
- const expected_profile = normalizeProfile(options.profile);
112
- if (visibility !== "private" && expected_profile !== "public") {
113
- throw new errors_1.UsageError("--profile public is required when --visibility is public or internal");
114
- }
115
- const source_path = options.sourcePath
116
- ? normalizeContained(options.sourcePath, "bundle import source path")
117
- : undefined;
118
- if (options.maxStaleSeconds !== undefined && (!Number.isInteger(options.maxStaleSeconds) || options.maxStaleSeconds <= 0)) {
119
- throw new errors_1.UsageError("--max-stale-seconds must be a positive integer");
120
- }
121
- const { configPath, raw } = readRawConfig(options.root);
122
- const imports = getImports(raw);
123
- if (imports[alias]) {
124
- throw new errors_1.UsageError(`bundle import already exists: ${alias}`);
125
- }
126
- const workspaces = raw.workspaces;
127
- if (workspaces && workspaces[alias]) {
128
- throw new errors_1.UsageError(`bundle import alias collides with workspace: ${alias}`);
129
- }
130
- imports[alias] = {
131
- path: bundlePath,
132
- enabled: true,
133
- visibility,
134
- expected_profile,
135
- ...(source_path ? { source_path } : {}),
136
- ...(options.sourceRepo ? { source_repo: options.sourceRepo } : {}),
137
- ...(options.maxStaleSeconds !== undefined ? { max_stale_seconds: options.maxStaleSeconds } : {}),
138
- };
139
- raw.bundle_imports = imports;
140
- const validated = (0, config_1.validateConfigSchema)(raw);
141
- const health = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, validated).index.imports.find((item) => item.alias === alias);
142
- if (!health) {
143
- throw new errors_1.NotFoundError(`bundle import not found after validation: ${alias}`);
144
- }
145
- if (health.error_count > 0) {
146
- throw new errors_1.ValidationError(`bundle import ${alias} is invalid:\n${health.errors.join("\n")}`);
147
- }
148
- writeRawConfig(configPath, raw);
149
- const receipt = receiptForHealth("added", health);
150
- if (options.json) {
151
- writeJson(receipt);
152
- return;
153
- }
154
- console.log(`bundle import added: ${alias} (${bundlePath})`);
155
- if (health.warning_count > 0) {
156
- console.log(`warnings: ${health.warning_count}`);
157
- }
158
- }
159
- function runBundleImportAddCommand(options) {
160
- return withBundleImportLock(options.root, () => runBundleImportAddCommandLocked(options));
161
- }
162
- function runBundleImportListCommand(options) {
163
- const config = (0, config_1.loadConfig)(options.root);
164
- const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports;
165
- if (options.json) {
166
- writeJson({ action: "list", count: imports.length, imports });
167
- return;
168
- }
169
- if (imports.length === 0) {
170
- console.log("no bundle imports configured");
171
- return;
172
- }
173
- for (const item of imports) {
174
- const status = item.enabled ? item.error_count > 0 ? "invalid" : item.stale ? "stale" : "ok" : "disabled";
175
- console.log(`${item.alias} | ${status} | ${item.visibility} | ${item.path}`);
176
- }
177
- }
178
- function runBundleImportRemoveCommandLocked(options) {
179
- const alias = normalizeAlias(options.alias);
180
- const { configPath, raw } = readRawConfig(options.root);
181
- const imports = getImports(raw);
182
- const existing = imports[alias];
183
- if (!existing) {
184
- throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
185
- }
186
- delete imports[alias];
187
- raw.bundle_imports = imports;
188
- (0, config_1.validateConfigSchema)(raw);
189
- writeRawConfig(configPath, raw);
190
- const receipt = { action: "removed", import: { alias } };
191
- if (options.json) {
192
- writeJson(receipt);
193
- return;
194
- }
195
- console.log(`bundle import removed: ${alias}`);
196
- }
197
- function runBundleImportRemoveCommand(options) {
198
- return withBundleImportLock(options.root, () => runBundleImportRemoveCommandLocked(options));
199
- }
200
- function setBundleImportEnabledLocked(options, enabled) {
201
- const alias = normalizeAlias(options.alias);
202
- const { configPath, raw } = readRawConfig(options.root);
203
- const imports = getImports(raw);
204
- const existing = imports[alias];
205
- if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
206
- throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
207
- }
208
- imports[alias] = { ...existing, enabled };
209
- raw.bundle_imports = imports;
210
- (0, config_1.validateConfigSchema)(raw);
211
- writeRawConfig(configPath, raw);
212
- const health = healthByAlias(options.root, alias);
213
- const receipt = receiptForHealth(enabled ? "enabled" : "disabled", health);
214
- if (options.json) {
215
- writeJson(receipt);
216
- return;
217
- }
218
- console.log(`bundle import ${enabled ? "enabled" : "disabled"}: ${alias}`);
219
- }
220
- function runBundleImportEnableCommand(options) {
221
- withBundleImportLock(options.root, () => setBundleImportEnabledLocked(options, true));
222
- }
223
- function runBundleImportDisableCommand(options) {
224
- withBundleImportLock(options.root, () => setBundleImportEnabledLocked(options, false));
225
- }
226
- function runBundleImportVerifyCommand(options) {
227
- const config = (0, config_1.loadConfig)(options.root);
228
- const all = options.all || !options.alias;
229
- const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports.filter((item) => all ? true : item.alias === options.alias);
230
- if (!all && imports.length === 0) {
231
- throw new errors_1.NotFoundError(`bundle import not found: ${options.alias}`);
232
- }
233
- const ok = imports.every((item) => item.error_count === 0 && !item.stale);
234
- const receipt = { action: "verified", ok, count: imports.length, imports };
235
- if (options.json) {
236
- writeJson(receipt);
237
- }
238
- else if (ok) {
239
- console.log(`bundle imports verified: ${imports.length}`);
240
- }
241
- else {
242
- console.log(`bundle import verify failed: ${imports.length}`);
243
- for (const item of imports) {
244
- for (const warning of item.warnings) {
245
- console.log(`warning: ${item.alias}: ${warning}`);
246
- }
247
- for (const error of item.errors) {
248
- console.log(`error: ${item.alias}: ${error}`);
249
- }
250
- }
251
- }
252
- if (!ok) {
253
- throw new errors_1.ValidationError("bundle import verify failed");
254
- }
255
- }