@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,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
|
+
}
|