@gmickel/gno 1.31.0 → 1.32.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 +5 -4
- package/assets/skill/SKILL.md +23 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.zip → gno-browser-clipper-v1.32.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +19 -0
- 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/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/mcp/tools/index.ts +18 -17
- package/src/mcp/tools/workspace-write.ts +215 -97
- 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 +191 -104
- 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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite persistence for content-free file-refactor recovery receipts.
|
|
3
|
+
*
|
|
4
|
+
* @module src/store/sqlite/file-refactor-journal-store
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Database } from "bun:sqlite";
|
|
8
|
+
|
|
9
|
+
import type { FileRefactorOperation } from "../../core/file-refactor-contract";
|
|
10
|
+
import type {
|
|
11
|
+
FileRefactorJournalAdvance,
|
|
12
|
+
FileRefactorJournalFileEntry,
|
|
13
|
+
FileRefactorJournalFilesystemState,
|
|
14
|
+
FileRefactorJournalIndexState,
|
|
15
|
+
FileRefactorJournalPhase,
|
|
16
|
+
FileRefactorRecoveryReceipt,
|
|
17
|
+
FileRefactorRecoveryReceiptDraft,
|
|
18
|
+
} from "../../core/file-refactor-journal";
|
|
19
|
+
import type { StoreResult } from "../types";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
assertAllowedJournalPhaseTransition,
|
|
23
|
+
FILE_REFACTOR_JOURNAL_MAX_RECEIPTS,
|
|
24
|
+
FILE_REFACTOR_JOURNAL_PHASE_ORDINAL,
|
|
25
|
+
parseFileRefactorJournalFileEntries,
|
|
26
|
+
} from "../../core/file-refactor-journal";
|
|
27
|
+
import { err, ok } from "../types";
|
|
28
|
+
|
|
29
|
+
interface DbRow {
|
|
30
|
+
journal_id: string;
|
|
31
|
+
plan_digest: string;
|
|
32
|
+
collection: string;
|
|
33
|
+
operation: FileRefactorOperation;
|
|
34
|
+
source_rel_path: string;
|
|
35
|
+
target_rel_path: string;
|
|
36
|
+
phase: FileRefactorJournalPhase;
|
|
37
|
+
phase_ordinal: number;
|
|
38
|
+
filesystem_state: FileRefactorJournalFilesystemState;
|
|
39
|
+
index_state: FileRefactorJournalIndexState;
|
|
40
|
+
file_entries_json: string;
|
|
41
|
+
created_at_ms: number;
|
|
42
|
+
updated_at_ms: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mapRow = (row: DbRow): FileRefactorRecoveryReceipt => ({
|
|
46
|
+
journalId: row.journal_id,
|
|
47
|
+
planDigest: row.plan_digest,
|
|
48
|
+
collection: row.collection,
|
|
49
|
+
operation: row.operation,
|
|
50
|
+
sourceRelPath: row.source_rel_path,
|
|
51
|
+
targetRelPath: row.target_rel_path,
|
|
52
|
+
phase: row.phase,
|
|
53
|
+
phaseOrdinal: row.phase_ordinal,
|
|
54
|
+
filesystemState: row.filesystem_state,
|
|
55
|
+
indexState: row.index_state,
|
|
56
|
+
fileEntries: parseFileRefactorJournalFileEntries(row.file_entries_json),
|
|
57
|
+
createdAtMs: row.created_at_ms,
|
|
58
|
+
updatedAtMs: row.updated_at_ms,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const assertContentFreeEntries = (
|
|
62
|
+
entries: FileRefactorJournalFileEntry[]
|
|
63
|
+
): void => {
|
|
64
|
+
const serialized = JSON.stringify(entries);
|
|
65
|
+
if (serialized.length > 65_536) {
|
|
66
|
+
throw new RangeError("File refactor journal entries exceed size bound");
|
|
67
|
+
}
|
|
68
|
+
parseFileRefactorJournalFileEntries(entries);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const pruneOldestTerminalReceipts = (db: Database): void => {
|
|
72
|
+
const countRow = db
|
|
73
|
+
.query<{ count: number }, []>(
|
|
74
|
+
`SELECT COUNT(*) AS count FROM file_refactor_recovery_journal`
|
|
75
|
+
)
|
|
76
|
+
.get();
|
|
77
|
+
const count = countRow?.count ?? 0;
|
|
78
|
+
if (count < FILE_REFACTOR_JOURNAL_MAX_RECEIPTS) return;
|
|
79
|
+
|
|
80
|
+
const overflow = count - FILE_REFACTOR_JOURNAL_MAX_RECEIPTS + 1;
|
|
81
|
+
db.run(
|
|
82
|
+
`DELETE FROM file_refactor_recovery_journal
|
|
83
|
+
WHERE journal_id IN (
|
|
84
|
+
SELECT journal_id FROM file_refactor_recovery_journal
|
|
85
|
+
WHERE phase IN ('converged', 'rolled_back', 'aborted')
|
|
86
|
+
ORDER BY updated_at_ms ASC, journal_id ASC
|
|
87
|
+
LIMIT ?
|
|
88
|
+
)`,
|
|
89
|
+
[overflow]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const after = db
|
|
93
|
+
.query<{ count: number }, []>(
|
|
94
|
+
`SELECT COUNT(*) AS count FROM file_refactor_recovery_journal`
|
|
95
|
+
)
|
|
96
|
+
.get();
|
|
97
|
+
if ((after?.count ?? 0) >= FILE_REFACTOR_JOURNAL_MAX_RECEIPTS) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
"File refactor journal at capacity with no prunable terminal receipts"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const createFileRefactorPreparedReceipt = (
|
|
105
|
+
db: Database,
|
|
106
|
+
draft: FileRefactorRecoveryReceiptDraft
|
|
107
|
+
): StoreResult<FileRefactorRecoveryReceipt> => {
|
|
108
|
+
try {
|
|
109
|
+
assertContentFreeEntries(draft.fileEntries);
|
|
110
|
+
pruneOldestTerminalReceipts(db);
|
|
111
|
+
const phase: FileRefactorJournalPhase = "prepared";
|
|
112
|
+
const phaseOrdinal = FILE_REFACTOR_JOURNAL_PHASE_ORDINAL[phase];
|
|
113
|
+
const fileEntriesJson = JSON.stringify(draft.fileEntries);
|
|
114
|
+
db.run(
|
|
115
|
+
`INSERT INTO file_refactor_recovery_journal (
|
|
116
|
+
journal_id, plan_digest, collection, operation,
|
|
117
|
+
source_rel_path, target_rel_path, phase, phase_ordinal,
|
|
118
|
+
filesystem_state, index_state, file_entries_json,
|
|
119
|
+
created_at_ms, updated_at_ms
|
|
120
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'unchanged', 'not_attempted', ?, ?, ?)`,
|
|
121
|
+
[
|
|
122
|
+
draft.journalId,
|
|
123
|
+
draft.planDigest,
|
|
124
|
+
draft.collection,
|
|
125
|
+
draft.operation,
|
|
126
|
+
draft.sourceRelPath,
|
|
127
|
+
draft.targetRelPath,
|
|
128
|
+
phase,
|
|
129
|
+
phaseOrdinal,
|
|
130
|
+
fileEntriesJson,
|
|
131
|
+
draft.createdAtMs,
|
|
132
|
+
draft.createdAtMs,
|
|
133
|
+
]
|
|
134
|
+
);
|
|
135
|
+
const row = db
|
|
136
|
+
.query<DbRow, [string]>(
|
|
137
|
+
`SELECT * FROM file_refactor_recovery_journal WHERE journal_id = ?`
|
|
138
|
+
)
|
|
139
|
+
.get(draft.journalId);
|
|
140
|
+
if (!row) {
|
|
141
|
+
return err("QUERY_FAILED", "Failed to read created refactor receipt");
|
|
142
|
+
}
|
|
143
|
+
return ok(mapRow(row));
|
|
144
|
+
} catch (cause) {
|
|
145
|
+
return err(
|
|
146
|
+
cause instanceof RangeError ||
|
|
147
|
+
(cause instanceof Error && cause.message.includes("Corrupt"))
|
|
148
|
+
? "INVALID_INPUT"
|
|
149
|
+
: "QUERY_FAILED",
|
|
150
|
+
cause instanceof Error
|
|
151
|
+
? cause.message
|
|
152
|
+
: "Failed to create file refactor recovery receipt",
|
|
153
|
+
cause
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const advanceFileRefactorReceipt = (
|
|
159
|
+
db: Database,
|
|
160
|
+
journalId: string,
|
|
161
|
+
update: FileRefactorJournalAdvance
|
|
162
|
+
): StoreResult<FileRefactorRecoveryReceipt> => {
|
|
163
|
+
try {
|
|
164
|
+
const existing = db
|
|
165
|
+
.query<DbRow, [string]>(
|
|
166
|
+
`SELECT * FROM file_refactor_recovery_journal WHERE journal_id = ?`
|
|
167
|
+
)
|
|
168
|
+
.get(journalId);
|
|
169
|
+
if (!existing) {
|
|
170
|
+
return err(
|
|
171
|
+
"NOT_FOUND",
|
|
172
|
+
`Refactor recovery receipt not found: ${journalId}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
assertAllowedJournalPhaseTransition(existing.phase, update.phase);
|
|
176
|
+
if (update.updatedAtMs < existing.updated_at_ms) {
|
|
177
|
+
return err("INVALID_INPUT", "Journal updatedAtMs must be monotonic");
|
|
178
|
+
}
|
|
179
|
+
const fileEntries =
|
|
180
|
+
update.fileEntries ??
|
|
181
|
+
parseFileRefactorJournalFileEntries(existing.file_entries_json);
|
|
182
|
+
assertContentFreeEntries(fileEntries);
|
|
183
|
+
const phaseOrdinal = FILE_REFACTOR_JOURNAL_PHASE_ORDINAL[update.phase];
|
|
184
|
+
const filesystemState = update.filesystemState ?? existing.filesystem_state;
|
|
185
|
+
const indexState = update.indexState ?? existing.index_state;
|
|
186
|
+
db.run(
|
|
187
|
+
`UPDATE file_refactor_recovery_journal
|
|
188
|
+
SET phase = ?,
|
|
189
|
+
phase_ordinal = ?,
|
|
190
|
+
filesystem_state = ?,
|
|
191
|
+
index_state = ?,
|
|
192
|
+
file_entries_json = ?,
|
|
193
|
+
updated_at_ms = ?
|
|
194
|
+
WHERE journal_id = ?`,
|
|
195
|
+
[
|
|
196
|
+
update.phase,
|
|
197
|
+
phaseOrdinal,
|
|
198
|
+
filesystemState,
|
|
199
|
+
indexState,
|
|
200
|
+
JSON.stringify(fileEntries),
|
|
201
|
+
update.updatedAtMs,
|
|
202
|
+
journalId,
|
|
203
|
+
]
|
|
204
|
+
);
|
|
205
|
+
const row = db
|
|
206
|
+
.query<DbRow, [string]>(
|
|
207
|
+
`SELECT * FROM file_refactor_recovery_journal WHERE journal_id = ?`
|
|
208
|
+
)
|
|
209
|
+
.get(journalId);
|
|
210
|
+
if (!row) {
|
|
211
|
+
return err("QUERY_FAILED", "Failed to read updated refactor receipt");
|
|
212
|
+
}
|
|
213
|
+
return ok(mapRow(row));
|
|
214
|
+
} catch (cause) {
|
|
215
|
+
return err(
|
|
216
|
+
cause instanceof RangeError ||
|
|
217
|
+
(cause instanceof Error &&
|
|
218
|
+
(cause.message.includes("Corrupt") ||
|
|
219
|
+
cause.message.includes("Invalid journal phase")))
|
|
220
|
+
? "INVALID_INPUT"
|
|
221
|
+
: "QUERY_FAILED",
|
|
222
|
+
cause instanceof Error
|
|
223
|
+
? cause.message
|
|
224
|
+
: "Failed to advance file refactor recovery receipt",
|
|
225
|
+
cause
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const getFileRefactorReceiptById = (
|
|
231
|
+
db: Database,
|
|
232
|
+
journalId: string
|
|
233
|
+
): StoreResult<FileRefactorRecoveryReceipt | null> => {
|
|
234
|
+
try {
|
|
235
|
+
const row = db
|
|
236
|
+
.query<DbRow, [string]>(
|
|
237
|
+
`SELECT * FROM file_refactor_recovery_journal WHERE journal_id = ?`
|
|
238
|
+
)
|
|
239
|
+
.get(journalId);
|
|
240
|
+
return ok(row ? mapRow(row) : null);
|
|
241
|
+
} catch (cause) {
|
|
242
|
+
return err(
|
|
243
|
+
"QUERY_FAILED",
|
|
244
|
+
cause instanceof Error
|
|
245
|
+
? cause.message
|
|
246
|
+
: "Failed to load file refactor recovery receipt",
|
|
247
|
+
cause
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const getLatestFileRefactorReceiptByPlanDigest = (
|
|
253
|
+
db: Database,
|
|
254
|
+
planDigest: string
|
|
255
|
+
): StoreResult<FileRefactorRecoveryReceipt | null> => {
|
|
256
|
+
try {
|
|
257
|
+
const row = db
|
|
258
|
+
.query<DbRow, [string]>(
|
|
259
|
+
`SELECT * FROM file_refactor_recovery_journal
|
|
260
|
+
WHERE plan_digest = ?
|
|
261
|
+
ORDER BY updated_at_ms DESC, journal_id DESC
|
|
262
|
+
LIMIT 1`
|
|
263
|
+
)
|
|
264
|
+
.get(planDigest);
|
|
265
|
+
return ok(row ? mapRow(row) : null);
|
|
266
|
+
} catch (cause) {
|
|
267
|
+
return err(
|
|
268
|
+
"QUERY_FAILED",
|
|
269
|
+
cause instanceof Error
|
|
270
|
+
? cause.message
|
|
271
|
+
: "Failed to lookup file refactor recovery receipt",
|
|
272
|
+
cause
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
};
|
package/src/store/types.ts
CHANGED
|
@@ -14,6 +14,11 @@ import type {
|
|
|
14
14
|
} from "../config/types";
|
|
15
15
|
import type { RecordAnchor, RecordMetadata } from "../converters/types";
|
|
16
16
|
import type { EgressLineage } from "../core/egress-provenance";
|
|
17
|
+
import type {
|
|
18
|
+
FileRefactorJournalAdvance,
|
|
19
|
+
FileRefactorRecoveryReceipt,
|
|
20
|
+
FileRefactorRecoveryReceiptDraft,
|
|
21
|
+
} from "../core/file-refactor-journal";
|
|
17
22
|
|
|
18
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
24
|
// Error Types
|
|
@@ -249,6 +254,52 @@ export interface DocLinkRow {
|
|
|
249
254
|
source: DocLinkSource;
|
|
250
255
|
}
|
|
251
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Bounded read snapshot for reference-safe rename/move planning.
|
|
259
|
+
* Content covers indexed backlinks unioned with a conservative content
|
|
260
|
+
* prefilter (opaque embeds/HTML/code/malformed may not be in doc_links).
|
|
261
|
+
* Never logged by callers.
|
|
262
|
+
*/
|
|
263
|
+
export interface FileRefactorResolutionCatalogDocument {
|
|
264
|
+
id: number;
|
|
265
|
+
uri: string;
|
|
266
|
+
relPath: string;
|
|
267
|
+
collection: string;
|
|
268
|
+
title: string | null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface FileRefactorResolutionReferrerDocument extends FileRefactorResolutionCatalogDocument {
|
|
272
|
+
/** Markdown body when available; null when mirror content is missing. */
|
|
273
|
+
content: string | null;
|
|
274
|
+
contentTruncated: boolean;
|
|
275
|
+
/** True when a potentially relevant document had no loadable mirror. */
|
|
276
|
+
contentMissing: boolean;
|
|
277
|
+
editable: boolean;
|
|
278
|
+
editableReason?: string;
|
|
279
|
+
sourceExt: string;
|
|
280
|
+
sourceMime: string;
|
|
281
|
+
recordKey: string | null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface FileRefactorResolutionSnapshot {
|
|
285
|
+
source: FileRefactorResolutionCatalogDocument & {
|
|
286
|
+
mirrorHash: string | null;
|
|
287
|
+
sourceExt: string;
|
|
288
|
+
sourceMime: string;
|
|
289
|
+
recordKey: string | null;
|
|
290
|
+
content: string | null;
|
|
291
|
+
contentTruncated: boolean;
|
|
292
|
+
editable: boolean;
|
|
293
|
+
editableReason?: string;
|
|
294
|
+
};
|
|
295
|
+
catalog: FileRefactorResolutionCatalogDocument[];
|
|
296
|
+
referrers: FileRefactorResolutionReferrerDocument[];
|
|
297
|
+
occupiedRelPaths: string[];
|
|
298
|
+
truncated: boolean;
|
|
299
|
+
/** Content-free truncation reason codes. */
|
|
300
|
+
truncationReasons: string[];
|
|
301
|
+
}
|
|
302
|
+
|
|
252
303
|
/** Semantic document edge row from DB. */
|
|
253
304
|
export interface DocEdgeRow {
|
|
254
305
|
sourceDocId: number;
|
|
@@ -1710,6 +1761,27 @@ export interface StorePort {
|
|
|
1710
1761
|
/** Purge the journal while retaining a cursor-expiry boundary. */
|
|
1711
1762
|
purgeDocumentChanges(): Promise<StoreResult<DocumentChangePurgeResult>>;
|
|
1712
1763
|
|
|
1764
|
+
/** Create a content-free prepared file-refactor recovery receipt. */
|
|
1765
|
+
createFileRefactorPreparedReceipt(
|
|
1766
|
+
draft: FileRefactorRecoveryReceiptDraft
|
|
1767
|
+
): Promise<StoreResult<FileRefactorRecoveryReceipt>>;
|
|
1768
|
+
|
|
1769
|
+
/** Advance phase/state on a file-refactor recovery receipt. */
|
|
1770
|
+
advanceFileRefactorReceipt(
|
|
1771
|
+
journalId: string,
|
|
1772
|
+
update: FileRefactorJournalAdvance
|
|
1773
|
+
): Promise<StoreResult<FileRefactorRecoveryReceipt>>;
|
|
1774
|
+
|
|
1775
|
+
/** Load one file-refactor recovery receipt by journal id. */
|
|
1776
|
+
getFileRefactorReceiptById(
|
|
1777
|
+
journalId: string
|
|
1778
|
+
): Promise<StoreResult<FileRefactorRecoveryReceipt | null>>;
|
|
1779
|
+
|
|
1780
|
+
/** Latest receipt for a plan digest (deterministic retry/recovery lookup). */
|
|
1781
|
+
getLatestFileRefactorReceiptByPlanDigest(
|
|
1782
|
+
planDigest: string
|
|
1783
|
+
): Promise<StoreResult<FileRefactorRecoveryReceipt | null>>;
|
|
1784
|
+
|
|
1713
1785
|
/** Register one user-owned Capsule file and its metadata-only evidence refs. */
|
|
1714
1786
|
upsertSavedCapsuleRegistration(
|
|
1715
1787
|
input: SavedCapsuleRegistrationInput
|
|
@@ -1946,6 +2018,18 @@ export interface StorePort {
|
|
|
1946
2018
|
>
|
|
1947
2019
|
>;
|
|
1948
2020
|
|
|
2021
|
+
/**
|
|
2022
|
+
* Bounded resolution/inventory snapshot for reference-safe rename/move planning.
|
|
2023
|
+
* Read-only: never mutates documents, links, or content.
|
|
2024
|
+
*/
|
|
2025
|
+
getFileRefactorResolutionSnapshot?(input: {
|
|
2026
|
+
sourceUri: string;
|
|
2027
|
+
maxCatalogDocuments?: number;
|
|
2028
|
+
maxReferrerDocuments?: number;
|
|
2029
|
+
maxContentCharsPerDocument?: number;
|
|
2030
|
+
maxTotalContentChars?: number;
|
|
2031
|
+
}): Promise<StoreResult<FileRefactorResolutionSnapshot>>;
|
|
2032
|
+
|
|
1949
2033
|
/**
|
|
1950
2034
|
* Set semantic edges for a document.
|
|
1951
2035
|
* Replaces edges from the given source.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
01281ef03fe26195c824909c53eed8d04ede0f9a2da72f3c4c19d7e1564e0a0c gno-browser-clipper-v1.31.0.zip
|