@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,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared inventory token types and caps for reference-safe refactors.
|
|
3
|
+
*
|
|
4
|
+
* @module src/core/link-inventory-types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
FileRefactorReasonCode,
|
|
9
|
+
FileRefactorReferenceClassification,
|
|
10
|
+
FileRefactorReferenceKind,
|
|
11
|
+
} from "./file-refactor-contract";
|
|
12
|
+
import type { LinkEncodingStyle } from "./link-destination-parse";
|
|
13
|
+
|
|
14
|
+
/** Hard caps — callers must fail closed when truncated. */
|
|
15
|
+
export const LINK_INVENTORY_CAPS = {
|
|
16
|
+
maxContentChars: 1_000_000,
|
|
17
|
+
maxTokensPerDocument: 2_000,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export interface LinkInventoryToken {
|
|
21
|
+
kind: FileRefactorReferenceKind;
|
|
22
|
+
classification?: FileRefactorReferenceClassification;
|
|
23
|
+
reasonCode?: FileRefactorReasonCode;
|
|
24
|
+
raw: string;
|
|
25
|
+
originalDestination: string;
|
|
26
|
+
destinationStart: number;
|
|
27
|
+
destinationEnd: number;
|
|
28
|
+
startOffset: number;
|
|
29
|
+
endOffset: number;
|
|
30
|
+
startLine: number;
|
|
31
|
+
startCol: number;
|
|
32
|
+
endLine: number;
|
|
33
|
+
endCol: number;
|
|
34
|
+
targetRef: string;
|
|
35
|
+
targetAnchor?: string;
|
|
36
|
+
targetCollection?: string;
|
|
37
|
+
targetQuery?: string;
|
|
38
|
+
hadLeadingDotSlash: boolean;
|
|
39
|
+
encodingStyle: LinkEncodingStyle;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface LinkInventoryResult {
|
|
43
|
+
tokens: LinkInventoryToken[];
|
|
44
|
+
truncated: boolean;
|
|
45
|
+
/** True when destination spans overlap after inventory. */
|
|
46
|
+
overlapping: boolean;
|
|
47
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser-backed link inventory for reference-safe file refactors.
|
|
3
|
+
*
|
|
4
|
+
* Produces exact UTF-16 destination token spans plus conservative opaque /
|
|
5
|
+
* malformed detections. Does not mutate disk or change parseLinks behavior.
|
|
6
|
+
*
|
|
7
|
+
* @module src/core/link-inventory
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExcludedRange } from "../ingestion/strip";
|
|
11
|
+
import type {
|
|
12
|
+
LinkInventoryResult,
|
|
13
|
+
LinkInventoryToken,
|
|
14
|
+
} from "./link-inventory-types";
|
|
15
|
+
|
|
16
|
+
import { getExcludedRanges } from "../ingestion/strip";
|
|
17
|
+
import {
|
|
18
|
+
inventoryInlineMarkdown,
|
|
19
|
+
inventoryReferenceDefinitions,
|
|
20
|
+
inventoryWikiLinks,
|
|
21
|
+
} from "./link-inventory-markdown";
|
|
22
|
+
import {
|
|
23
|
+
buildLineOffsets,
|
|
24
|
+
inventoryEmbeds,
|
|
25
|
+
inventoryHtmlHrefs,
|
|
26
|
+
inventoryMalformedWiki,
|
|
27
|
+
} from "./link-inventory-opaque";
|
|
28
|
+
import { LINK_INVENTORY_CAPS } from "./link-inventory-types";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
LINK_INVENTORY_CAPS,
|
|
32
|
+
type LinkInventoryResult,
|
|
33
|
+
type LinkInventoryToken,
|
|
34
|
+
} from "./link-inventory-types";
|
|
35
|
+
export {
|
|
36
|
+
buildContentPrefilterNeedles,
|
|
37
|
+
buildSourceRelevanceKeys,
|
|
38
|
+
isRelevantDestination,
|
|
39
|
+
} from "./link-relevance";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Destination identity for inventory dedupe.
|
|
43
|
+
* Kind is intentionally omitted so identical spans from overlapping scanners
|
|
44
|
+
* collapse to the first (scanner-precedence) token.
|
|
45
|
+
*/
|
|
46
|
+
export function inventoryDestinationKey(token: {
|
|
47
|
+
destinationStart: number;
|
|
48
|
+
destinationEnd: number;
|
|
49
|
+
originalDestination: string;
|
|
50
|
+
}): string {
|
|
51
|
+
return `${token.destinationStart}:${token.destinationEnd}:${token.originalDestination}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Dedupe identical destination spans (regardless of scanner kind) and detect
|
|
56
|
+
* true partial overlaps. First token wins — callers should push in scanner order.
|
|
57
|
+
*/
|
|
58
|
+
export function dedupeInventoryDestinationTokens(
|
|
59
|
+
tokens: LinkInventoryToken[]
|
|
60
|
+
): {
|
|
61
|
+
tokens: LinkInventoryToken[];
|
|
62
|
+
overlapping: boolean;
|
|
63
|
+
} {
|
|
64
|
+
const seen = new Set<string>();
|
|
65
|
+
const unique: LinkInventoryToken[] = [];
|
|
66
|
+
for (const token of tokens) {
|
|
67
|
+
const key = inventoryDestinationKey(token);
|
|
68
|
+
if (seen.has(key)) continue;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
unique.push(token);
|
|
71
|
+
}
|
|
72
|
+
unique.sort((a, b) => {
|
|
73
|
+
if (a.destinationStart !== b.destinationStart) {
|
|
74
|
+
return a.destinationStart - b.destinationStart;
|
|
75
|
+
}
|
|
76
|
+
return a.destinationEnd - b.destinationEnd;
|
|
77
|
+
});
|
|
78
|
+
let overlapping = false;
|
|
79
|
+
for (let i = 1; i < unique.length; i += 1) {
|
|
80
|
+
const prev = unique[i - 1]!;
|
|
81
|
+
const cur = unique[i]!;
|
|
82
|
+
if (cur.destinationStart < prev.destinationEnd) {
|
|
83
|
+
overlapping = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
unique.sort((a, b) => {
|
|
88
|
+
if (a.startLine !== b.startLine) return a.startLine - b.startLine;
|
|
89
|
+
if (a.startCol !== b.startCol) return a.startCol - b.startCol;
|
|
90
|
+
return a.destinationStart - b.destinationStart;
|
|
91
|
+
});
|
|
92
|
+
return { tokens: unique, overlapping };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Inventory all relevant rewrite / opaque reference tokens in one document.
|
|
97
|
+
*/
|
|
98
|
+
export function inventoryDocumentLinks(
|
|
99
|
+
markdown: string,
|
|
100
|
+
options: {
|
|
101
|
+
sourceKeys: ReadonlySet<string>;
|
|
102
|
+
excludedRanges?: ExcludedRange[];
|
|
103
|
+
}
|
|
104
|
+
): LinkInventoryResult {
|
|
105
|
+
const truncated = { value: false };
|
|
106
|
+
if (markdown.length > LINK_INVENTORY_CAPS.maxContentChars) {
|
|
107
|
+
return { tokens: [], truncated: true, overlapping: false };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const excluded = options.excludedRanges ?? getExcludedRanges(markdown);
|
|
111
|
+
const lineOffsets = buildLineOffsets(markdown);
|
|
112
|
+
const tokens: LinkInventoryToken[] = [];
|
|
113
|
+
const consumed = new Set<number>();
|
|
114
|
+
const sourceKeys = options.sourceKeys;
|
|
115
|
+
|
|
116
|
+
const ok =
|
|
117
|
+
inventoryEmbeds(
|
|
118
|
+
markdown,
|
|
119
|
+
lineOffsets,
|
|
120
|
+
sourceKeys,
|
|
121
|
+
excluded,
|
|
122
|
+
tokens,
|
|
123
|
+
truncated,
|
|
124
|
+
consumed
|
|
125
|
+
) &&
|
|
126
|
+
inventoryHtmlHrefs(
|
|
127
|
+
markdown,
|
|
128
|
+
lineOffsets,
|
|
129
|
+
sourceKeys,
|
|
130
|
+
excluded,
|
|
131
|
+
tokens,
|
|
132
|
+
truncated,
|
|
133
|
+
consumed
|
|
134
|
+
) &&
|
|
135
|
+
inventoryMalformedWiki(
|
|
136
|
+
markdown,
|
|
137
|
+
lineOffsets,
|
|
138
|
+
sourceKeys,
|
|
139
|
+
excluded,
|
|
140
|
+
tokens,
|
|
141
|
+
truncated,
|
|
142
|
+
consumed
|
|
143
|
+
) &&
|
|
144
|
+
inventoryWikiLinks(
|
|
145
|
+
markdown,
|
|
146
|
+
lineOffsets,
|
|
147
|
+
sourceKeys,
|
|
148
|
+
excluded,
|
|
149
|
+
tokens,
|
|
150
|
+
truncated,
|
|
151
|
+
consumed
|
|
152
|
+
) &&
|
|
153
|
+
inventoryReferenceDefinitions(
|
|
154
|
+
markdown,
|
|
155
|
+
lineOffsets,
|
|
156
|
+
sourceKeys,
|
|
157
|
+
excluded,
|
|
158
|
+
tokens,
|
|
159
|
+
truncated,
|
|
160
|
+
consumed
|
|
161
|
+
) &&
|
|
162
|
+
inventoryInlineMarkdown(
|
|
163
|
+
markdown,
|
|
164
|
+
lineOffsets,
|
|
165
|
+
sourceKeys,
|
|
166
|
+
excluded,
|
|
167
|
+
tokens,
|
|
168
|
+
truncated,
|
|
169
|
+
consumed
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (!ok) {
|
|
173
|
+
return { tokens, truncated: true, overlapping: false };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const finalized = dedupeInventoryDestinationTokens(tokens);
|
|
177
|
+
return {
|
|
178
|
+
tokens: finalized.tokens,
|
|
179
|
+
truncated: truncated.value,
|
|
180
|
+
overlapping: finalized.overlapping,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative destination relevance for reference-safe file refactors.
|
|
3
|
+
*
|
|
4
|
+
* Uses exact token/path/basename equality after stripping query/fragment,
|
|
5
|
+
* safe decoding, NFC normalization, and syntax delimiters. No fuzzy
|
|
6
|
+
* substring matching — unrelated `other-note.md` must not match `note.md`.
|
|
7
|
+
*
|
|
8
|
+
* @module src/core/link-relevance
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// node:path/posix — no Bun path utils
|
|
12
|
+
import { posix as pathPosix } from "node:path";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
stripAngleBracketDestination,
|
|
16
|
+
unescapeCommonMarkDestination,
|
|
17
|
+
} from "./link-destination-parse";
|
|
18
|
+
import { normalizeWikiName, stripWikiMdExt } from "./links";
|
|
19
|
+
|
|
20
|
+
function safeDecodeForRelevance(value: string): string {
|
|
21
|
+
return value
|
|
22
|
+
.replaceAll("%20", " ")
|
|
23
|
+
.replaceAll("%28", "(")
|
|
24
|
+
.replaceAll("%29", ")");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function stripQueryAndFragment(value: string): string {
|
|
28
|
+
const hash = value.indexOf("#");
|
|
29
|
+
const withoutHash = hash >= 0 ? value.slice(0, hash) : value;
|
|
30
|
+
const query = withoutHash.indexOf("?");
|
|
31
|
+
return query >= 0 ? withoutHash.slice(0, query) : withoutHash;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function addKey(keys: Set<string>, value: string): void {
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
if (!trimmed) return;
|
|
37
|
+
keys.add(normalizeWikiName(trimmed));
|
|
38
|
+
keys.add(normalizeWikiName(stripWikiMdExt(trimmed)));
|
|
39
|
+
keys.add(trimmed.toLowerCase());
|
|
40
|
+
keys.add(stripWikiMdExt(trimmed).toLowerCase());
|
|
41
|
+
const base = pathPosix.basename(trimmed);
|
|
42
|
+
keys.add(normalizeWikiName(base));
|
|
43
|
+
keys.add(normalizeWikiName(stripWikiMdExt(base)));
|
|
44
|
+
keys.add(base.toLowerCase());
|
|
45
|
+
keys.add(stripWikiMdExt(base).toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Build lookup keys used to decide whether a destination is about the source. */
|
|
49
|
+
export function buildSourceRelevanceKeys(input: {
|
|
50
|
+
relPath: string;
|
|
51
|
+
title: string | null | undefined;
|
|
52
|
+
}): Set<string> {
|
|
53
|
+
const keys = new Set<string>();
|
|
54
|
+
addKey(keys, input.relPath);
|
|
55
|
+
addKey(keys, input.relPath.normalize("NFC"));
|
|
56
|
+
addKey(keys, input.relPath.normalize("NFD"));
|
|
57
|
+
if (input.title) {
|
|
58
|
+
addKey(keys, input.title);
|
|
59
|
+
addKey(keys, input.title.normalize("NFC"));
|
|
60
|
+
addKey(keys, input.title.normalize("NFD"));
|
|
61
|
+
}
|
|
62
|
+
return keys;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Normalize a destination token into candidate equality keys (exact only).
|
|
67
|
+
*/
|
|
68
|
+
export function destinationRelevanceCandidates(destination: string): string[] {
|
|
69
|
+
const trimmed = destination.trim();
|
|
70
|
+
if (!trimmed) return [];
|
|
71
|
+
|
|
72
|
+
const { path: withoutAngles } = stripAngleBracketDestination(trimmed);
|
|
73
|
+
const unescaped = unescapeCommonMarkDestination(withoutAngles);
|
|
74
|
+
const withoutQueryFrag = stripQueryAndFragment(unescaped);
|
|
75
|
+
const decoded = safeDecodeForRelevance(withoutQueryFrag);
|
|
76
|
+
|
|
77
|
+
const forms = [
|
|
78
|
+
trimmed,
|
|
79
|
+
withoutAngles,
|
|
80
|
+
unescaped,
|
|
81
|
+
withoutQueryFrag,
|
|
82
|
+
decoded,
|
|
83
|
+
withoutQueryFrag.normalize("NFC"),
|
|
84
|
+
withoutQueryFrag.normalize("NFD"),
|
|
85
|
+
decoded.normalize("NFC"),
|
|
86
|
+
decoded.normalize("NFD"),
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const keys = new Set<string>();
|
|
90
|
+
for (const form of forms) {
|
|
91
|
+
addKey(keys, form);
|
|
92
|
+
}
|
|
93
|
+
return [...keys];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Exact-token relevance: true only when a normalized destination key equals a
|
|
98
|
+
* source key. Malformed prefixes may still match when the prefix token itself
|
|
99
|
+
* equals a source key (bounded exact equality, not substring).
|
|
100
|
+
*/
|
|
101
|
+
export function isRelevantDestination(
|
|
102
|
+
destination: string,
|
|
103
|
+
sourceKeys: ReadonlySet<string>
|
|
104
|
+
): boolean {
|
|
105
|
+
const candidates = destinationRelevanceCandidates(destination);
|
|
106
|
+
return candidates.some((key) => sourceKeys.has(key));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Content-prefilter needle strings for SQL LIKE (caller escapes wildcards).
|
|
111
|
+
* Conservative: source path/title identities and common escape encodings.
|
|
112
|
+
*/
|
|
113
|
+
export function buildContentPrefilterNeedles(input: {
|
|
114
|
+
relPath: string;
|
|
115
|
+
title: string | null | undefined;
|
|
116
|
+
}): string[] {
|
|
117
|
+
const needles = new Set<string>();
|
|
118
|
+
const add = (value: string): void => {
|
|
119
|
+
const trimmed = value.trim();
|
|
120
|
+
if (!trimmed || trimmed.length < 2) return;
|
|
121
|
+
needles.add(trimmed);
|
|
122
|
+
needles.add(trimmed.normalize("NFC"));
|
|
123
|
+
needles.add(trimmed.normalize("NFD"));
|
|
124
|
+
needles.add(trimmed.replaceAll(" ", "%20"));
|
|
125
|
+
needles.add(
|
|
126
|
+
trimmed
|
|
127
|
+
.replaceAll(" ", "%20")
|
|
128
|
+
.replaceAll("(", "%28")
|
|
129
|
+
.replaceAll(")", "%29")
|
|
130
|
+
);
|
|
131
|
+
needles.add(trimmed.replaceAll("(", "%28").replaceAll(")", "%29"));
|
|
132
|
+
needles.add(
|
|
133
|
+
trimmed
|
|
134
|
+
.replaceAll(" ", "\\ ")
|
|
135
|
+
.replaceAll("(", "\\(")
|
|
136
|
+
.replaceAll(")", "\\)")
|
|
137
|
+
);
|
|
138
|
+
needles.add(trimmed.replaceAll("(", "\\(").replaceAll(")", "\\)"));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
add(input.relPath);
|
|
142
|
+
add(pathPosix.basename(input.relPath));
|
|
143
|
+
add(stripWikiMdExt(pathPosix.basename(input.relPath)));
|
|
144
|
+
add(stripWikiMdExt(input.relPath));
|
|
145
|
+
if (input.title) {
|
|
146
|
+
add(input.title);
|
|
147
|
+
add(`${input.title}.md`);
|
|
148
|
+
}
|
|
149
|
+
return [...needles].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
150
|
+
}
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -95,6 +95,10 @@ import {
|
|
|
95
95
|
handleDuplicateNote,
|
|
96
96
|
handleMoveNote,
|
|
97
97
|
handleRenameNote,
|
|
98
|
+
MOVE_NOTE_MCP_ANNOTATIONS,
|
|
99
|
+
moveNoteInputSchema,
|
|
100
|
+
RENAME_NOTE_MCP_ANNOTATIONS,
|
|
101
|
+
renameNoteInputSchema,
|
|
98
102
|
} from "./workspace-write";
|
|
99
103
|
|
|
100
104
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -398,17 +402,6 @@ const createFolderInputSchema = z.object({
|
|
|
398
402
|
parentPath: z.string().optional(),
|
|
399
403
|
});
|
|
400
404
|
|
|
401
|
-
const renameNoteInputSchema = z.object({
|
|
402
|
-
ref: z.string().min(1, "ref cannot be empty"),
|
|
403
|
-
name: z.string().min(1, "name cannot be empty"),
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const moveNoteInputSchema = z.object({
|
|
407
|
-
ref: z.string().min(1, "ref cannot be empty"),
|
|
408
|
-
folderPath: z.string().min(1, "folderPath cannot be empty"),
|
|
409
|
-
name: z.string().optional(),
|
|
410
|
-
});
|
|
411
|
-
|
|
412
405
|
const duplicateNoteInputSchema = z.object({
|
|
413
406
|
ref: z.string().min(1, "ref cannot be empty"),
|
|
414
407
|
folderPath: z.string().optional(),
|
|
@@ -1351,17 +1344,25 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
1351
1344
|
(args) => handleCreateFolder(args, ctx)
|
|
1352
1345
|
);
|
|
1353
1346
|
|
|
1354
|
-
server.
|
|
1347
|
+
server.registerTool(
|
|
1355
1348
|
"gno_rename_note",
|
|
1356
|
-
|
|
1357
|
-
|
|
1349
|
+
{
|
|
1350
|
+
description:
|
|
1351
|
+
'Preview or apply a reference-safe rename of an editable note. Use action=preview first, then action=apply with the exact planDigest, confirmation="apply", confirm=true, and schemaVersion="1.0". Annotations are hints only; --enable-write remains authoritative.',
|
|
1352
|
+
inputSchema: renameNoteInputSchema,
|
|
1353
|
+
annotations: RENAME_NOTE_MCP_ANNOTATIONS,
|
|
1354
|
+
},
|
|
1358
1355
|
(args) => handleRenameNote(args, ctx)
|
|
1359
1356
|
);
|
|
1360
1357
|
|
|
1361
|
-
server.
|
|
1358
|
+
server.registerTool(
|
|
1362
1359
|
"gno_move_note",
|
|
1363
|
-
|
|
1364
|
-
|
|
1360
|
+
{
|
|
1361
|
+
description:
|
|
1362
|
+
'Preview or apply a reference-safe same-collection move of an editable note. Use action=preview first, then action=apply with the exact planDigest, confirmation="apply", confirm=true, and schemaVersion="1.0". Annotations are hints only; --enable-write remains authoritative.',
|
|
1363
|
+
inputSchema: moveNoteInputSchema,
|
|
1364
|
+
annotations: MOVE_NOTE_MCP_ANNOTATIONS,
|
|
1365
|
+
},
|
|
1365
1366
|
(args) => handleMoveNote(args, ctx)
|
|
1366
1367
|
);
|
|
1367
1368
|
|