mdkg 0.5.0 → 0.5.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 CHANGED
@@ -8,6 +8,23 @@ mdkg is pre-v1 public alpha software. Command, graph, cache, bundle, and DAL con
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.5.1 - 2026-07-14
12
+
13
+ ### Changed
14
+
15
+ - Archive compression now derives writable ownership from enabled local
16
+ workspaces. `archive compress --all --ws <local-alias>` limits bulk selection,
17
+ and exact workspace-qualified archive qids are supported for direct targets.
18
+ - Archive compression JSON receipts preserve existing fields and add selected
19
+ workspace and read-only imported-projection exclusion evidence.
20
+
21
+ ### Fixed
22
+
23
+ - Fixed `archive compress --all` traversing imported subgraph ZIP-fragment paths
24
+ and potentially mutating earlier local archives before a later invalid target
25
+ failed. Imported archives remain discoverable but fail closed for direct
26
+ compression, and the complete local selection is preflighted before writes.
27
+
11
28
  ## 0.5.0 - 2026-07-11
12
29
 
13
30
  ### Added
@@ -1,7 +1,7 @@
1
1
  # CLI Command Matrix
2
2
 
3
- as_of: 2026-07-11
4
- package_version_in_source: 0.5.0
3
+ as_of: 2026-07-14
4
+ package_version_in_source: 0.5.1
5
5
  source: live help from `src/cli.ts`, runtime command handlers, and `dec-15`..`dec-18`
6
6
  status: canonical single-source command and flag reference for mdkg
7
7
 
@@ -657,9 +657,9 @@ Usage:
657
657
  - `mdkg archive show <id-or-archive-uri> [--ws <alias>] [--json]`
658
658
  - `mdkg archive verify [id-or-archive-uri] [--json]`
659
659
  - `mdkg archive verify [id-or-archive-uri] [--ws <alias>] [--json]`
660
- - `mdkg archive compress <id-or-archive-uri|--all> [--json]`
661
- - `mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]`
662
- - `mdkg archive compress --all [--json]`
660
+ - `mdkg archive compress <id-or-archive-uri-or-qid|--all> [--ws <local-alias>] [--json]`
661
+ - `mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]`
662
+ - `mdkg archive compress --all [--ws <local-alias>] [--json]`
663
663
 
664
664
  Fields:
665
665
  - archive sidecars use `type: archive`
@@ -673,6 +673,18 @@ Notes:
673
673
  - archive visibility defaults to `private`
674
674
  - `archive://<archive.id>` refs are validated against local archive sidecars
675
675
  - `archive verify` and `validate` both check the ZIP hash, ZIP readability, payload SHA-256, and payload byte size
676
+ - archive compression is owned exclusively by enabled local workspaces; imported
677
+ archive projections remain visible to list/show/search but are read-only
678
+ - `archive compress --all` selects every locally writable archive, while
679
+ `--all --ws <local-alias>` limits selection to one enabled local workspace
680
+ - direct workspace-qualified qids such as `root:archive.example` are supported;
681
+ imported qids and `--ws <imported-alias>` fail before filesystem path
682
+ construction with source-workspace and bundle-refresh guidance
683
+ - compression preflights ownership, identity, containment, symlinks, raw input,
684
+ and destinations for the complete selected set before the first write
685
+ - JSON keeps `action`, `count`, and `archives`, and adds
686
+ `selection: { requested_workspace, selected_workspaces,
687
+ excluded_read_only: [{ workspace, qid, reason }] }`
676
688
  - `archive verify` passes when the raw local source file is missing but the committed sidecar and ZIP cache are valid
677
689
  - generated raw source copies live under `.mdkg/archive/**/source/` and are ignored by default
678
690
  - `doctor` warns when archive ZIP caches exceed `archive.large_cache_warning_bytes` (default `26214400`; `0` disables)
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.5.0`
17
+ Current package version in source: `0.5.1`
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
 
@@ -653,6 +653,16 @@ When the source passed to `mdkg archive add` is inside the repo, `source_path` i
653
653
 
654
654
  Archive sidecar visibility defaults to `private`. Use `mdkg archive add --visibility public` only when the sidecar metadata and ZIP cache are safe for public packs or public bundles.
655
655
 
656
+ Archive compression follows workspace ownership. `mdkg archive compress --all`
657
+ compresses archives from enabled local workspaces and reports read-only imported
658
+ archive projections as exclusions; it never treats bundle ZIP fragments as
659
+ filesystem paths. Use `--all --ws <local-alias>` to limit bulk compression or a
660
+ workspace-qualified qid such as `root:archive.example` for an exact local
661
+ target. Direct imported qids and imported `--ws` aliases fail before writes with
662
+ guidance to compress in the source workspace and refresh its subgraph bundle.
663
+ The JSON receipt preserves `action`, `count`, and `archives` and adds the
664
+ requested workspace, selected local workspaces, and excluded read-only qids.
665
+
656
666
  By default, init/upgrade ignore generated raw archive source copies with `.mdkg/archive/**/source/`; sidecar `.md` files and compressed `.zip` caches remain commit-eligible. `mdkg doctor` warns when a committed archive ZIP cache exceeds `archive.large_cache_warning_bytes` in `.mdkg/config.json` (default `26214400`; set `0` to disable). Large-cache warnings do not block archive add or validation.
657
667
 
658
668
  ## Current direction
package/dist/cli.js CHANGED
@@ -583,8 +583,13 @@ function printArchiveHelp(log, subcommand) {
583
583
  break;
584
584
  case "compress":
585
585
  log("Usage:");
586
- log(" mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]");
587
- log(" mdkg archive compress --all [--json]");
586
+ log(" mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]");
587
+ log(" mdkg archive compress --all [--ws <local-alias>] [--json]");
588
+ log("\nNotes:");
589
+ log(" - compression mutates archives owned by enabled local workspaces only");
590
+ log(" - --all excludes read-only imported archive projections and reports them in the receipt");
591
+ log(" - direct imported qids and --ws <imported-alias> fail with source-workspace guidance");
592
+ log(" - all selected archives pass ownership and path preflight before the first write");
588
593
  break;
589
594
  default:
590
595
  log("Usage:");
@@ -592,7 +597,7 @@ function printArchiveHelp(log, subcommand) {
592
597
  log(" mdkg archive list [--kind source|artifact] [--visibility private|internal|public] [--json]");
593
598
  log(" mdkg archive show <id-or-archive-uri> [--json]");
594
599
  log(" mdkg archive verify [id-or-archive-uri] [--json]");
595
- log(" mdkg archive compress <id-or-archive-uri|--all> [--json]");
600
+ log(" mdkg archive compress <id-or-archive-uri-or-qid|--all> [--ws <local-alias>] [--json]");
596
601
  log("\nNotes:");
597
602
  log(" - archive add copies the source, writes a sidecar, and writes a deterministic zip cache");
598
603
  log(" - archive visibility defaults to private");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "tool": "mdkg",
4
- "package_version": "0.5.0",
4
+ "package_version": "0.5.1",
5
5
  "source": {
6
6
  "help_targets": "scripts/cli_help_targets.js",
7
7
  "command_matrix": "CLI_COMMAND_MATRIX.md"
@@ -25,7 +25,7 @@
25
25
  " mdkg archive list [--kind source|artifact] [--visibility private|internal|public] [--json]",
26
26
  " mdkg archive show <id-or-archive-uri> [--json]",
27
27
  " mdkg archive verify [id-or-archive-uri] [--json]",
28
- " mdkg archive compress <id-or-archive-uri|--all> [--json]"
28
+ " mdkg archive compress <id-or-archive-uri-or-qid|--all> [--ws <local-alias>] [--json]"
29
29
  ],
30
30
  "args": [
31
31
  {
@@ -39,9 +39,9 @@
39
39
  "source": "<id-or-archive-uri>"
40
40
  },
41
41
  {
42
- "name": "id-or-archive-uri|--all",
42
+ "name": "id-or-archive-uri-or-qid|--all",
43
43
  "required": true,
44
- "source": "<id-or-archive-uri|--all>"
44
+ "source": "<id-or-archive-uri-or-qid|--all>"
45
45
  }
46
46
  ],
47
47
  "flags": [
@@ -203,22 +203,22 @@
203
203
  "visibility": "public",
204
204
  "summary": "mdkg archive compress command",
205
205
  "usage": [
206
- " mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]",
207
- " mdkg archive compress --all [--json]"
206
+ " mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]",
207
+ " mdkg archive compress --all [--ws <local-alias>] [--json]"
208
208
  ],
209
209
  "args": [
210
210
  {
211
- "name": "id-or-archive-uri",
211
+ "name": "id-or-archive-uri-or-qid",
212
212
  "required": true,
213
- "source": "<id-or-archive-uri>"
213
+ "source": "<id-or-archive-uri-or-qid>"
214
214
  }
215
215
  ],
216
216
  "flags": [
217
217
  {
218
218
  "name": "--all",
219
- "value": "[--json]",
219
+ "value": "[--ws <local-alias>]",
220
220
  "required": false,
221
- "description": "mdkg archive compress --all [--json]"
221
+ "description": "mdkg archive compress --all [--ws <local-alias>] [--json]"
222
222
  },
223
223
  {
224
224
  "name": "--help",
@@ -237,6 +237,12 @@
237
237
  "value": null,
238
238
  "required": false,
239
239
  "description": "--version, -V Show version"
240
+ },
241
+ {
242
+ "name": "--ws",
243
+ "value": "<imported-alias>",
244
+ "required": false,
245
+ "description": "- direct imported qids and --ws <imported-alias> fail with source-workspace guidance"
240
246
  }
241
247
  ],
242
248
  "output_formats": [
@@ -245,7 +251,7 @@
245
251
  ],
246
252
  "json_schema_ref": "mdkg.command_output.v1",
247
253
  "side_effects": [
248
- "refresh-archive-zip-cache"
254
+ "refresh-local-workspace-archive-zip-caches"
249
255
  ],
250
256
  "read_paths": [
251
257
  ".mdkg/**"
@@ -258,9 +264,11 @@
258
264
  "supported": false
259
265
  },
260
266
  "lock_policy": "mutation-lock-required",
261
- "atomic_write_policy": "zip-temp-rename",
267
+ "atomic_write_policy": "full-selection-preflight-then-per-file-atomic-replacement",
262
268
  "receipts": [
263
- "archive-compress-receipt"
269
+ "archive-compress-receipt",
270
+ "archive-workspace-selection-receipt",
271
+ "read-only-exclusion-receipt"
264
272
  ],
265
273
  "danger_level": "moderate",
266
274
  "aliases": [],
@@ -275,8 +283,8 @@
275
283
  }
276
284
  ],
277
285
  "examples": [
278
- " mdkg archive compress --all [--json]",
279
- " mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]"
286
+ " mdkg archive compress --all [--ws <local-alias>] [--json]",
287
+ " mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]"
280
288
  ],
281
289
  "docs": {
282
290
  "help_target": "mdkg help archive compress",
@@ -10548,5 +10556,5 @@
10548
10556
  }
10549
10557
  }
10550
10558
  ],
10551
- "contract_hash": "e9bd2d82d340887bc58d518c7e67541996f4b7744b2d1981763060575265aa29"
10559
+ "contract_hash": "1d137b3c750bc1d8b9bcb863123719d070291266582c8aec20f8e21529434d54"
10552
10560
  }
@@ -83,6 +83,22 @@ function normalizeArchiveId(raw) {
83
83
  }
84
84
  return normalized;
85
85
  }
86
+ function archiveTargetFromInput(value, ws) {
87
+ const uriId = (0, refs_1.archiveIdFromUri)(value);
88
+ if (uriId) {
89
+ return { id: normalizeArchiveId(uriId), workspace: normalizeWorkspace(ws) };
90
+ }
91
+ const separator = value.indexOf(":");
92
+ if (separator > 0) {
93
+ const workspace = normalizeWorkspace(value.slice(0, separator));
94
+ const id = normalizeArchiveId(value.slice(separator + 1));
95
+ if (ws && normalizeWorkspace(ws) !== workspace) {
96
+ throw new errors_1.UsageError(`archive qid ${value} conflicts with --ws ${normalizeWorkspace(ws)}`);
97
+ }
98
+ return { id, workspace };
99
+ }
100
+ return { id: normalizeArchiveId(value), workspace: normalizeWorkspace(ws) };
101
+ }
86
102
  function archiveIdFromInput(value) {
87
103
  return normalizeArchiveId((0, refs_1.archiveIdFromUri)(value) ?? value);
88
104
  }
@@ -133,9 +149,8 @@ function maybeReindex(root) {
133
149
  function resolveArchiveNode(root, id, ws) {
134
150
  const config = (0, config_1.loadConfig)(root);
135
151
  const { index } = (0, index_cache_1.loadIndex)({ root, config });
136
- const archiveId = archiveIdFromInput(id);
137
- const wsHint = normalizeWorkspace(ws);
138
- const qid = archiveId.includes(":") ? archiveId : `${wsHint}:${archiveId}`;
152
+ const target = archiveTargetFromInput(id, ws);
153
+ const qid = `${target.workspace}:${target.id}`;
139
154
  const node = index.nodes[qid];
140
155
  if (!node || node.type !== "archive") {
141
156
  throw new errors_1.NotFoundError(`archive not found: ${id}`);
@@ -143,6 +158,9 @@ function resolveArchiveNode(root, id, ws) {
143
158
  return node;
144
159
  }
145
160
  function archiveNodePaths(root, node) {
161
+ if (node.source?.imported || node.source?.read_only || node.path.includes("#")) {
162
+ throw new errors_1.ValidationError(`refusing to derive filesystem paths for read-only archive projection ${node.qid}`);
163
+ }
146
164
  const sidecarPath = path_1.default.resolve(root, node.path);
147
165
  const sidecarDir = path_1.default.dirname(sidecarPath);
148
166
  return {
@@ -175,9 +193,12 @@ function stringAttribute(value) {
175
193
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
176
194
  }
177
195
  function writeArchiveSidecar(root, sidecarPath, frontmatter, body) {
196
+ (0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: toPosixPath(path_1.default.relative(root, sidecarPath)) }, formatArchiveSidecar(frontmatter, body));
197
+ }
198
+ function formatArchiveSidecar(frontmatter, body) {
178
199
  const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter);
179
200
  const content = ["---", ...lines, "---", body.trimStart()].join("\n");
180
- (0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: toPosixPath(path_1.default.relative(root, sidecarPath)) }, content.endsWith("\n") ? content : `${content}\n`);
201
+ return content.endsWith("\n") ? content : `${content}\n`;
181
202
  }
182
203
  function verifyArchiveSidecar(root, ws, sidecarPath) {
183
204
  const relativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
@@ -436,42 +457,137 @@ function runArchiveVerifyCommand(options) {
436
457
  throw new errors_1.ValidationError("archive verification failed");
437
458
  }
438
459
  }
439
- function runArchiveCompressCommandLocked(options) {
440
- if (!options.all && !options.id) {
441
- throw new errors_1.UsageError("archive compress requires <id-or-archive-uri> or --all");
460
+ function isReadOnlyArchiveProjection(config, node) {
461
+ return Boolean(node.source?.imported ||
462
+ node.source?.read_only ||
463
+ config.subgraphs[node.source?.subgraph_alias ?? node.ws]);
464
+ }
465
+ function readOnlyArchiveError(node) {
466
+ const alias = node.source?.subgraph_alias ?? node.ws;
467
+ return new errors_1.ValidationError(`cannot compress read-only archive ${node.qid} from imported workspace ${alias}; ` +
468
+ "run compression in the source workspace and refresh the subgraph bundle");
469
+ }
470
+ function readOnlyWorkspaceError(alias) {
471
+ return new errors_1.ValidationError(`cannot compress archives in read-only imported workspace ${alias}; ` +
472
+ "run compression in the source workspace and refresh the subgraph bundle");
473
+ }
474
+ function assertWritableArchiveOwner(config, node) {
475
+ if (isReadOnlyArchiveProjection(config, node)) {
476
+ throw readOnlyArchiveError(node);
442
477
  }
443
- const config = (0, config_1.loadConfig)(options.root);
444
- const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
445
- const nodes = options.all
446
- ? Object.values(index.nodes).filter((node) => node.type === "archive")
447
- : [resolveArchiveNode(options.root, String(options.id), options.ws)];
448
- const updated = [];
449
- const today = (0, date_1.formatDate)(options.now ?? new Date());
450
- for (const node of nodes) {
451
- const { sidecarPath, rawPath, zipPath } = archiveNodePaths(options.root, node);
452
- const rawRelativePath = toPosixPath(path_1.default.relative(options.root, rawPath));
453
- const zipRelativePath = toPosixPath(path_1.default.relative(options.root, zipPath));
454
- const sidecarRelativePath = toPosixPath(path_1.default.relative(options.root, sidecarPath));
455
- for (const relativePath of [rawRelativePath, zipRelativePath, sidecarRelativePath]) {
456
- (0, filesystem_authority_1.withContainedPathSink)({ root: options.root, relativePath, operation: relativePath === rawRelativePath ? "read" : "replace" }, () => undefined);
478
+ const workspace = config.workspaces[node.ws];
479
+ if (!workspace) {
480
+ throw new errors_1.ValidationError(`archive ${node.qid} has no configured local workspace owner`);
481
+ }
482
+ if (!workspace.enabled) {
483
+ throw new errors_1.ValidationError(`archive workspace ${node.ws} is disabled and not writable`);
484
+ }
485
+ const archiveRoot = (0, workspace_path_1.workspaceDocumentRelativePath)(workspace.path, workspace.mdkg_dir, "archive");
486
+ const nodePath = toPosixPath(node.path);
487
+ if (!nodePath.startsWith(`${archiveRoot}/`)) {
488
+ throw new errors_1.ValidationError(`archive ${node.qid} path is outside its configured local archive root: ${archiveRoot}`);
489
+ }
490
+ }
491
+ function resolveArchiveNodeFromIndex(index, id, ws) {
492
+ const target = archiveTargetFromInput(id, ws);
493
+ const qid = `${target.workspace}:${target.id}`;
494
+ const node = index.nodes[qid];
495
+ if (!node || node.type !== "archive") {
496
+ throw new errors_1.NotFoundError(`archive not found: ${id}`);
497
+ }
498
+ return node;
499
+ }
500
+ function selectArchiveCompressionNodes(options, config, index) {
501
+ if (options.all && options.id) {
502
+ throw new errors_1.UsageError("archive compress accepts either <id-or-archive-uri-or-qid> or --all, not both");
503
+ }
504
+ const requestedWorkspace = options.ws ? normalizeWorkspace(options.ws) : null;
505
+ const excluded = [];
506
+ let nodes;
507
+ if (options.all) {
508
+ if (requestedWorkspace && config.subgraphs[requestedWorkspace]) {
509
+ throw readOnlyWorkspaceError(requestedWorkspace);
457
510
  }
458
- if (!(0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath: rawRelativePath })) {
459
- throw new errors_1.NotFoundError(`raw archive file missing for ${node.qid}: ${path_1.default.relative(options.root, rawPath)}`);
511
+ if (requestedWorkspace) {
512
+ const workspace = config.workspaces[requestedWorkspace];
513
+ if (!workspace) {
514
+ throw new errors_1.NotFoundError(`workspace not found: ${requestedWorkspace}`);
515
+ }
516
+ if (!workspace.enabled) {
517
+ throw new errors_1.ValidationError(`archive workspace ${requestedWorkspace} is disabled and not writable`);
518
+ }
460
519
  }
461
- const rawData = (0, filesystem_authority_1.readContainedFile)({ root: options.root, relativePath: rawRelativePath }, null);
462
- const zipData = (0, zip_1.createDeterministicZip)(path_1.default.basename(rawPath), rawData);
463
- (0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: zipRelativePath }, zipData);
464
- const parsed = (0, frontmatter_1.parseFrontmatter)((0, filesystem_authority_1.readContainedFile)({ root: options.root, relativePath: sidecarRelativePath }), sidecarPath);
465
- const nextFrontmatter = {
466
- ...parsed.frontmatter,
467
- byte_size: String(rawData.length),
468
- sha256: (0, archive_integrity_1.hashArchiveBuffer)(rawData),
469
- compressed_sha256: (0, archive_integrity_1.hashArchiveBuffer)(zipData),
470
- ingest_status: "compressed",
471
- updated: today,
472
- };
473
- writeArchiveSidecar(options.root, sidecarPath, nextFrontmatter, parsed.body);
474
- updated.push({
520
+ nodes = Object.values(index.nodes)
521
+ .filter((node) => node.type === "archive")
522
+ .sort((a, b) => a.qid.localeCompare(b.qid))
523
+ .filter((node) => {
524
+ if (requestedWorkspace && node.ws !== requestedWorkspace) {
525
+ return false;
526
+ }
527
+ if (isReadOnlyArchiveProjection(config, node)) {
528
+ if (!requestedWorkspace) {
529
+ excluded.push({
530
+ workspace: node.ws,
531
+ qid: node.qid,
532
+ reason: "read_only_imported_subgraph",
533
+ });
534
+ }
535
+ return false;
536
+ }
537
+ assertWritableArchiveOwner(config, node);
538
+ return true;
539
+ });
540
+ }
541
+ else {
542
+ const node = resolveArchiveNodeFromIndex(index, String(options.id), options.ws);
543
+ assertWritableArchiveOwner(config, node);
544
+ nodes = [node];
545
+ }
546
+ return {
547
+ nodes,
548
+ selection: {
549
+ requested_workspace: requestedWorkspace,
550
+ selected_workspaces: Array.from(new Set(nodes.map((node) => node.ws))).sort(),
551
+ excluded_read_only: excluded.sort((a, b) => a.qid.localeCompare(b.qid)),
552
+ },
553
+ };
554
+ }
555
+ function preflightArchiveCompression(root, node, today) {
556
+ for (const attribute of ["stored_path", "compressed_path"]) {
557
+ if (typeof node.attributes[attribute] !== "string" || !String(node.attributes[attribute]).trim()) {
558
+ throw new errors_1.ValidationError(`${node.qid}: ${attribute} is required before archive compression`);
559
+ }
560
+ }
561
+ const { sidecarPath, rawPath, zipPath } = archiveNodePaths(root, node);
562
+ const rawRelativePath = toPosixPath(path_1.default.relative(root, rawPath));
563
+ const zipRelativePath = toPosixPath(path_1.default.relative(root, zipPath));
564
+ const sidecarRelativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
565
+ for (const relativePath of [rawRelativePath, zipRelativePath, sidecarRelativePath]) {
566
+ (0, filesystem_authority_1.withContainedPathSink)({ root, relativePath, operation: relativePath === rawRelativePath ? "read" : "replace" }, () => undefined);
567
+ }
568
+ if (!(0, filesystem_authority_1.containedPathExists)({ root, relativePath: rawRelativePath })) {
569
+ throw new errors_1.NotFoundError(`raw archive file missing for ${node.qid}: ${path_1.default.relative(root, rawPath)}`);
570
+ }
571
+ const rawData = (0, filesystem_authority_1.readContainedFile)({ root, relativePath: rawRelativePath }, null);
572
+ const parsed = (0, frontmatter_1.parseFrontmatter)((0, filesystem_authority_1.readContainedFile)({ root, relativePath: sidecarRelativePath }), sidecarPath);
573
+ if (parsed.frontmatter.type !== "archive" || parsed.frontmatter.id !== node.id) {
574
+ throw new errors_1.ValidationError(`${node.qid}: archive sidecar identity changed before compression`);
575
+ }
576
+ const zipData = (0, zip_1.createDeterministicZip)(path_1.default.basename(rawPath), rawData);
577
+ const nextFrontmatter = {
578
+ ...parsed.frontmatter,
579
+ byte_size: String(rawData.length),
580
+ sha256: (0, archive_integrity_1.hashArchiveBuffer)(rawData),
581
+ compressed_sha256: (0, archive_integrity_1.hashArchiveBuffer)(zipData),
582
+ ingest_status: "compressed",
583
+ updated: today,
584
+ };
585
+ return {
586
+ zipRelativePath,
587
+ sidecarRelativePath,
588
+ zipData,
589
+ sidecarContent: formatArchiveSidecar(nextFrontmatter, parsed.body),
590
+ receipt: {
475
591
  workspace: node.ws,
476
592
  id: node.id,
477
593
  qid: node.qid,
@@ -482,14 +598,35 @@ function runArchiveCompressCommandLocked(options) {
482
598
  sha256: String(nextFrontmatter.sha256),
483
599
  compressed_sha256: String(nextFrontmatter.compressed_sha256),
484
600
  visibility: (0, visibility_1.normalizeVisibility)(typeof nextFrontmatter.visibility === "string" ? nextFrontmatter.visibility : undefined, "archive visibility"),
485
- });
601
+ },
602
+ };
603
+ }
604
+ function runArchiveCompressCommandLocked(options) {
605
+ if (!options.all && !options.id) {
606
+ throw new errors_1.UsageError("archive compress requires <id-or-archive-uri-or-qid> or --all");
607
+ }
608
+ const config = (0, config_1.loadConfig)(options.root);
609
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
610
+ const { nodes, selection } = selectArchiveCompressionNodes(options, config, index);
611
+ const today = (0, date_1.formatDate)(options.now ?? new Date());
612
+ const plans = nodes.map((node) => preflightArchiveCompression(options.root, node, today));
613
+ const updated = [];
614
+ for (const plan of plans) {
615
+ (0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: plan.zipRelativePath }, plan.zipData);
616
+ (0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: plan.sidecarRelativePath }, plan.sidecarContent);
617
+ updated.push(plan.receipt);
486
618
  }
487
619
  maybeReindex(options.root);
488
620
  if (options.json) {
489
- console.log(JSON.stringify({ action: "compressed", count: updated.length, archives: updated }, null, 2));
621
+ console.log(JSON.stringify({ action: "compressed", count: updated.length, archives: updated, selection }, null, 2));
490
622
  return;
491
623
  }
492
624
  console.log(`archive compressed: ${updated.length}`);
625
+ console.log(`selected workspaces: ${selection.selected_workspaces.join(", ") || "none"}`);
626
+ console.log(`excluded read-only projections: ${selection.excluded_read_only.length}`);
627
+ for (const excluded of selection.excluded_read_only) {
628
+ console.log(` - ${excluded.qid} (${excluded.reason})`);
629
+ }
493
630
  }
494
631
  function withArchiveLock(root, fn) {
495
632
  const config = (0, config_1.loadConfig)(root);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "tool": "mdkg",
4
- "mdkg_version": "0.5.0",
4
+ "mdkg_version": "0.5.1",
5
5
  "files": [
6
6
  {
7
7
  "path": ".mdkg/config.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdkg",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Git-native project memory for AI coding agents",
5
5
  "license": "MIT",
6
6
  "bin": {