@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.
Files changed (53) hide show
  1. package/README.md +5 -4
  2. package/assets/skill/SKILL.md +23 -0
  3. package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.zip → gno-browser-clipper-v1.32.0.zip} +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +1 -0
  5. package/browser-extension/dist/manifest.json +1 -1
  6. package/package.json +1 -1
  7. package/spec/cli.md +19 -0
  8. package/spec/db/schema.sql +55 -0
  9. package/spec/mcp.md +114 -11
  10. package/spec/output-schemas/file-refactor-apply-result.schema.json +305 -0
  11. package/spec/output-schemas/file-refactor-preview.schema.json +393 -0
  12. package/src/core/document-capabilities.ts +13 -0
  13. package/src/core/file-ops.ts +129 -1
  14. package/src/core/file-refactor-adapter.ts +329 -0
  15. package/src/core/file-refactor-apply-edits.ts +61 -0
  16. package/src/core/file-refactor-apply-fs.ts +512 -0
  17. package/src/core/file-refactor-apply-safety.ts +340 -0
  18. package/src/core/file-refactor-apply-validate.ts +401 -0
  19. package/src/core/file-refactor-contract.ts +486 -0
  20. package/src/core/file-refactor-destination.ts +123 -0
  21. package/src/core/file-refactor-from-snapshot.ts +148 -0
  22. package/src/core/file-refactor-journal-port.ts +150 -0
  23. package/src/core/file-refactor-journal.ts +347 -0
  24. package/src/core/file-refactor-paths.ts +60 -0
  25. package/src/core/file-refactor-plan-classify.ts +208 -0
  26. package/src/core/file-refactor-plan-validate.ts +169 -0
  27. package/src/core/file-refactor-planner-types.ts +62 -0
  28. package/src/core/file-refactor-planner.ts +423 -0
  29. package/src/core/file-refactor-resolve.ts +280 -0
  30. package/src/core/file-refactor-service.ts +468 -0
  31. package/src/core/file-refactors.ts +84 -56
  32. package/src/core/link-destination-parse.ts +275 -0
  33. package/src/core/link-inventory-markdown.ts +454 -0
  34. package/src/core/link-inventory-opaque.ts +244 -0
  35. package/src/core/link-inventory-types.ts +47 -0
  36. package/src/core/link-inventory.ts +182 -0
  37. package/src/core/link-relevance.ts +150 -0
  38. package/src/mcp/tools/index.ts +18 -17
  39. package/src/mcp/tools/workspace-write.ts +215 -97
  40. package/src/sdk/client.ts +167 -115
  41. package/src/sdk/index.ts +7 -0
  42. package/src/sdk/types.ts +32 -2
  43. package/src/serve/file-refactor-http.ts +239 -0
  44. package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
  45. package/src/serve/public/globals.built.css +1 -1
  46. package/src/serve/public/pages/DocView.tsx +176 -41
  47. package/src/serve/routes/api.ts +191 -104
  48. package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
  49. package/src/store/migrations/index.ts +2 -0
  50. package/src/store/sqlite/adapter.ts +452 -0
  51. package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
  52. package/src/store/types.ts +84 -0
  53. package/browser-extension/artifacts/gno-browser-clipper-v1.31.0.zip.sha256 +0 -1
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Canonical refactor impact preview for rename/move dialogs.
3
+ * Scholarly Dusk vocabulary: rail-style labels, mono paths, amber caution.
4
+ *
5
+ * @module src/serve/public/components/RefactorImpactPreview
6
+ */
7
+
8
+ import type { FileRefactorPreviewPlan } from "../../../core/file-refactor-contract";
9
+
10
+ const UNRESOLVED_CLASSIFICATIONS = new Set([
11
+ "ambiguous",
12
+ "unsupported",
13
+ "malformed",
14
+ "invalid",
15
+ ]);
16
+
17
+ function shortDigest(digest: string): string {
18
+ return `${digest.slice(0, 8)}…${digest.slice(-6)}`;
19
+ }
20
+
21
+ export interface RefactorImpactPreviewProps {
22
+ plan: FileRefactorPreviewPlan | null;
23
+ loading?: boolean;
24
+ confirmed: boolean;
25
+ onConfirmedChange: (confirmed: boolean) => void;
26
+ /** Accessible outcome message after apply (sync-pending, recovery, etc.). */
27
+ outcomeMessage?: string | null;
28
+ outcomeTone?: "warning" | "error" | "success";
29
+ }
30
+
31
+ export function RefactorImpactPreview({
32
+ plan,
33
+ loading = false,
34
+ confirmed,
35
+ onConfirmedChange,
36
+ outcomeMessage,
37
+ outcomeTone = "warning",
38
+ }: RefactorImpactPreviewProps) {
39
+ if (loading && !plan) {
40
+ return (
41
+ <div
42
+ aria-busy="true"
43
+ aria-live="polite"
44
+ className="rounded-md border border-border/30 bg-muted/20 px-3 py-2 font-mono text-[11px] text-muted-foreground"
45
+ >
46
+ Computing reference impact…
47
+ </div>
48
+ );
49
+ }
50
+
51
+ if (!plan) {
52
+ return null;
53
+ }
54
+
55
+ const rewriteable = plan.examinedReferences.filter(
56
+ (ref) => ref.classification === "rewriteable"
57
+ );
58
+ const unresolved = plan.examinedReferences.filter((ref) =>
59
+ UNRESOLVED_CLASSIFICATIONS.has(ref.classification)
60
+ );
61
+ const affectedPaths = plan.affectedDocuments.map((doc) => doc.relPath);
62
+ const confirmId = `refactor-confirm-${plan.planDigest.slice(0, 12)}`;
63
+
64
+ const outcomeClass =
65
+ outcomeTone === "error"
66
+ ? "border-destructive/40 bg-destructive/10 text-destructive"
67
+ : outcomeTone === "success"
68
+ ? "border-primary/30 bg-primary/10 text-primary"
69
+ : "border-amber-500/30 bg-amber-500/10 text-amber-500";
70
+
71
+ return (
72
+ <div className="max-h-[40vh] space-y-3 overflow-y-auto pr-1">
73
+ <div className="space-y-1">
74
+ <div className="font-mono text-[10px] text-muted-foreground/60 uppercase tracking-[0.15em]">
75
+ Safety
76
+ </div>
77
+ <div
78
+ aria-live="polite"
79
+ className={
80
+ plan.canApply
81
+ ? "rounded-md border border-primary/25 bg-primary/10 px-3 py-2 text-[13px] text-primary"
82
+ : "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[13px] text-destructive"
83
+ }
84
+ role="status"
85
+ >
86
+ {plan.canApply
87
+ ? "Plan is safe to apply with the exact digest below."
88
+ : `Cannot apply: ${(plan.safety.blockingReasonCodes[0] ?? "blocked").replaceAll("_", " ")}.`}
89
+ </div>
90
+ <p className="font-mono text-[11px] text-muted-foreground/70 break-all">
91
+ Digest {shortDigest(plan.planDigest)}
92
+ </p>
93
+ </div>
94
+
95
+ <div className="space-y-1">
96
+ <div className="font-mono text-[10px] text-muted-foreground/60 uppercase tracking-[0.15em]">
97
+ Target
98
+ </div>
99
+ <p className="font-mono text-[11px] text-muted-foreground/80 break-all">
100
+ {plan.source.relPath} → {plan.target.relPath}
101
+ </p>
102
+ <p className="font-mono text-[10px] text-muted-foreground/50 tabular-nums">
103
+ {plan.safety.rewriteableCount} rewrite
104
+ {plan.safety.rewriteableCount === 1 ? "" : "s"} ·{" "}
105
+ {plan.safety.unchangedCount} unchanged · {unresolved.length}{" "}
106
+ unresolved
107
+ </p>
108
+ </div>
109
+
110
+ {affectedPaths.length > 0 && (
111
+ <div className="space-y-1">
112
+ <div className="font-mono text-[10px] text-muted-foreground/60 uppercase tracking-[0.15em]">
113
+ Affected documents
114
+ </div>
115
+ <ul className="space-y-0.5">
116
+ {affectedPaths.map((path) => (
117
+ <li
118
+ className="font-mono text-[11px] text-muted-foreground/80 break-all"
119
+ key={path}
120
+ >
121
+ {path}
122
+ </li>
123
+ ))}
124
+ </ul>
125
+ </div>
126
+ )}
127
+
128
+ {rewriteable.length > 0 && (
129
+ <div className="space-y-1">
130
+ <div className="font-mono text-[10px] text-muted-foreground/60 uppercase tracking-[0.15em]">
131
+ Rewrites
132
+ </div>
133
+ <ul className="space-y-1">
134
+ {rewriteable.slice(0, 12).map((ref) => (
135
+ <li
136
+ className="rounded-sm px-2 py-1 font-mono text-[11px] text-muted-foreground hover:bg-muted/20"
137
+ key={`${ref.documentRelPath}:${ref.startLine ?? 0}:${ref.startCol ?? 0}:${ref.originalDestination ?? ""}`}
138
+ >
139
+ <span className="text-foreground/90">
140
+ {ref.documentRelPath}
141
+ </span>
142
+ <span className="text-muted-foreground/50"> · </span>
143
+ <span>
144
+ {ref.originalDestination ?? "?"} →{" "}
145
+ {ref.proposedDestination ?? "?"}
146
+ </span>
147
+ </li>
148
+ ))}
149
+ {rewriteable.length > 12 && (
150
+ <li className="font-mono text-[10px] text-muted-foreground/50">
151
+ +{rewriteable.length - 12} more
152
+ </li>
153
+ )}
154
+ </ul>
155
+ </div>
156
+ )}
157
+
158
+ {unresolved.length > 0 && (
159
+ <div className="space-y-1">
160
+ <div className="font-mono text-[10px] text-amber-500/70 uppercase tracking-[0.15em]">
161
+ Unresolved / unsupported
162
+ </div>
163
+ <ul className="space-y-1">
164
+ {unresolved.slice(0, 12).map((ref) => (
165
+ <li
166
+ className="rounded-sm border border-amber-500/20 bg-amber-500/5 px-2 py-1 font-mono text-[11px] text-amber-500/90"
167
+ key={`${ref.documentRelPath}:${ref.classification}:${ref.startLine ?? 0}:${ref.originalDestination ?? ""}`}
168
+ >
169
+ <span>
170
+ {ref.documentRelPath}
171
+ {ref.startLine ? `:${ref.startLine}` : ""}
172
+ </span>
173
+ <span className="opacity-60"> · {ref.classification}</span>
174
+ {ref.reasonCode && (
175
+ <span className="opacity-60"> · {ref.reasonCode}</span>
176
+ )}
177
+ </li>
178
+ ))}
179
+ {unresolved.length > 12 && (
180
+ <li className="font-mono text-[10px] text-amber-500/60">
181
+ +{unresolved.length - 12} more
182
+ </li>
183
+ )}
184
+ </ul>
185
+ </div>
186
+ )}
187
+
188
+ {plan.safety.warnings.length > 0 && (
189
+ <div
190
+ className="rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-amber-500 text-sm"
191
+ role="status"
192
+ >
193
+ {plan.safety.warnings.map((warning) => (
194
+ <div key={warning}>{warning}</div>
195
+ ))}
196
+ </div>
197
+ )}
198
+
199
+ <div className="flex items-start gap-2 rounded-md border border-border/30 bg-muted/15 px-3 py-2">
200
+ <input
201
+ checked={confirmed}
202
+ className="mt-0.5 size-4 accent-primary"
203
+ disabled={!plan.canApply}
204
+ id={confirmId}
205
+ onChange={(event) => onConfirmedChange(event.target.checked)}
206
+ type="checkbox"
207
+ />
208
+ <label className="text-[13px] leading-snug" htmlFor={confirmId}>
209
+ Confirm exact plan digest{" "}
210
+ <span className="font-mono text-[11px] text-muted-foreground">
211
+ {shortDigest(plan.planDigest)}
212
+ </span>
213
+ </label>
214
+ </div>
215
+
216
+ {outcomeMessage && (
217
+ <div
218
+ aria-live="assertive"
219
+ className={`rounded-md border px-3 py-2 text-sm ${outcomeClass}`}
220
+ role="alert"
221
+ >
222
+ {outcomeMessage}
223
+ </div>
224
+ )}
225
+ </div>
226
+ );
227
+ }