@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.
- package/README.md +15 -5
- package/assets/skill/SKILL.md +34 -0
- package/assets/skill/cli-reference.md +10 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.zip → gno-browser-clipper-v1.33.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.33.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +5 -1
- package/spec/cli.md +35 -1
- package/spec/db/schema.sql +55 -0
- package/spec/mcp.md +114 -11
- package/spec/output-schemas/file-refactor-apply-result.schema.json +305 -0
- package/spec/output-schemas/file-refactor-preview.schema.json +393 -0
- package/spec/output-schemas/publish-artifact.schema.json +76 -1
- package/src/cli/commands/publish.ts +43 -7
- package/src/core/document-capabilities.ts +13 -0
- package/src/core/file-ops.ts +129 -1
- package/src/core/file-refactor-adapter.ts +329 -0
- package/src/core/file-refactor-apply-edits.ts +61 -0
- package/src/core/file-refactor-apply-fs.ts +512 -0
- package/src/core/file-refactor-apply-safety.ts +340 -0
- package/src/core/file-refactor-apply-validate.ts +401 -0
- package/src/core/file-refactor-contract.ts +486 -0
- package/src/core/file-refactor-destination.ts +123 -0
- package/src/core/file-refactor-from-snapshot.ts +148 -0
- package/src/core/file-refactor-journal-port.ts +150 -0
- package/src/core/file-refactor-journal.ts +347 -0
- package/src/core/file-refactor-paths.ts +60 -0
- package/src/core/file-refactor-plan-classify.ts +208 -0
- package/src/core/file-refactor-plan-validate.ts +169 -0
- package/src/core/file-refactor-planner-types.ts +62 -0
- package/src/core/file-refactor-planner.ts +423 -0
- package/src/core/file-refactor-resolve.ts +280 -0
- package/src/core/file-refactor-service.ts +468 -0
- package/src/core/file-refactors.ts +84 -56
- package/src/core/link-destination-parse.ts +275 -0
- package/src/core/link-inventory-markdown.ts +454 -0
- package/src/core/link-inventory-opaque.ts +244 -0
- package/src/core/link-inventory-types.ts +47 -0
- package/src/core/link-inventory.ts +182 -0
- package/src/core/link-relevance.ts +150 -0
- package/src/ingestion/strip.ts +152 -26
- package/src/mcp/tools/index.ts +18 -17
- package/src/mcp/tools/workspace-write.ts +215 -97
- package/src/publish/artifact-asset-codec.ts +75 -0
- package/src/publish/artifact-asset-contract.ts +152 -0
- package/src/publish/artifact-asset-parse.ts +401 -0
- package/src/publish/artifact-asset-sniff.ts +108 -0
- package/src/publish/artifact-asset-validate.ts +209 -0
- package/src/publish/artifact-assets.ts +58 -0
- package/src/publish/artifact-validation.ts +32 -6
- package/src/publish/artifact.ts +50 -3
- package/src/publish/attachment-bundle.ts +145 -0
- package/src/publish/attachment-discover.ts +203 -0
- package/src/publish/attachment-load.ts +133 -0
- package/src/publish/attachment-obsidian.ts +45 -0
- package/src/publish/attachment-path.ts +334 -0
- package/src/publish/attachment-raster.ts +852 -0
- package/src/publish/attachment-resolver.ts +280 -0
- package/src/publish/attachment-types.ts +54 -0
- package/src/publish/encrypted-export.ts +121 -44
- package/src/publish/export-attachments.ts +224 -0
- package/src/publish/export-service.ts +142 -80
- package/src/publish/obsidian-sanitize.ts +121 -13
- package/src/sdk/client.ts +167 -115
- package/src/sdk/index.ts +7 -0
- package/src/sdk/types.ts +32 -2
- package/src/serve/file-refactor-http.ts +239 -0
- package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/DocView.tsx +176 -41
- package/src/serve/routes/api.ts +193 -105
- package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +452 -0
- package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
- package/src/store/types.ts +84 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.31.0.zip.sha256 +0 -1
|
@@ -2,12 +2,23 @@
|
|
|
2
2
|
* Obsidian-aware markdown pre-processor for publish export.
|
|
3
3
|
*
|
|
4
4
|
* Strips wikilinks, drops navigation sidebar idioms, removes references to
|
|
5
|
-
* private (`_internal/`) paths, and
|
|
6
|
-
*
|
|
5
|
+
* private (`_internal/`) paths, and resolves/bundles local raster embeds when
|
|
6
|
+
* an attachment context is provided (otherwise drops image embeds with a
|
|
7
|
+
* warning for backward compatibility).
|
|
7
8
|
*
|
|
8
9
|
* @module src/publish/obsidian-sanitize
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import { discoverImageOccurrences } from "./attachment-discover";
|
|
13
|
+
import { safePercentDecode } from "./attachment-path";
|
|
14
|
+
import {
|
|
15
|
+
rewriteAttachmentsInMarkdown,
|
|
16
|
+
type AttachmentDiagnostic,
|
|
17
|
+
type AttachmentResolveContext,
|
|
18
|
+
type AttachmentRewriteResult,
|
|
19
|
+
type PendingAssetPayload,
|
|
20
|
+
} from "./attachment-resolver";
|
|
21
|
+
|
|
11
22
|
const WIKILINK_INTERNAL_PREFIX = /_internal\//i;
|
|
12
23
|
const NAV_SIDEBAR_LINE = /^(!?\[\[[^\]]+\]\]\s*\|?\s*)+$/;
|
|
13
24
|
const IMAGE_EMBED = /!\[\[([^\]]+)\]\]/g;
|
|
@@ -16,6 +27,7 @@ const ALIASED_WIKILINK = /\[\[([^\]|]+)\|([^\]]+)\]\]/g;
|
|
|
16
27
|
const BARE_WIKILINK = /\[\[([^\]]+)\]\]/g;
|
|
17
28
|
const TAIL_SEGMENT = /[^/]+$/;
|
|
18
29
|
const BLOCK_ID_SUFFIX = /#\^?[\w-]+$/;
|
|
30
|
+
const EXTERNAL_DESTINATION = /^(?:\/\/|[a-z][a-z0-9+.-]*:)/iu;
|
|
19
31
|
|
|
20
32
|
export interface SanitizeWarning {
|
|
21
33
|
kind:
|
|
@@ -31,6 +43,13 @@ export interface SanitizeResult {
|
|
|
31
43
|
warnings: SanitizeWarning[];
|
|
32
44
|
}
|
|
33
45
|
|
|
46
|
+
export interface PublishSanitizeResult extends SanitizeResult {
|
|
47
|
+
diagnostics: AttachmentDiagnostic[];
|
|
48
|
+
externalCount: number;
|
|
49
|
+
payloads: Map<string, PendingAssetPayload>;
|
|
50
|
+
preDedupRawBytes: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
34
53
|
const splitFrontmatter = (
|
|
35
54
|
source: string
|
|
36
55
|
): { body: string; frontmatter: string } => {
|
|
@@ -61,8 +80,57 @@ const deriveLinkDisplay = (target: string): string => {
|
|
|
61
80
|
return tail.trim() || raw;
|
|
62
81
|
};
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
const
|
|
83
|
+
const isPrivateImageReference = (sourceRef: string): boolean => {
|
|
84
|
+
const trimmed = sourceRef.trim();
|
|
85
|
+
if (EXTERNAL_DESTINATION.test(trimmed)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const decoded = safePercentDecode(trimmed);
|
|
89
|
+
if (decoded === null) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return (
|
|
93
|
+
decoded
|
|
94
|
+
.split(/[?#]/u, 1)[0]
|
|
95
|
+
?.split("/")
|
|
96
|
+
.some((segment) => segment.toLowerCase() === "_internal") ?? false
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove private image references before attachment resolution can read bytes
|
|
102
|
+
* or replace the authored path with an opaque gno-asset sentinel.
|
|
103
|
+
*/
|
|
104
|
+
const stripPrivateImageReferences = (
|
|
105
|
+
body: string
|
|
106
|
+
): { markdown: string; warnings: SanitizeWarning[] } => {
|
|
107
|
+
const replacements = discoverImageOccurrences(body)
|
|
108
|
+
.filter((occurrence) => isPrivateImageReference(occurrence.sourceRef))
|
|
109
|
+
.map((occurrence) => ({
|
|
110
|
+
detail: occurrence.sourceRef.trim(),
|
|
111
|
+
end: occurrence.end,
|
|
112
|
+
start: occurrence.start,
|
|
113
|
+
}));
|
|
114
|
+
let markdown = body;
|
|
115
|
+
for (const replacement of [...replacements].sort(
|
|
116
|
+
(left, right) => right.start - left.start
|
|
117
|
+
)) {
|
|
118
|
+
markdown =
|
|
119
|
+
markdown.slice(0, replacement.start) + markdown.slice(replacement.end);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
markdown,
|
|
123
|
+
warnings: replacements.map((replacement) => ({
|
|
124
|
+
detail: replacement.detail,
|
|
125
|
+
kind: "internal-reference-stripped" as const,
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const applyWikilinkSanitize = (
|
|
131
|
+
body: string,
|
|
132
|
+
options: { dropImageEmbeds: boolean }
|
|
133
|
+
): { markdown: string; warnings: SanitizeWarning[] } => {
|
|
66
134
|
const warnings: SanitizeWarning[] = [];
|
|
67
135
|
const lines = body.split("\n");
|
|
68
136
|
const output: string[] = [];
|
|
@@ -81,13 +149,15 @@ export function sanitizeObsidianMarkdown(source: string): SanitizeResult {
|
|
|
81
149
|
|
|
82
150
|
let line = rawLine;
|
|
83
151
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
152
|
+
if (options.dropImageEmbeds) {
|
|
153
|
+
line = line.replace(IMAGE_EMBED, (_match, target: string) => {
|
|
154
|
+
warnings.push({
|
|
155
|
+
kind: "image-embed-dropped",
|
|
156
|
+
detail: target.trim(),
|
|
157
|
+
});
|
|
158
|
+
return "";
|
|
88
159
|
});
|
|
89
|
-
|
|
90
|
-
});
|
|
160
|
+
}
|
|
91
161
|
|
|
92
162
|
line = line.replace(INTERNAL_WIKILINK, (match) => {
|
|
93
163
|
warnings.push({
|
|
@@ -134,9 +204,46 @@ export function sanitizeObsidianMarkdown(source: string): SanitizeResult {
|
|
|
134
204
|
output.push(line);
|
|
135
205
|
}
|
|
136
206
|
|
|
207
|
+
return { markdown: output.join("\n"), warnings };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Legacy sync sanitizer: drops Obsidian image embeds and converts wikilinks.
|
|
212
|
+
* Prefer `sanitizePublishMarkdown` when attachment bundling is enabled.
|
|
213
|
+
*/
|
|
214
|
+
export function sanitizeObsidianMarkdown(source: string): SanitizeResult {
|
|
215
|
+
const { body, frontmatter } = splitFrontmatter(source);
|
|
216
|
+
const sanitized = applyWikilinkSanitize(body, { dropImageEmbeds: true });
|
|
217
|
+
return {
|
|
218
|
+
markdown: `${frontmatter}${sanitized.markdown}`,
|
|
219
|
+
warnings: sanitized.warnings,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Parser-aware publish sanitize: resolve/rewrite local rasters in the same
|
|
225
|
+
* pass as Obsidian cleanup (image discovery is not a second independent scan).
|
|
226
|
+
*/
|
|
227
|
+
export async function sanitizePublishMarkdown(
|
|
228
|
+
source: string,
|
|
229
|
+
attachmentCtx: AttachmentResolveContext
|
|
230
|
+
): Promise<PublishSanitizeResult> {
|
|
231
|
+
const { body, frontmatter } = splitFrontmatter(source);
|
|
232
|
+
const privateImages = stripPrivateImageReferences(body);
|
|
233
|
+
const rewritten: AttachmentRewriteResult = await rewriteAttachmentsInMarkdown(
|
|
234
|
+
privateImages.markdown,
|
|
235
|
+
attachmentCtx
|
|
236
|
+
);
|
|
237
|
+
const sanitized = applyWikilinkSanitize(rewritten.markdown, {
|
|
238
|
+
dropImageEmbeds: false,
|
|
239
|
+
});
|
|
137
240
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
241
|
+
diagnostics: rewritten.diagnostics,
|
|
242
|
+
externalCount: rewritten.externalCount,
|
|
243
|
+
markdown: `${frontmatter}${sanitized.markdown}`,
|
|
244
|
+
payloads: rewritten.payloads,
|
|
245
|
+
preDedupRawBytes: rewritten.preDedupRawBytes,
|
|
246
|
+
warnings: [...privateImages.warnings, ...sanitized.warnings],
|
|
140
247
|
};
|
|
141
248
|
}
|
|
142
249
|
|
|
@@ -159,7 +266,8 @@ export function formatSanitizeWarnings(warnings: SanitizeWarning[]): string[] {
|
|
|
159
266
|
}
|
|
160
267
|
|
|
161
268
|
const labels: Record<SanitizeWarning["kind"], string> = {
|
|
162
|
-
"image-embed-dropped":
|
|
269
|
+
"image-embed-dropped":
|
|
270
|
+
"Image embeds dropped (unsupported, missing, or unresolved)",
|
|
163
271
|
"internal-reference-stripped": "Private `_internal/` references stripped",
|
|
164
272
|
"nav-sidebar-dropped": "Navigation sidebar lines dropped",
|
|
165
273
|
"wikilink-unresolved":
|
package/src/sdk/client.ts
CHANGED
|
@@ -34,10 +34,12 @@ import type {
|
|
|
34
34
|
GnoIndexResult,
|
|
35
35
|
GnoIndexStatus,
|
|
36
36
|
GnoListOptions,
|
|
37
|
+
GnoMoveNoteApplyOptions,
|
|
37
38
|
GnoMoveNoteOptions,
|
|
38
39
|
GnoMultiGetOptions,
|
|
39
40
|
GnoQueryOptions,
|
|
40
41
|
GnoRefactorNoteResult,
|
|
42
|
+
GnoRenameNoteApplyOptions,
|
|
41
43
|
GnoRenameNoteOptions,
|
|
42
44
|
GnoSearchOptions,
|
|
43
45
|
GnoUpdateOptions,
|
|
@@ -86,20 +88,23 @@ import { writeCapturePlanFile } from "../core/capture-write";
|
|
|
86
88
|
import { projectCollectionEgressPolicy } from "../core/collection-egress-policy-projection";
|
|
87
89
|
import { CollectionEgressPolicyService } from "../core/collection-egress-policy-service";
|
|
88
90
|
import { applyConfigChange } from "../core/config-mutation";
|
|
91
|
+
import { getDocumentCapabilities } from "../core/document-capabilities";
|
|
89
92
|
import { EgressAuditService } from "../core/egress-audit";
|
|
90
93
|
import { authorizeCurrentEgress } from "../core/egress-authorization";
|
|
94
|
+
import { atomicWrite, copyFilePath, createFolderPath } from "../core/file-ops";
|
|
91
95
|
import {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} from "../core/file-ops";
|
|
97
|
-
import {
|
|
96
|
+
applyCanonicalFileRefactor,
|
|
97
|
+
assertFileRefactorSyncConverged,
|
|
98
|
+
buildCanonicalRefactorPlan,
|
|
99
|
+
buildDurableFileRefactorApplyDeps,
|
|
98
100
|
buildRefactorWarnings,
|
|
101
|
+
parseRefactorApplyConfirmation,
|
|
99
102
|
planCreateFolder,
|
|
100
103
|
planDuplicateRefactor,
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
resolveMoveTarget,
|
|
105
|
+
resolveRenameTarget,
|
|
106
|
+
type FileRefactorApplyResult,
|
|
107
|
+
type FileRefactorPreviewPlan,
|
|
103
108
|
} from "../core/file-refactors";
|
|
104
109
|
import { resolveEffectiveIndex } from "../core/indexed-reference";
|
|
105
110
|
import {
|
|
@@ -334,6 +339,52 @@ class GnoClientImpl implements GnoClient {
|
|
|
334
339
|
return filtered;
|
|
335
340
|
}
|
|
336
341
|
|
|
342
|
+
private requireRefactorApplyConfirmation(options: {
|
|
343
|
+
schemaVersion?: unknown;
|
|
344
|
+
planDigest?: unknown;
|
|
345
|
+
confirmation?: unknown;
|
|
346
|
+
}) {
|
|
347
|
+
const parsed = parseRefactorApplyConfirmation(options);
|
|
348
|
+
if ("error" in parsed) {
|
|
349
|
+
throw sdkError("VALIDATION", parsed.message);
|
|
350
|
+
}
|
|
351
|
+
return parsed;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private async prepareRefactorSource(ref: string) {
|
|
355
|
+
const doc = await getDocumentByRef(this.store, this.config, ref, {});
|
|
356
|
+
const stored = await this.store.getDocumentByUri(doc.uri);
|
|
357
|
+
if (!stored.ok || !stored.value) {
|
|
358
|
+
throw sdkError("NOT_FOUND", "Document not found");
|
|
359
|
+
}
|
|
360
|
+
const storedDoc = stored.value;
|
|
361
|
+
const collection = this.getCollections(storedDoc.collection)[0];
|
|
362
|
+
if (!collection) {
|
|
363
|
+
throw sdkError(
|
|
364
|
+
"VALIDATION",
|
|
365
|
+
`Collection not found: ${storedDoc.collection}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const capabilities = getDocumentCapabilities({
|
|
369
|
+
sourceExt: storedDoc.sourceExt,
|
|
370
|
+
sourceMime: storedDoc.sourceMime,
|
|
371
|
+
contentAvailable: storedDoc.mirrorHash !== null,
|
|
372
|
+
recordKey: storedDoc.recordKey,
|
|
373
|
+
});
|
|
374
|
+
if (!capabilities.editable) {
|
|
375
|
+
throw sdkError(
|
|
376
|
+
"VALIDATION",
|
|
377
|
+
capabilities.reason ?? "Document is read-only in place."
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
storedDoc,
|
|
382
|
+
collection,
|
|
383
|
+
sourceFullPath: `${collection.path}/${storedDoc.relPath}`,
|
|
384
|
+
editable: capabilities.editable,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
337
388
|
private async createRuntimePorts(options: {
|
|
338
389
|
embed?: boolean;
|
|
339
390
|
expand?: boolean;
|
|
@@ -1657,125 +1708,126 @@ class GnoClientImpl implements GnoClient {
|
|
|
1657
1708
|
};
|
|
1658
1709
|
}
|
|
1659
1710
|
|
|
1660
|
-
async
|
|
1711
|
+
async previewRenameNote(
|
|
1661
1712
|
options: GnoRenameNoteOptions
|
|
1662
|
-
): Promise<
|
|
1713
|
+
): Promise<FileRefactorPreviewPlan> {
|
|
1663
1714
|
this.assertOpen();
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
{}
|
|
1669
|
-
);
|
|
1670
|
-
const stored = await this.store.getDocumentByUri(doc.uri);
|
|
1671
|
-
if (!stored.ok || !stored.value) {
|
|
1672
|
-
throw sdkError("NOT_FOUND", "Document not found");
|
|
1673
|
-
}
|
|
1674
|
-
const storedDoc = stored.value;
|
|
1675
|
-
const collection = this.getCollections(storedDoc.collection)[0];
|
|
1676
|
-
if (!collection) {
|
|
1677
|
-
throw sdkError(
|
|
1678
|
-
"VALIDATION",
|
|
1679
|
-
`Collection not found: ${storedDoc.collection}`
|
|
1680
|
-
);
|
|
1681
|
-
}
|
|
1682
|
-
const plan = planRenameRefactor({
|
|
1683
|
-
collection: collection.name,
|
|
1684
|
-
currentRelPath: storedDoc.relPath,
|
|
1715
|
+
const prepared = await this.prepareRefactorSource(options.ref);
|
|
1716
|
+
const target = resolveRenameTarget({
|
|
1717
|
+
collection: prepared.collection.name,
|
|
1718
|
+
currentRelPath: prepared.storedDoc.relPath,
|
|
1685
1719
|
nextName: options.name,
|
|
1686
1720
|
});
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1721
|
+
return buildCanonicalRefactorPlan({
|
|
1722
|
+
operation: "rename",
|
|
1723
|
+
doc: prepared.storedDoc,
|
|
1724
|
+
collection: prepared.collection,
|
|
1725
|
+
sourceFullPath: prepared.sourceFullPath,
|
|
1726
|
+
target,
|
|
1727
|
+
store: this.store,
|
|
1728
|
+
sourceEditable: prepared.editable,
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
async previewMoveNote(
|
|
1733
|
+
options: GnoMoveNoteOptions
|
|
1734
|
+
): Promise<FileRefactorPreviewPlan> {
|
|
1735
|
+
this.assertOpen();
|
|
1736
|
+
const prepared = await this.prepareRefactorSource(options.ref);
|
|
1737
|
+
const target = resolveMoveTarget({
|
|
1738
|
+
collection: prepared.collection.name,
|
|
1739
|
+
currentRelPath: prepared.storedDoc.relPath,
|
|
1740
|
+
folderPath: options.folderPath,
|
|
1741
|
+
nextName: options.name,
|
|
1742
|
+
});
|
|
1743
|
+
return buildCanonicalRefactorPlan({
|
|
1744
|
+
operation: "move",
|
|
1745
|
+
doc: prepared.storedDoc,
|
|
1746
|
+
collection: prepared.collection,
|
|
1747
|
+
sourceFullPath: prepared.sourceFullPath,
|
|
1748
|
+
target,
|
|
1749
|
+
store: this.store,
|
|
1750
|
+
sourceEditable: prepared.editable,
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
async renameNote(
|
|
1755
|
+
options: GnoRenameNoteApplyOptions
|
|
1756
|
+
): Promise<FileRefactorApplyResult> {
|
|
1757
|
+
this.assertOpen();
|
|
1758
|
+
const confirmation = this.requireRefactorApplyConfirmation(options);
|
|
1759
|
+
const prepared = await this.prepareRefactorSource(options.ref);
|
|
1760
|
+
const target = resolveRenameTarget({
|
|
1761
|
+
collection: prepared.collection.name,
|
|
1762
|
+
currentRelPath: prepared.storedDoc.relPath,
|
|
1763
|
+
nextName: options.name,
|
|
1764
|
+
});
|
|
1765
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
1766
|
+
operation: "rename",
|
|
1767
|
+
doc: prepared.storedDoc,
|
|
1768
|
+
collection: prepared.collection,
|
|
1769
|
+
sourceFullPath: prepared.sourceFullPath,
|
|
1770
|
+
target,
|
|
1771
|
+
store: this.store,
|
|
1772
|
+
sourceEditable: prepared.editable,
|
|
1773
|
+
});
|
|
1774
|
+
const apply = await applyCanonicalFileRefactor({
|
|
1775
|
+
plan,
|
|
1776
|
+
confirmation,
|
|
1777
|
+
deps: buildDurableFileRefactorApplyDeps({
|
|
1778
|
+
collection: prepared.collection,
|
|
1779
|
+
store: this.store,
|
|
1780
|
+
syncAfterCommit: async () => {
|
|
1781
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
1782
|
+
prepared.collection,
|
|
1783
|
+
this.store,
|
|
1784
|
+
withContentTypeRules({ runUpdateCmd: false }, this.config)
|
|
1785
|
+
);
|
|
1786
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
1713
1787
|
},
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1788
|
+
}),
|
|
1789
|
+
});
|
|
1790
|
+
return apply;
|
|
1717
1791
|
}
|
|
1718
1792
|
|
|
1719
|
-
async moveNote(
|
|
1793
|
+
async moveNote(
|
|
1794
|
+
options: GnoMoveNoteApplyOptions
|
|
1795
|
+
): Promise<FileRefactorApplyResult> {
|
|
1720
1796
|
this.assertOpen();
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
);
|
|
1727
|
-
const stored = await this.store.getDocumentByUri(doc.uri);
|
|
1728
|
-
if (!stored.ok || !stored.value) {
|
|
1729
|
-
throw sdkError("NOT_FOUND", "Document not found");
|
|
1730
|
-
}
|
|
1731
|
-
const storedDoc = stored.value;
|
|
1732
|
-
const collection = this.getCollections(storedDoc.collection)[0];
|
|
1733
|
-
if (!collection) {
|
|
1734
|
-
throw sdkError(
|
|
1735
|
-
"VALIDATION",
|
|
1736
|
-
`Collection not found: ${storedDoc.collection}`
|
|
1737
|
-
);
|
|
1738
|
-
}
|
|
1739
|
-
const plan = planMoveRefactor({
|
|
1740
|
-
collection: collection.name,
|
|
1741
|
-
currentRelPath: storedDoc.relPath,
|
|
1797
|
+
const confirmation = this.requireRefactorApplyConfirmation(options);
|
|
1798
|
+
const prepared = await this.prepareRefactorSource(options.ref);
|
|
1799
|
+
const target = resolveMoveTarget({
|
|
1800
|
+
collection: prepared.collection.name,
|
|
1801
|
+
currentRelPath: prepared.storedDoc.relPath,
|
|
1742
1802
|
folderPath: options.folderPath,
|
|
1743
1803
|
nextName: options.name,
|
|
1744
1804
|
});
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
this.store,
|
|
1752
|
-
|
|
1753
|
-
);
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
(entry) => entry.linkType === "wiki"
|
|
1768
|
-
).length,
|
|
1769
|
-
markdownLinks: linksResult.value.filter(
|
|
1770
|
-
(entry) => entry.linkType === "markdown"
|
|
1771
|
-
).length,
|
|
1805
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
1806
|
+
operation: "move",
|
|
1807
|
+
doc: prepared.storedDoc,
|
|
1808
|
+
collection: prepared.collection,
|
|
1809
|
+
sourceFullPath: prepared.sourceFullPath,
|
|
1810
|
+
target,
|
|
1811
|
+
store: this.store,
|
|
1812
|
+
sourceEditable: prepared.editable,
|
|
1813
|
+
});
|
|
1814
|
+
const apply = await applyCanonicalFileRefactor({
|
|
1815
|
+
plan,
|
|
1816
|
+
confirmation,
|
|
1817
|
+
deps: buildDurableFileRefactorApplyDeps({
|
|
1818
|
+
collection: prepared.collection,
|
|
1819
|
+
store: this.store,
|
|
1820
|
+
syncAfterCommit: async () => {
|
|
1821
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
1822
|
+
prepared.collection,
|
|
1823
|
+
this.store,
|
|
1824
|
+
withContentTypeRules({ runUpdateCmd: false }, this.config)
|
|
1825
|
+
);
|
|
1826
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
1772
1827
|
},
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
}
|
|
1777
|
-
).warnings,
|
|
1778
|
-
};
|
|
1828
|
+
}),
|
|
1829
|
+
});
|
|
1830
|
+
return apply;
|
|
1779
1831
|
}
|
|
1780
1832
|
|
|
1781
1833
|
async duplicateNote(
|
package/src/sdk/index.ts
CHANGED
|
@@ -40,6 +40,9 @@ export type {
|
|
|
40
40
|
GnoClientInitOptions,
|
|
41
41
|
GnoEmbedOptions,
|
|
42
42
|
GnoEmbedResult,
|
|
43
|
+
GnoFileRefactorApplyConfirmation,
|
|
44
|
+
GnoFileRefactorApplyResult,
|
|
45
|
+
GnoFileRefactorPreviewPlan,
|
|
43
46
|
GnoGetOptions,
|
|
44
47
|
GnoGetResult,
|
|
45
48
|
GnoIndexOptions,
|
|
@@ -49,10 +52,14 @@ export type {
|
|
|
49
52
|
GnoListOptions,
|
|
50
53
|
GnoListResult,
|
|
51
54
|
GnoModelOverrides,
|
|
55
|
+
GnoMoveNoteApplyOptions,
|
|
56
|
+
GnoMoveNoteOptions,
|
|
52
57
|
GnoMultiGetDocument,
|
|
53
58
|
GnoMultiGetOptions,
|
|
54
59
|
GnoMultiGetResult,
|
|
55
60
|
GnoQueryOptions,
|
|
61
|
+
GnoRenameNoteApplyOptions,
|
|
62
|
+
GnoRenameNoteOptions,
|
|
56
63
|
GnoProjectHintOptions,
|
|
57
64
|
GnoSearchOptions,
|
|
58
65
|
GnoSkippedDocument,
|
package/src/sdk/types.ts
CHANGED
|
@@ -32,6 +32,10 @@ import type {
|
|
|
32
32
|
EgressAuditShowResult,
|
|
33
33
|
EgressAuditStatusResult,
|
|
34
34
|
} from "../core/egress-audit";
|
|
35
|
+
import type {
|
|
36
|
+
FileRefactorApplyResult,
|
|
37
|
+
FileRefactorPreviewPlan,
|
|
38
|
+
} from "../core/file-refactor-contract";
|
|
35
39
|
import type {
|
|
36
40
|
KnowledgeChangesResult,
|
|
37
41
|
KnowledgeDiffResult,
|
|
@@ -262,6 +266,22 @@ export interface GnoMoveNoteOptions {
|
|
|
262
266
|
name?: string;
|
|
263
267
|
}
|
|
264
268
|
|
|
269
|
+
/** Canonical apply confirmation — preview first, then pass exact digest. */
|
|
270
|
+
export interface GnoFileRefactorApplyConfirmation {
|
|
271
|
+
schemaVersion: "1.0";
|
|
272
|
+
planDigest: string;
|
|
273
|
+
confirmation: "apply";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface GnoRenameNoteApplyOptions
|
|
277
|
+
extends GnoRenameNoteOptions, GnoFileRefactorApplyConfirmation {}
|
|
278
|
+
|
|
279
|
+
export interface GnoMoveNoteApplyOptions
|
|
280
|
+
extends GnoMoveNoteOptions, GnoFileRefactorApplyConfirmation {}
|
|
281
|
+
|
|
282
|
+
export type GnoFileRefactorPreviewPlan = FileRefactorPreviewPlan;
|
|
283
|
+
export type GnoFileRefactorApplyResult = FileRefactorApplyResult;
|
|
284
|
+
|
|
265
285
|
export interface GnoDuplicateNoteOptions {
|
|
266
286
|
ref: string;
|
|
267
287
|
folderPath?: string;
|
|
@@ -350,8 +370,18 @@ export interface GnoClient {
|
|
|
350
370
|
capture(options: GnoCaptureOptions): Promise<GnoCaptureResult>;
|
|
351
371
|
createNote(options: GnoCreateNoteOptions): Promise<GnoCreateNoteResult>;
|
|
352
372
|
createFolder(options: GnoCreateFolderOptions): Promise<GnoCreateFolderResult>;
|
|
353
|
-
|
|
354
|
-
|
|
373
|
+
previewRenameNote(
|
|
374
|
+
options: GnoRenameNoteOptions
|
|
375
|
+
): Promise<GnoFileRefactorPreviewPlan>;
|
|
376
|
+
previewMoveNote(
|
|
377
|
+
options: GnoMoveNoteOptions
|
|
378
|
+
): Promise<GnoFileRefactorPreviewPlan>;
|
|
379
|
+
renameNote(
|
|
380
|
+
options: GnoRenameNoteApplyOptions
|
|
381
|
+
): Promise<GnoFileRefactorApplyResult>;
|
|
382
|
+
moveNote(
|
|
383
|
+
options: GnoMoveNoteApplyOptions
|
|
384
|
+
): Promise<GnoFileRefactorApplyResult>;
|
|
355
385
|
duplicateNote(
|
|
356
386
|
options: GnoDuplicateNoteOptions
|
|
357
387
|
): Promise<GnoRefactorNoteResult>;
|