@gmickel/gno 1.31.0 → 1.33.0

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.
Files changed (77) hide show
  1. package/README.md +15 -5
  2. package/assets/skill/SKILL.md +34 -0
  3. package/assets/skill/cli-reference.md +10 -2
  4. package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.zip → gno-browser-clipper-v1.33.0.zip} +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v1.33.0.zip.sha256 +1 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/package.json +5 -1
  8. package/spec/cli.md +35 -1
  9. package/spec/db/schema.sql +55 -0
  10. package/spec/mcp.md +114 -11
  11. package/spec/output-schemas/file-refactor-apply-result.schema.json +305 -0
  12. package/spec/output-schemas/file-refactor-preview.schema.json +393 -0
  13. package/spec/output-schemas/publish-artifact.schema.json +76 -1
  14. package/src/cli/commands/publish.ts +43 -7
  15. package/src/core/document-capabilities.ts +13 -0
  16. package/src/core/file-ops.ts +129 -1
  17. package/src/core/file-refactor-adapter.ts +329 -0
  18. package/src/core/file-refactor-apply-edits.ts +61 -0
  19. package/src/core/file-refactor-apply-fs.ts +512 -0
  20. package/src/core/file-refactor-apply-safety.ts +340 -0
  21. package/src/core/file-refactor-apply-validate.ts +401 -0
  22. package/src/core/file-refactor-contract.ts +486 -0
  23. package/src/core/file-refactor-destination.ts +123 -0
  24. package/src/core/file-refactor-from-snapshot.ts +148 -0
  25. package/src/core/file-refactor-journal-port.ts +150 -0
  26. package/src/core/file-refactor-journal.ts +347 -0
  27. package/src/core/file-refactor-paths.ts +60 -0
  28. package/src/core/file-refactor-plan-classify.ts +208 -0
  29. package/src/core/file-refactor-plan-validate.ts +169 -0
  30. package/src/core/file-refactor-planner-types.ts +62 -0
  31. package/src/core/file-refactor-planner.ts +423 -0
  32. package/src/core/file-refactor-resolve.ts +280 -0
  33. package/src/core/file-refactor-service.ts +468 -0
  34. package/src/core/file-refactors.ts +84 -56
  35. package/src/core/link-destination-parse.ts +275 -0
  36. package/src/core/link-inventory-markdown.ts +454 -0
  37. package/src/core/link-inventory-opaque.ts +244 -0
  38. package/src/core/link-inventory-types.ts +47 -0
  39. package/src/core/link-inventory.ts +182 -0
  40. package/src/core/link-relevance.ts +150 -0
  41. package/src/ingestion/strip.ts +152 -26
  42. package/src/mcp/tools/index.ts +18 -17
  43. package/src/mcp/tools/workspace-write.ts +215 -97
  44. package/src/publish/artifact-asset-codec.ts +75 -0
  45. package/src/publish/artifact-asset-contract.ts +152 -0
  46. package/src/publish/artifact-asset-parse.ts +401 -0
  47. package/src/publish/artifact-asset-sniff.ts +108 -0
  48. package/src/publish/artifact-asset-validate.ts +209 -0
  49. package/src/publish/artifact-assets.ts +58 -0
  50. package/src/publish/artifact-validation.ts +32 -6
  51. package/src/publish/artifact.ts +50 -3
  52. package/src/publish/attachment-bundle.ts +145 -0
  53. package/src/publish/attachment-discover.ts +203 -0
  54. package/src/publish/attachment-load.ts +133 -0
  55. package/src/publish/attachment-obsidian.ts +45 -0
  56. package/src/publish/attachment-path.ts +334 -0
  57. package/src/publish/attachment-raster.ts +852 -0
  58. package/src/publish/attachment-resolver.ts +280 -0
  59. package/src/publish/attachment-types.ts +54 -0
  60. package/src/publish/encrypted-export.ts +121 -44
  61. package/src/publish/export-attachments.ts +224 -0
  62. package/src/publish/export-service.ts +142 -80
  63. package/src/publish/obsidian-sanitize.ts +121 -13
  64. package/src/sdk/client.ts +167 -115
  65. package/src/sdk/index.ts +7 -0
  66. package/src/sdk/types.ts +32 -2
  67. package/src/serve/file-refactor-http.ts +239 -0
  68. package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
  69. package/src/serve/public/globals.built.css +1 -1
  70. package/src/serve/public/pages/DocView.tsx +176 -41
  71. package/src/serve/routes/api.ts +193 -105
  72. package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
  73. package/src/store/migrations/index.ts +2 -0
  74. package/src/store/sqlite/adapter.ts +452 -0
  75. package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
  76. package/src/store/types.ts +84 -0
  77. package/browser-extension/artifacts/gno-browser-clipper-v1.31.0.zip.sha256 +0 -1
@@ -16,6 +16,11 @@ import { AsyncLocalStorage } from "node:async_hooks";
16
16
  import { basename } from "node:path";
17
17
 
18
18
  import type { EgressLineage } from "../../core/egress-provenance";
19
+ import type {
20
+ FileRefactorJournalAdvance,
21
+ FileRefactorRecoveryReceipt,
22
+ FileRefactorRecoveryReceiptDraft,
23
+ } from "../../core/file-refactor-journal";
19
24
  import type {
20
25
  ActivationIndexDocument,
21
26
  ActivationIndexIdentity,
@@ -55,6 +60,8 @@ import type {
55
60
  EgressAuditStatusResult,
56
61
  FtsResult,
57
62
  FtsSearchOptions,
63
+ FileRefactorResolutionReferrerDocument,
64
+ FileRefactorResolutionSnapshot,
58
65
  GetGraphNeighborsOptions,
59
66
  GetGraphOptions,
60
67
  GraphEdgeConfidence,
@@ -112,6 +119,10 @@ import {
112
119
  type FtsTokenizer,
113
120
  resolveConfiguredEgressPolicy,
114
121
  } from "../../config/types";
122
+ import {
123
+ getDocumentCapabilities,
124
+ isTextLikeReferenceDocument,
125
+ } from "../../core/document-capabilities";
115
126
  import { analyzeGraphCommunities } from "../../core/graph-analysis";
116
127
  import {
117
128
  classifyResolvedGraphEdge,
@@ -122,6 +133,7 @@ import {
122
133
  buildWikiBestRankMatchCountSubquery,
123
134
  buildWikiBestRankSubquery,
124
135
  } from "../../core/graph-resolver";
136
+ import { buildContentPrefilterNeedles } from "../../core/link-relevance";
125
137
  import { normalizeWikiName, stripWikiMdExt } from "../../core/links";
126
138
  import {
127
139
  parseActivationReceipt,
@@ -160,6 +172,12 @@ import {
160
172
  listEgressAuditReceipts as listStoredEgressAuditReceipts,
161
173
  purgeEgressAuditReceipts as purgeStoredEgressAuditReceipts,
162
174
  } from "./egress-audit-store";
175
+ import {
176
+ advanceFileRefactorReceipt as advanceStoredFileRefactorReceipt,
177
+ createFileRefactorPreparedReceipt as createStoredFileRefactorPreparedReceipt,
178
+ getFileRefactorReceiptById as getStoredFileRefactorReceiptById,
179
+ getLatestFileRefactorReceiptByPlanDigest as getStoredLatestFileRefactorReceiptByPlanDigest,
180
+ } from "./file-refactor-journal-store";
163
181
  import { loadFts5Snowball } from "./fts5-snowball";
164
182
  import { queryGraphNeighborsForSeeds } from "./graph-neighbors";
165
183
  import {
@@ -1960,6 +1978,38 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1960
1978
  return purgeStoredDocumentChanges(this.ensureOpen());
1961
1979
  }
1962
1980
 
1981
+ async createFileRefactorPreparedReceipt(
1982
+ draft: FileRefactorRecoveryReceiptDraft
1983
+ ): Promise<StoreResult<FileRefactorRecoveryReceipt>> {
1984
+ return createStoredFileRefactorPreparedReceipt(this.ensureOpen(), draft);
1985
+ }
1986
+
1987
+ async advanceFileRefactorReceipt(
1988
+ journalId: string,
1989
+ update: FileRefactorJournalAdvance
1990
+ ): Promise<StoreResult<FileRefactorRecoveryReceipt>> {
1991
+ return advanceStoredFileRefactorReceipt(
1992
+ this.ensureOpen(),
1993
+ journalId,
1994
+ update
1995
+ );
1996
+ }
1997
+
1998
+ async getFileRefactorReceiptById(
1999
+ journalId: string
2000
+ ): Promise<StoreResult<FileRefactorRecoveryReceipt | null>> {
2001
+ return getStoredFileRefactorReceiptById(this.ensureOpen(), journalId);
2002
+ }
2003
+
2004
+ async getLatestFileRefactorReceiptByPlanDigest(
2005
+ planDigest: string
2006
+ ): Promise<StoreResult<FileRefactorRecoveryReceipt | null>> {
2007
+ return getStoredLatestFileRefactorReceiptByPlanDigest(
2008
+ this.ensureOpen(),
2009
+ planDigest
2010
+ );
2011
+ }
2012
+
1963
2013
  async upsertSavedCapsuleRegistration(
1964
2014
  input: SavedCapsuleRegistrationInput
1965
2015
  ): Promise<StoreResult<SavedCapsuleRegistrationRecord>> {
@@ -3371,6 +3421,408 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
3371
3421
  }
3372
3422
  }
3373
3423
 
3424
+ /**
3425
+ * Bounded read seam for reference-safe rename/move planning.
3426
+ * Loads catalog metadata plus content for indexed backlinks unioned with a
3427
+ * conservative SQL content prefilter (opaque embeds/HTML/code/malformed may
3428
+ * not appear in doc_links). Missing mirror content fails closed.
3429
+ */
3430
+ async getFileRefactorResolutionSnapshot(input: {
3431
+ sourceUri: string;
3432
+ maxCatalogDocuments?: number;
3433
+ maxReferrerDocuments?: number;
3434
+ maxContentCharsPerDocument?: number;
3435
+ maxTotalContentChars?: number;
3436
+ }): Promise<StoreResult<FileRefactorResolutionSnapshot>> {
3437
+ try {
3438
+ const db = this.ensureOpen();
3439
+ const maxCatalog = Math.max(
3440
+ 1,
3441
+ Math.floor(input.maxCatalogDocuments ?? 5_000)
3442
+ );
3443
+ const maxReferrers = Math.max(
3444
+ 1,
3445
+ Math.floor(input.maxReferrerDocuments ?? 5_000)
3446
+ );
3447
+ const maxChars = Math.max(
3448
+ 1,
3449
+ Math.floor(input.maxContentCharsPerDocument ?? 1_000_000)
3450
+ );
3451
+ const maxTotalChars = Math.max(
3452
+ maxChars,
3453
+ Math.floor(input.maxTotalContentChars ?? 20_000_000)
3454
+ );
3455
+ const truncationReasons: string[] = [];
3456
+
3457
+ const sourceRow = db
3458
+ .query<
3459
+ {
3460
+ id: number;
3461
+ uri: string;
3462
+ rel_path: string;
3463
+ collection: string;
3464
+ title: string | null;
3465
+ mirror_hash: string | null;
3466
+ source_ext: string;
3467
+ source_mime: string;
3468
+ record_key: string | null;
3469
+ },
3470
+ [string]
3471
+ >(
3472
+ `SELECT id, uri, rel_path, collection, title, mirror_hash,
3473
+ source_ext, source_mime, record_key
3474
+ FROM documents
3475
+ WHERE uri = ? AND active = 1`
3476
+ )
3477
+ .get(input.sourceUri);
3478
+
3479
+ if (!sourceRow) {
3480
+ return err("NOT_FOUND", `Document not found: ${input.sourceUri}`);
3481
+ }
3482
+
3483
+ const sourceCaps = getDocumentCapabilities({
3484
+ sourceExt: sourceRow.source_ext,
3485
+ sourceMime: sourceRow.source_mime,
3486
+ contentAvailable: Boolean(sourceRow.mirror_hash),
3487
+ recordKey: sourceRow.record_key,
3488
+ });
3489
+
3490
+ let sourceContent: string | null = null;
3491
+ let sourceContentTruncated = false;
3492
+ if (sourceRow.mirror_hash) {
3493
+ const contentRow = db
3494
+ .query<{ markdown: string }, [number, string]>(
3495
+ `SELECT substr(markdown, 1, ?) AS markdown
3496
+ FROM content WHERE mirror_hash = ?`
3497
+ )
3498
+ .get(maxChars + 1, sourceRow.mirror_hash);
3499
+ if (contentRow) {
3500
+ sourceContentTruncated = contentRow.markdown.length > maxChars;
3501
+ sourceContent = sourceContentTruncated
3502
+ ? contentRow.markdown.slice(0, maxChars)
3503
+ : contentRow.markdown;
3504
+ if (sourceContentTruncated) {
3505
+ truncationReasons.push("source_content_truncated");
3506
+ }
3507
+ } else {
3508
+ truncationReasons.push("source_content_missing");
3509
+ }
3510
+ } else {
3511
+ // Null mirror_hash means source bytes are unavailable — fail closed.
3512
+ truncationReasons.push("source_content_missing");
3513
+ }
3514
+
3515
+ const catalogRows = db
3516
+ .query<
3517
+ {
3518
+ id: number;
3519
+ uri: string;
3520
+ rel_path: string;
3521
+ collection: string;
3522
+ title: string | null;
3523
+ },
3524
+ [string, number]
3525
+ >(
3526
+ `SELECT id, uri, rel_path, collection, title
3527
+ FROM documents
3528
+ WHERE active = 1 AND collection = ?
3529
+ ORDER BY id
3530
+ LIMIT ?`
3531
+ )
3532
+ .all(sourceRow.collection, maxCatalog + 1);
3533
+
3534
+ if (catalogRows.length > maxCatalog) {
3535
+ truncationReasons.push("catalog_truncated");
3536
+ }
3537
+ const catalog = catalogRows.slice(0, maxCatalog).map((row) => ({
3538
+ id: row.id,
3539
+ uri: row.uri,
3540
+ relPath: row.rel_path,
3541
+ collection: row.collection,
3542
+ title: row.title,
3543
+ }));
3544
+
3545
+ const occupiedRelPaths = catalog.map((doc) => doc.relPath);
3546
+
3547
+ const backlinksResult = await this.getBacklinksForDoc(sourceRow.id, {
3548
+ collection: sourceRow.collection,
3549
+ });
3550
+ if (!backlinksResult.ok) {
3551
+ return backlinksResult;
3552
+ }
3553
+
3554
+ const candidateIds = new Set<number>();
3555
+ for (const row of backlinksResult.value) {
3556
+ if (row.sourceDocId !== sourceRow.id) {
3557
+ candidateIds.add(row.sourceDocId);
3558
+ }
3559
+ }
3560
+
3561
+ const needles = buildContentPrefilterNeedles({
3562
+ relPath: sourceRow.rel_path,
3563
+ title: sourceRow.title,
3564
+ });
3565
+ const escapeLike = (value: string): string =>
3566
+ value
3567
+ .replaceAll("\\", "\\\\")
3568
+ .replaceAll("%", "\\%")
3569
+ .replaceAll("_", "\\_");
3570
+
3571
+ if (needles.length > 0) {
3572
+ const likeClauses = needles
3573
+ .map(() => `c.markdown LIKE '%' || ? || '%' ESCAPE '\\'`)
3574
+ .join(" OR ");
3575
+ const prefilterRows = db
3576
+ .query<{ id: number }, (string | number)[]>(
3577
+ `SELECT DISTINCT d.id AS id
3578
+ FROM documents d
3579
+ INNER JOIN content c ON c.mirror_hash = d.mirror_hash
3580
+ WHERE d.active = 1
3581
+ AND d.collection = ?
3582
+ AND d.id != ?
3583
+ AND (${likeClauses})
3584
+ ORDER BY d.id
3585
+ LIMIT ?`
3586
+ )
3587
+ .all(
3588
+ sourceRow.collection,
3589
+ sourceRow.id,
3590
+ ...needles.map(escapeLike),
3591
+ maxReferrers + 1
3592
+ );
3593
+ if (prefilterRows.length > maxReferrers) {
3594
+ truncationReasons.push("content_prefilter_truncated");
3595
+ }
3596
+ for (const row of prefilterRows.slice(0, maxReferrers + 1)) {
3597
+ candidateIds.add(row.id);
3598
+ }
3599
+ }
3600
+
3601
+ // Completeness: union same-collection docs whose mirror content cannot be
3602
+ // loaded. Text-like docs (including read-only logical .md records) fail
3603
+ // closed; non-text binaries are omitted (they cannot hold markdown/wiki/
3604
+ // HTML refs). Preserve referrer caps via LIMIT + sorted slice below.
3605
+ const missingMirrorRows = db
3606
+ .query<
3607
+ {
3608
+ id: number;
3609
+ source_ext: string;
3610
+ source_mime: string;
3611
+ },
3612
+ [string, number, number]
3613
+ >(
3614
+ `SELECT d.id AS id,
3615
+ d.source_ext AS source_ext,
3616
+ d.source_mime AS source_mime
3617
+ FROM documents d
3618
+ WHERE d.active = 1
3619
+ AND d.collection = ?
3620
+ AND d.id != ?
3621
+ AND (
3622
+ d.mirror_hash IS NULL
3623
+ OR NOT EXISTS (
3624
+ SELECT 1 FROM content c WHERE c.mirror_hash = d.mirror_hash
3625
+ )
3626
+ )
3627
+ ORDER BY d.id
3628
+ LIMIT ?`
3629
+ )
3630
+ .all(sourceRow.collection, sourceRow.id, maxReferrers + 1);
3631
+ if (missingMirrorRows.length > maxReferrers) {
3632
+ truncationReasons.push("missing_mirror_scan_truncated");
3633
+ }
3634
+ for (const row of missingMirrorRows) {
3635
+ if (!isTextLikeReferenceDocument(row.source_ext, row.source_mime)) {
3636
+ continue;
3637
+ }
3638
+ candidateIds.add(row.id);
3639
+ }
3640
+
3641
+ const sortedCandidateIds = [...candidateIds].sort((a, b) => a - b);
3642
+ if (sortedCandidateIds.length > maxReferrers) {
3643
+ truncationReasons.push("referrers_truncated");
3644
+ }
3645
+
3646
+ const referrers: FileRefactorResolutionReferrerDocument[] = [];
3647
+ let totalContentChars = sourceContent?.length ?? 0;
3648
+
3649
+ for (const referrerId of sortedCandidateIds.slice(0, maxReferrers)) {
3650
+ const referrerRow = db
3651
+ .query<
3652
+ {
3653
+ id: number;
3654
+ uri: string;
3655
+ rel_path: string;
3656
+ collection: string;
3657
+ title: string | null;
3658
+ mirror_hash: string | null;
3659
+ source_ext: string;
3660
+ source_mime: string;
3661
+ record_key: string | null;
3662
+ },
3663
+ [number]
3664
+ >(
3665
+ `SELECT id, uri, rel_path, collection, title, mirror_hash,
3666
+ source_ext, source_mime, record_key
3667
+ FROM documents
3668
+ WHERE id = ? AND active = 1`
3669
+ )
3670
+ .get(referrerId);
3671
+ if (!referrerRow) continue;
3672
+
3673
+ const caps = getDocumentCapabilities({
3674
+ sourceExt: referrerRow.source_ext,
3675
+ sourceMime: referrerRow.source_mime,
3676
+ contentAvailable: Boolean(referrerRow.mirror_hash),
3677
+ recordKey: referrerRow.record_key,
3678
+ });
3679
+
3680
+ if (!referrerRow.mirror_hash) {
3681
+ truncationReasons.push("referrer_content_missing");
3682
+ referrers.push({
3683
+ id: referrerRow.id,
3684
+ uri: referrerRow.uri,
3685
+ relPath: referrerRow.rel_path,
3686
+ collection: referrerRow.collection,
3687
+ title: referrerRow.title,
3688
+ content: null,
3689
+ contentTruncated: false,
3690
+ contentMissing: true,
3691
+ editable: caps.editable,
3692
+ editableReason: caps.editable
3693
+ ? undefined
3694
+ : caps.reason
3695
+ ? "read_only_document"
3696
+ : "capability_denied",
3697
+ sourceExt: referrerRow.source_ext,
3698
+ sourceMime: referrerRow.source_mime,
3699
+ recordKey: referrerRow.record_key,
3700
+ });
3701
+ continue;
3702
+ }
3703
+
3704
+ const contentRow = db
3705
+ .query<{ markdown: string }, [number, string]>(
3706
+ `SELECT substr(markdown, 1, ?) AS markdown
3707
+ FROM content WHERE mirror_hash = ?`
3708
+ )
3709
+ .get(maxChars + 1, referrerRow.mirror_hash);
3710
+
3711
+ if (!contentRow) {
3712
+ truncationReasons.push("referrer_content_missing");
3713
+ referrers.push({
3714
+ id: referrerRow.id,
3715
+ uri: referrerRow.uri,
3716
+ relPath: referrerRow.rel_path,
3717
+ collection: referrerRow.collection,
3718
+ title: referrerRow.title,
3719
+ content: null,
3720
+ contentTruncated: false,
3721
+ contentMissing: true,
3722
+ editable: caps.editable,
3723
+ editableReason: caps.editable
3724
+ ? undefined
3725
+ : caps.reason
3726
+ ? "read_only_document"
3727
+ : "capability_denied",
3728
+ sourceExt: referrerRow.source_ext,
3729
+ sourceMime: referrerRow.source_mime,
3730
+ recordKey: referrerRow.record_key,
3731
+ });
3732
+ continue;
3733
+ }
3734
+
3735
+ const contentTruncated = contentRow.markdown.length > maxChars;
3736
+ if (contentTruncated) {
3737
+ truncationReasons.push("referrer_content_truncated");
3738
+ }
3739
+ const content = contentTruncated
3740
+ ? contentRow.markdown.slice(0, maxChars)
3741
+ : contentRow.markdown;
3742
+ totalContentChars += content.length;
3743
+ if (totalContentChars > maxTotalChars) {
3744
+ truncationReasons.push("total_content_truncated");
3745
+ referrers.push({
3746
+ id: referrerRow.id,
3747
+ uri: referrerRow.uri,
3748
+ relPath: referrerRow.rel_path,
3749
+ collection: referrerRow.collection,
3750
+ title: referrerRow.title,
3751
+ content: null,
3752
+ contentTruncated: false,
3753
+ contentMissing: true,
3754
+ editable: caps.editable,
3755
+ editableReason: caps.editable
3756
+ ? undefined
3757
+ : caps.reason
3758
+ ? "read_only_document"
3759
+ : "capability_denied",
3760
+ sourceExt: referrerRow.source_ext,
3761
+ sourceMime: referrerRow.source_mime,
3762
+ recordKey: referrerRow.record_key,
3763
+ });
3764
+ continue;
3765
+ }
3766
+
3767
+ referrers.push({
3768
+ id: referrerRow.id,
3769
+ uri: referrerRow.uri,
3770
+ relPath: referrerRow.rel_path,
3771
+ collection: referrerRow.collection,
3772
+ title: referrerRow.title,
3773
+ content,
3774
+ contentTruncated,
3775
+ contentMissing: false,
3776
+ editable: caps.editable,
3777
+ editableReason: caps.editable
3778
+ ? undefined
3779
+ : caps.reason
3780
+ ? "read_only_document"
3781
+ : "capability_denied",
3782
+ sourceExt: referrerRow.source_ext,
3783
+ sourceMime: referrerRow.source_mime,
3784
+ recordKey: referrerRow.record_key,
3785
+ });
3786
+ }
3787
+
3788
+ const snapshot: FileRefactorResolutionSnapshot = {
3789
+ source: {
3790
+ id: sourceRow.id,
3791
+ uri: sourceRow.uri,
3792
+ relPath: sourceRow.rel_path,
3793
+ collection: sourceRow.collection,
3794
+ title: sourceRow.title,
3795
+ mirrorHash: sourceRow.mirror_hash,
3796
+ sourceExt: sourceRow.source_ext,
3797
+ sourceMime: sourceRow.source_mime,
3798
+ recordKey: sourceRow.record_key,
3799
+ content: sourceContent,
3800
+ contentTruncated: sourceContentTruncated,
3801
+ editable: sourceCaps.editable,
3802
+ editableReason: sourceCaps.editable
3803
+ ? undefined
3804
+ : sourceCaps.reason
3805
+ ? "read_only_document"
3806
+ : "capability_denied",
3807
+ },
3808
+ catalog,
3809
+ referrers,
3810
+ occupiedRelPaths,
3811
+ truncated: truncationReasons.length > 0,
3812
+ truncationReasons: [...new Set(truncationReasons)].sort(),
3813
+ };
3814
+ return ok(snapshot);
3815
+ } catch (cause) {
3816
+ return err(
3817
+ "QUERY_FAILED",
3818
+ cause instanceof Error
3819
+ ? cause.message
3820
+ : "Failed to load file refactor resolution snapshot",
3821
+ cause
3822
+ );
3823
+ }
3824
+ }
3825
+
3374
3826
  /**
3375
3827
  * Set semantic edges for a document.
3376
3828
  * Replaces edges from the given source.