@gmickel/gno 1.30.7 → 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.
Files changed (71) hide show
  1. package/README.md +6 -5
  2. package/assets/skill/SKILL.md +25 -0
  3. package/assets/skill/mcp-reference.md +6 -0
  4. package/browser-extension/artifacts/{gno-browser-clipper-v1.30.7.zip → gno-browser-clipper-v1.32.0.zip} +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +1 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/package.json +1 -1
  8. package/spec/cli.md +19 -0
  9. package/spec/db/schema.sql +55 -0
  10. package/spec/mcp.md +176 -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/section-target-create-result.schema.json +20 -0
  14. package/spec/output-schemas/section-target-resolve-result.schema.json +194 -0
  15. package/spec/output-schemas/section-target.schema.json +118 -0
  16. package/spec/output-schemas/section.schema.json +113 -0
  17. package/src/core/document-capabilities.ts +13 -0
  18. package/src/core/file-ops.ts +129 -1
  19. package/src/core/file-refactor-adapter.ts +329 -0
  20. package/src/core/file-refactor-apply-edits.ts +61 -0
  21. package/src/core/file-refactor-apply-fs.ts +512 -0
  22. package/src/core/file-refactor-apply-safety.ts +340 -0
  23. package/src/core/file-refactor-apply-validate.ts +401 -0
  24. package/src/core/file-refactor-contract.ts +486 -0
  25. package/src/core/file-refactor-destination.ts +123 -0
  26. package/src/core/file-refactor-from-snapshot.ts +148 -0
  27. package/src/core/file-refactor-journal-port.ts +150 -0
  28. package/src/core/file-refactor-journal.ts +347 -0
  29. package/src/core/file-refactor-paths.ts +60 -0
  30. package/src/core/file-refactor-plan-classify.ts +208 -0
  31. package/src/core/file-refactor-plan-validate.ts +169 -0
  32. package/src/core/file-refactor-planner-types.ts +62 -0
  33. package/src/core/file-refactor-planner.ts +423 -0
  34. package/src/core/file-refactor-resolve.ts +280 -0
  35. package/src/core/file-refactor-service.ts +468 -0
  36. package/src/core/file-refactors.ts +84 -56
  37. package/src/core/link-destination-parse.ts +275 -0
  38. package/src/core/link-inventory-markdown.ts +454 -0
  39. package/src/core/link-inventory-opaque.ts +244 -0
  40. package/src/core/link-inventory-types.ts +47 -0
  41. package/src/core/link-inventory.ts +182 -0
  42. package/src/core/link-relevance.ts +150 -0
  43. package/src/core/section-parse.ts +187 -0
  44. package/src/core/section-target-link.ts +154 -0
  45. package/src/core/section-target-resolve.ts +351 -0
  46. package/src/core/section-target-transport.ts +519 -0
  47. package/src/core/section-target.ts +263 -0
  48. package/src/core/sections.ts +60 -115
  49. package/src/mcp/AGENTS.md +1 -0
  50. package/src/mcp/CLAUDE.md +1 -0
  51. package/src/mcp/http-egress.ts +1 -0
  52. package/src/mcp/tools/index.ts +37 -17
  53. package/src/mcp/tools/sections.ts +512 -0
  54. package/src/mcp/tools/workspace-write.ts +215 -97
  55. package/src/sdk/client.ts +238 -116
  56. package/src/sdk/index.ts +12 -0
  57. package/src/sdk/types.ts +61 -3
  58. package/src/serve/file-refactor-http.ts +239 -0
  59. package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
  60. package/src/serve/public/globals.built.css +1 -1
  61. package/src/serve/public/lib/section-links.ts +189 -0
  62. package/src/serve/public/pages/DocView.tsx +395 -77
  63. package/src/serve/routes/api.ts +191 -104
  64. package/src/serve/routes/section-targets.ts +221 -0
  65. package/src/serve/server.ts +34 -0
  66. package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
  67. package/src/store/migrations/index.ts +2 -0
  68. package/src/store/sqlite/adapter.ts +452 -0
  69. package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
  70. package/src/store/types.ts +84 -0
  71. package/browser-extension/artifacts/gno-browser-clipper-v1.30.7.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
+ };
@@ -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
- c8fbd761eeb10076ef5f05d917d7815000415fc869973341170cb45874e95133 gno-browser-clipper-v1.30.7.zip