@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.
- package/README.md +6 -5
- package/assets/skill/SKILL.md +25 -0
- package/assets/skill/mcp-reference.md +6 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.30.7.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 +176 -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/section-target-create-result.schema.json +20 -0
- package/spec/output-schemas/section-target-resolve-result.schema.json +194 -0
- package/spec/output-schemas/section-target.schema.json +118 -0
- package/spec/output-schemas/section.schema.json +113 -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/core/section-parse.ts +187 -0
- package/src/core/section-target-link.ts +154 -0
- package/src/core/section-target-resolve.ts +351 -0
- package/src/core/section-target-transport.ts +519 -0
- package/src/core/section-target.ts +263 -0
- package/src/core/sections.ts +60 -115
- package/src/mcp/AGENTS.md +1 -0
- package/src/mcp/CLAUDE.md +1 -0
- package/src/mcp/http-egress.ts +1 -0
- package/src/mcp/tools/index.ts +37 -17
- package/src/mcp/tools/sections.ts +512 -0
- package/src/mcp/tools/workspace-write.ts +215 -97
- package/src/sdk/client.ts +238 -116
- package/src/sdk/index.ts +12 -0
- package/src/sdk/types.ts +61 -3
- 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/lib/section-links.ts +189 -0
- package/src/serve/public/pages/DocView.tsx +395 -77
- package/src/serve/routes/api.ts +191 -104
- package/src/serve/routes/section-targets.ts +221 -0
- package/src/serve/server.ts +34 -0
- 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.30.7.zip.sha256 +0 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { mkdir } from "node:fs/promises";
|
|
9
9
|
// node:path for dirname/join (no Bun path utils)
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
|
+
import { z } from "zod";
|
|
11
12
|
|
|
12
13
|
import type { Collection } from "../../config/types";
|
|
13
14
|
import type { ToolContext } from "../server";
|
|
@@ -15,17 +16,22 @@ import type { ToolContext } from "../server";
|
|
|
15
16
|
import { getDocumentCapabilities } from "../../core/document-capabilities";
|
|
16
17
|
import { MCP_ERRORS } from "../../core/errors";
|
|
17
18
|
import { withWriteLock } from "../../core/file-lock";
|
|
19
|
+
import { copyFilePath, createFolderPath } from "../../core/file-ops";
|
|
18
20
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
21
|
+
applyCanonicalFileRefactor,
|
|
22
|
+
assertFileRefactorSyncConverged,
|
|
23
|
+
buildCanonicalRefactorPlan,
|
|
24
|
+
buildDurableFileRefactorApplyDeps,
|
|
24
25
|
buildRefactorWarnings,
|
|
26
|
+
FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
27
|
+
FILE_REFACTOR_SCHEMA_VERSION,
|
|
28
|
+
parseRefactorApplyConfirmation,
|
|
25
29
|
planCreateFolder,
|
|
26
30
|
planDuplicateRefactor,
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
resolveMoveTarget,
|
|
32
|
+
resolveRenameTarget,
|
|
33
|
+
type FileRefactorApplyResult,
|
|
34
|
+
type FileRefactorPreviewPlan,
|
|
29
35
|
} from "../../core/file-refactors";
|
|
30
36
|
import { recordContentMutation } from "../../core/mutation-generations";
|
|
31
37
|
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
@@ -37,23 +43,72 @@ interface CreateFolderInput {
|
|
|
37
43
|
parentPath?: string;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
interface RenameNoteInput {
|
|
41
|
-
ref: string;
|
|
42
|
-
name: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface MoveNoteInput {
|
|
46
|
-
ref: string;
|
|
47
|
-
folderPath: string;
|
|
48
|
-
name?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
46
|
interface DuplicateNoteInput {
|
|
52
47
|
ref: string;
|
|
53
48
|
folderPath?: string;
|
|
54
49
|
name?: string;
|
|
55
50
|
}
|
|
56
51
|
|
|
52
|
+
const refactorPreviewActionSchema = z.literal("preview");
|
|
53
|
+
const refactorApplyActionSchema = z.literal("apply");
|
|
54
|
+
|
|
55
|
+
export const renameNoteInputSchema = z.discriminatedUnion("action", [
|
|
56
|
+
z.object({
|
|
57
|
+
action: refactorPreviewActionSchema,
|
|
58
|
+
ref: z.string().min(1, "ref cannot be empty"),
|
|
59
|
+
name: z.string().min(1, "name cannot be empty"),
|
|
60
|
+
}),
|
|
61
|
+
z.object({
|
|
62
|
+
action: refactorApplyActionSchema,
|
|
63
|
+
ref: z.string().min(1, "ref cannot be empty"),
|
|
64
|
+
name: z.string().min(1, "name cannot be empty"),
|
|
65
|
+
schemaVersion: z.literal(FILE_REFACTOR_SCHEMA_VERSION),
|
|
66
|
+
planDigest: z
|
|
67
|
+
.string()
|
|
68
|
+
.regex(/^[a-f0-9]{64}$/, "planDigest must be a lowercase SHA-256"),
|
|
69
|
+
confirmation: z.literal(FILE_REFACTOR_APPLY_CONFIRMATION),
|
|
70
|
+
confirm: z.literal(true),
|
|
71
|
+
}),
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
export const moveNoteInputSchema = z.discriminatedUnion("action", [
|
|
75
|
+
z.object({
|
|
76
|
+
action: refactorPreviewActionSchema,
|
|
77
|
+
ref: z.string().min(1, "ref cannot be empty"),
|
|
78
|
+
folderPath: z.string().min(1, "folderPath cannot be empty"),
|
|
79
|
+
name: z.string().optional(),
|
|
80
|
+
}),
|
|
81
|
+
z.object({
|
|
82
|
+
action: refactorApplyActionSchema,
|
|
83
|
+
ref: z.string().min(1, "ref cannot be empty"),
|
|
84
|
+
folderPath: z.string().min(1, "folderPath cannot be empty"),
|
|
85
|
+
name: z.string().optional(),
|
|
86
|
+
schemaVersion: z.literal(FILE_REFACTOR_SCHEMA_VERSION),
|
|
87
|
+
planDigest: z
|
|
88
|
+
.string()
|
|
89
|
+
.regex(/^[a-f0-9]{64}$/, "planDigest must be a lowercase SHA-256"),
|
|
90
|
+
confirmation: z.literal(FILE_REFACTOR_APPLY_CONFIRMATION),
|
|
91
|
+
confirm: z.literal(true),
|
|
92
|
+
}),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
export type RenameNoteInput = z.infer<typeof renameNoteInputSchema>;
|
|
96
|
+
export type MoveNoteInput = z.infer<typeof moveNoteInputSchema>;
|
|
97
|
+
|
|
98
|
+
export const RENAME_NOTE_MCP_ANNOTATIONS = {
|
|
99
|
+
readOnlyHint: false,
|
|
100
|
+
destructiveHint: true,
|
|
101
|
+
idempotentHint: false,
|
|
102
|
+
openWorldHint: false,
|
|
103
|
+
} as const;
|
|
104
|
+
|
|
105
|
+
export const MOVE_NOTE_MCP_ANNOTATIONS = {
|
|
106
|
+
readOnlyHint: false,
|
|
107
|
+
destructiveHint: true,
|
|
108
|
+
idempotentHint: false,
|
|
109
|
+
openWorldHint: false,
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
57
112
|
function resolveCollection(ctx: ToolContext, name: string): Collection {
|
|
58
113
|
const normalized = name.trim().toLowerCase();
|
|
59
114
|
const collection = ctx.collections.find((entry) => entry.name === normalized);
|
|
@@ -156,6 +211,136 @@ function ensureEditable(doc: {
|
|
|
156
211
|
}`
|
|
157
212
|
);
|
|
158
213
|
}
|
|
214
|
+
return capabilities;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function requireWriteEnabled(ctx: ToolContext): void {
|
|
218
|
+
if (!ctx.enableWrite) {
|
|
219
|
+
throw new Error("Write tools disabled. Start MCP with --enable-write.");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function requireApplyConfirmation(args: {
|
|
224
|
+
schemaVersion: unknown;
|
|
225
|
+
planDigest: unknown;
|
|
226
|
+
confirmation: unknown;
|
|
227
|
+
confirm: unknown;
|
|
228
|
+
}) {
|
|
229
|
+
if (args.confirm !== true) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`${MCP_ERRORS.INVALID_INPUT.code}: confirm must be true to apply`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const parsed = parseRefactorApplyConfirmation(args);
|
|
235
|
+
if ("error" in parsed) {
|
|
236
|
+
throw new Error(`${parsed.error}: ${parsed.message}`);
|
|
237
|
+
}
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function formatPreviewSummary(plan: FileRefactorPreviewPlan): string {
|
|
242
|
+
return `Preview ${plan.operation}: ${plan.source.relPath} → ${plan.target.relPath} (digest ${plan.planDigest.slice(0, 12)}…, canApply=${plan.canApply})`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function formatApplySummary(result: FileRefactorApplyResult): string {
|
|
246
|
+
return `${result.operation} ${result.status}: ${result.source.relPath} → ${result.target.relPath}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function previewOrApplyRename(
|
|
250
|
+
args: RenameNoteInput,
|
|
251
|
+
ctx: ToolContext
|
|
252
|
+
): Promise<FileRefactorPreviewPlan | FileRefactorApplyResult> {
|
|
253
|
+
requireWriteEnabled(ctx);
|
|
254
|
+
const doc = await resolveDocByRef(ctx, args.ref);
|
|
255
|
+
const capabilities = ensureEditable(doc);
|
|
256
|
+
const collection = resolveCollection(ctx, doc.collection);
|
|
257
|
+
const target = resolveRenameTarget({
|
|
258
|
+
collection: collection.name,
|
|
259
|
+
currentRelPath: doc.relPath,
|
|
260
|
+
nextName: args.name,
|
|
261
|
+
});
|
|
262
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
263
|
+
operation: "rename",
|
|
264
|
+
doc,
|
|
265
|
+
collection,
|
|
266
|
+
sourceFullPath: join(collection.path, doc.relPath),
|
|
267
|
+
target,
|
|
268
|
+
store: ctx.store,
|
|
269
|
+
sourceEditable: capabilities.editable,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (args.action === "preview") {
|
|
273
|
+
return plan;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const confirmation = requireApplyConfirmation(args);
|
|
277
|
+
// Collection-scoped lock lives inside applyFileRefactor — do not wrap with
|
|
278
|
+
// ctx.writeLockPath (avoids double-locking incompatible with REST/SDK).
|
|
279
|
+
return applyCanonicalFileRefactor({
|
|
280
|
+
plan,
|
|
281
|
+
confirmation,
|
|
282
|
+
deps: buildDurableFileRefactorApplyDeps({
|
|
283
|
+
collection,
|
|
284
|
+
store: ctx.store,
|
|
285
|
+
syncAfterCommit: async () => {
|
|
286
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
287
|
+
collection,
|
|
288
|
+
ctx.store,
|
|
289
|
+
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
290
|
+
);
|
|
291
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
292
|
+
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function previewOrApplyMove(
|
|
299
|
+
args: MoveNoteInput,
|
|
300
|
+
ctx: ToolContext
|
|
301
|
+
): Promise<FileRefactorPreviewPlan | FileRefactorApplyResult> {
|
|
302
|
+
requireWriteEnabled(ctx);
|
|
303
|
+
const doc = await resolveDocByRef(ctx, args.ref);
|
|
304
|
+
const capabilities = ensureEditable(doc);
|
|
305
|
+
const collection = resolveCollection(ctx, doc.collection);
|
|
306
|
+
const target = resolveMoveTarget({
|
|
307
|
+
collection: collection.name,
|
|
308
|
+
currentRelPath: doc.relPath,
|
|
309
|
+
folderPath: args.folderPath,
|
|
310
|
+
nextName: args.name,
|
|
311
|
+
});
|
|
312
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
313
|
+
operation: "move",
|
|
314
|
+
doc,
|
|
315
|
+
collection,
|
|
316
|
+
sourceFullPath: join(collection.path, doc.relPath),
|
|
317
|
+
target,
|
|
318
|
+
store: ctx.store,
|
|
319
|
+
sourceEditable: capabilities.editable,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
if (args.action === "preview") {
|
|
323
|
+
return plan;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const confirmation = requireApplyConfirmation(args);
|
|
327
|
+
return applyCanonicalFileRefactor({
|
|
328
|
+
plan,
|
|
329
|
+
confirmation,
|
|
330
|
+
deps: buildDurableFileRefactorApplyDeps({
|
|
331
|
+
collection,
|
|
332
|
+
store: ctx.store,
|
|
333
|
+
syncAfterCommit: async () => {
|
|
334
|
+
const syncResult = await defaultSyncService.syncCollection(
|
|
335
|
+
collection,
|
|
336
|
+
ctx.store,
|
|
337
|
+
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
338
|
+
);
|
|
339
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
340
|
+
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
341
|
+
},
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
159
344
|
}
|
|
160
345
|
|
|
161
346
|
export function handleCreateFolder(
|
|
@@ -166,9 +351,7 @@ export function handleCreateFolder(
|
|
|
166
351
|
ctx,
|
|
167
352
|
"gno_create_folder",
|
|
168
353
|
async () => {
|
|
169
|
-
|
|
170
|
-
throw new Error("Write tools disabled. Start MCP with --enable-write.");
|
|
171
|
-
}
|
|
354
|
+
requireWriteEnabled(ctx);
|
|
172
355
|
|
|
173
356
|
return withWriteLock(ctx.writeLockPath, async () => {
|
|
174
357
|
const collection = resolveCollection(ctx, args.collection);
|
|
@@ -196,40 +379,11 @@ export function handleRenameNote(
|
|
|
196
379
|
return runTool(
|
|
197
380
|
ctx,
|
|
198
381
|
"gno_rename_note",
|
|
199
|
-
async () =>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return withWriteLock(ctx.writeLockPath, async () => {
|
|
205
|
-
const doc = await resolveDocByRef(ctx, args.ref);
|
|
206
|
-
ensureEditable(doc);
|
|
207
|
-
const collection = resolveCollection(ctx, doc.collection);
|
|
208
|
-
const plan = planRenameRefactor({
|
|
209
|
-
collection: collection.name,
|
|
210
|
-
currentRelPath: doc.relPath,
|
|
211
|
-
nextName: args.name,
|
|
212
|
-
});
|
|
213
|
-
const currentPath = join(collection.path, doc.relPath);
|
|
214
|
-
const nextPath = join(collection.path, plan.nextRelPath);
|
|
215
|
-
await renameFilePath(currentPath, nextPath);
|
|
216
|
-
const syncResult = await defaultSyncService.syncCollection(
|
|
217
|
-
collection,
|
|
218
|
-
ctx.store,
|
|
219
|
-
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
220
|
-
);
|
|
221
|
-
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
222
|
-
return {
|
|
223
|
-
uri: plan.nextUri,
|
|
224
|
-
relPath: plan.nextRelPath,
|
|
225
|
-
warnings: buildRefactorWarnings(
|
|
226
|
-
await getRefactorSnapshot(ctx, doc.id),
|
|
227
|
-
{ filenameChanged: true }
|
|
228
|
-
).warnings,
|
|
229
|
-
};
|
|
230
|
-
});
|
|
231
|
-
},
|
|
232
|
-
(data) => `Renamed note to ${data.relPath}`
|
|
382
|
+
async () => previewOrApplyRename(args, ctx),
|
|
383
|
+
(data) =>
|
|
384
|
+
"planDigest" in data && "canApply" in data
|
|
385
|
+
? formatPreviewSummary(data)
|
|
386
|
+
: formatApplySummary(data)
|
|
233
387
|
);
|
|
234
388
|
}
|
|
235
389
|
|
|
@@ -240,45 +394,11 @@ export function handleMoveNote(
|
|
|
240
394
|
return runTool(
|
|
241
395
|
ctx,
|
|
242
396
|
"gno_move_note",
|
|
243
|
-
async () =>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return withWriteLock(ctx.writeLockPath, async () => {
|
|
249
|
-
const doc = await resolveDocByRef(ctx, args.ref);
|
|
250
|
-
ensureEditable(doc);
|
|
251
|
-
const collection = resolveCollection(ctx, doc.collection);
|
|
252
|
-
const plan = planMoveRefactor({
|
|
253
|
-
collection: collection.name,
|
|
254
|
-
currentRelPath: doc.relPath,
|
|
255
|
-
folderPath: args.folderPath,
|
|
256
|
-
nextName: args.name,
|
|
257
|
-
});
|
|
258
|
-
const currentPath = join(collection.path, doc.relPath);
|
|
259
|
-
const nextPath = join(collection.path, plan.nextRelPath);
|
|
260
|
-
await mkdir(dirname(nextPath), { recursive: true });
|
|
261
|
-
await renameFilePath(currentPath, nextPath);
|
|
262
|
-
const syncResult = await defaultSyncService.syncCollection(
|
|
263
|
-
collection,
|
|
264
|
-
ctx.store,
|
|
265
|
-
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
266
|
-
);
|
|
267
|
-
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
268
|
-
return {
|
|
269
|
-
uri: plan.nextUri,
|
|
270
|
-
relPath: plan.nextRelPath,
|
|
271
|
-
warnings: buildRefactorWarnings(
|
|
272
|
-
await getRefactorSnapshot(ctx, doc.id),
|
|
273
|
-
{
|
|
274
|
-
folderChanged: true,
|
|
275
|
-
filenameChanged: Boolean(args.name),
|
|
276
|
-
}
|
|
277
|
-
).warnings,
|
|
278
|
-
};
|
|
279
|
-
});
|
|
280
|
-
},
|
|
281
|
-
(data) => `Moved note to ${data.relPath}`
|
|
397
|
+
async () => previewOrApplyMove(args, ctx),
|
|
398
|
+
(data) =>
|
|
399
|
+
"planDigest" in data && "canApply" in data
|
|
400
|
+
? formatPreviewSummary(data)
|
|
401
|
+
: formatApplySummary(data)
|
|
282
402
|
);
|
|
283
403
|
}
|
|
284
404
|
|
|
@@ -290,9 +410,7 @@ export function handleDuplicateNote(
|
|
|
290
410
|
ctx,
|
|
291
411
|
"gno_duplicate_note",
|
|
292
412
|
async () => {
|
|
293
|
-
|
|
294
|
-
throw new Error("Write tools disabled. Start MCP with --enable-write.");
|
|
295
|
-
}
|
|
413
|
+
requireWriteEnabled(ctx);
|
|
296
414
|
|
|
297
415
|
return withWriteLock(ctx.writeLockPath, async () => {
|
|
298
416
|
const doc = await resolveDocByRef(ctx, args.ref);
|