@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,263 @@
1
+ /**
2
+ * Durable SectionTargetV1 types, bounds, and creation.
3
+ * Private helper module — import public API from `./sections`.
4
+ *
5
+ * @module src/core/section-target
6
+ */
7
+
8
+ import {
9
+ extractSectionRecords,
10
+ lineStartOffsets,
11
+ type SectionRecord,
12
+ } from "./section-parse";
13
+
14
+ /** Versioned, bounded, content-derived section locator (v1). */
15
+ export interface SectionTargetV1 {
16
+ schemaVersion: "1";
17
+ document: {
18
+ uri: string;
19
+ };
20
+ /** Human-readable heading anchor at capture time. */
21
+ anchor: string;
22
+ /** NFC-normalized heading ancestry including the section title. */
23
+ headingPath: string[];
24
+ /** 1-based occurrence among sections with the same headingPath. */
25
+ occurrence: number;
26
+ quote: {
27
+ exact: string;
28
+ prefix: string;
29
+ suffix: string;
30
+ };
31
+ /** SHA-256 hex fingerprint of the full source document content. */
32
+ sourceFingerprint: string;
33
+ /** Line/offset hints only — never identity. */
34
+ hints: {
35
+ line: number;
36
+ startOffset: number;
37
+ endOffset: number;
38
+ };
39
+ }
40
+
41
+ export const SECTION_TARGET_SCHEMA_VERSION = "1" as const;
42
+
43
+ /**
44
+ * Hard bounds so targets never embed full section bodies and remain
45
+ * schema-faithful. Identity fields (uri/anchor/headingPath) are never
46
+ * truncated — creation fails closed when they cannot fit.
47
+ */
48
+ export const SECTION_TARGET_BOUNDS = {
49
+ exactMaxChars: 96,
50
+ prefixMaxChars: 32,
51
+ suffixMaxChars: 32,
52
+ uriMaxChars: 1024,
53
+ anchorMaxChars: 512,
54
+ headingPathItemMaxChars: 512,
55
+ headingPathMaxItems: 6,
56
+ maxSerializedBytes: 2048,
57
+ } as const;
58
+
59
+ const UTF8 = new TextEncoder();
60
+
61
+ /** Structural section record with bounded quote evidence. */
62
+ export interface QuotedSectionRecord extends SectionRecord {
63
+ quote: {
64
+ exact: string;
65
+ prefix: string;
66
+ suffix: string;
67
+ };
68
+ quoteStartOffset: number;
69
+ quoteEndOffset: number;
70
+ }
71
+
72
+ const clipStart = (value: string, maxChars: number): string =>
73
+ value.length <= maxChars ? value : value.slice(0, maxChars);
74
+
75
+ const clipEnd = (value: string, maxChars: number): string =>
76
+ value.length <= maxChars ? value : value.slice(value.length - maxChars);
77
+
78
+ const bytesOf = (value: string): number => UTF8.encode(value).byteLength;
79
+
80
+ /** SHA-256 hex digest via Web Crypto (browser- and Bun-safe). */
81
+ export async function fingerprintSourceContent(
82
+ content: string
83
+ ): Promise<string> {
84
+ const digest = new Uint8Array(
85
+ await crypto.subtle.digest("SHA-256", UTF8.encode(content))
86
+ );
87
+ return [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
88
+ }
89
+
90
+ const chooseQuoteRange = (
91
+ content: string,
92
+ recordTitleStart: number,
93
+ recordTitleEnd: number,
94
+ bodyStartOffset: number,
95
+ bodyEndOffset: number
96
+ ): { start: number; end: number } => {
97
+ const body = content.slice(bodyStartOffset, bodyEndOffset);
98
+ const trimmedLead = body.match(/^\s*/)?.[0]?.length ?? 0;
99
+ const trimmedTrail = body.match(/\s*$/)?.[0]?.length ?? 0;
100
+ const bodyContentStart = bodyStartOffset + trimmedLead;
101
+ const bodyContentEnd = Math.max(
102
+ bodyContentStart,
103
+ bodyEndOffset - trimmedTrail
104
+ );
105
+ if (bodyContentStart < bodyContentEnd) {
106
+ const exact = clipStart(
107
+ content.slice(bodyContentStart, bodyContentEnd),
108
+ SECTION_TARGET_BOUNDS.exactMaxChars
109
+ );
110
+ if (exact.length > 0) {
111
+ return {
112
+ start: bodyContentStart,
113
+ end: bodyContentStart + exact.length,
114
+ };
115
+ }
116
+ }
117
+ return { start: recordTitleStart, end: recordTitleEnd };
118
+ };
119
+
120
+ /**
121
+ * Prefer local context so heading renames / insertions do not poison prefix
122
+ * evidence. Falls back to a short global clip only when local context is empty.
123
+ */
124
+ const buildBoundedQuote = (
125
+ content: string,
126
+ exactStart: number,
127
+ exactEnd: number,
128
+ localPrefixFloor: number
129
+ ): { exact: string; prefix: string; suffix: string } => {
130
+ const exact = clipStart(
131
+ content.slice(exactStart, exactEnd),
132
+ SECTION_TARGET_BOUNDS.exactMaxChars
133
+ );
134
+ const boundedEnd = exactStart + exact.length;
135
+ const localPrefix = content.slice(localPrefixFloor, exactStart);
136
+ const prefixSource =
137
+ localPrefix.length > 0 ? localPrefix : content.slice(0, exactStart);
138
+ return {
139
+ exact,
140
+ prefix: clipEnd(prefixSource, SECTION_TARGET_BOUNDS.prefixMaxChars),
141
+ suffix: clipStart(
142
+ content.slice(boundedEnd),
143
+ SECTION_TARGET_BOUNDS.suffixMaxChars
144
+ ),
145
+ };
146
+ };
147
+
148
+ /** Attach bounded quote evidence to structural section records. */
149
+ export const withQuotes = (
150
+ content: string,
151
+ records: readonly SectionRecord[]
152
+ ): QuotedSectionRecord[] => {
153
+ const starts = lineStartOffsets(content);
154
+ return records.map((record) => {
155
+ const bodyStartOffset = starts[record.section.line] ?? content.length;
156
+ const bodyEndOffset =
157
+ record.endLine >= starts.length
158
+ ? content.length
159
+ : (starts[record.endLine] ?? content.length);
160
+ const quoteRange = chooseQuoteRange(
161
+ content,
162
+ record.titleStartOffset,
163
+ record.titleEndOffset,
164
+ bodyStartOffset,
165
+ bodyEndOffset
166
+ );
167
+ // Keep prefix local to the gap after the heading title (or markers before
168
+ // a title-only quote) so renames do not poison quote recovery.
169
+ const prefixFloor =
170
+ quoteRange.start >= record.titleEndOffset
171
+ ? record.titleEndOffset
172
+ : (starts[record.section.line - 1] ?? record.titleStartOffset);
173
+ return {
174
+ ...record,
175
+ quoteStartOffset: quoteRange.start,
176
+ quoteEndOffset: quoteRange.end,
177
+ quote: buildBoundedQuote(
178
+ content,
179
+ quoteRange.start,
180
+ quoteRange.end,
181
+ prefixFloor
182
+ ),
183
+ };
184
+ });
185
+ };
186
+
187
+ /**
188
+ * True when every identity/quote field and the UTF-8 JSON encoding satisfy
189
+ * runtime bounds. Identity fields must be present faithfully — never truncated.
190
+ */
191
+ export const isBoundedSectionTarget = (target: SectionTargetV1): boolean => {
192
+ const { uri } = target.document;
193
+ if (
194
+ uri.length < 1 ||
195
+ uri.length > SECTION_TARGET_BOUNDS.uriMaxChars ||
196
+ target.anchor.length < 1 ||
197
+ target.anchor.length > SECTION_TARGET_BOUNDS.anchorMaxChars ||
198
+ target.headingPath.length < 1 ||
199
+ target.headingPath.length > SECTION_TARGET_BOUNDS.headingPathMaxItems ||
200
+ target.headingPath.some(
201
+ (item) =>
202
+ item.length < 1 ||
203
+ item.length > SECTION_TARGET_BOUNDS.headingPathItemMaxChars
204
+ ) ||
205
+ target.quote.exact.length > SECTION_TARGET_BOUNDS.exactMaxChars ||
206
+ target.quote.prefix.length > SECTION_TARGET_BOUNDS.prefixMaxChars ||
207
+ target.quote.suffix.length > SECTION_TARGET_BOUNDS.suffixMaxChars
208
+ ) {
209
+ return false;
210
+ }
211
+ return (
212
+ bytesOf(JSON.stringify(target)) <= SECTION_TARGET_BOUNDS.maxSerializedBytes
213
+ );
214
+ };
215
+
216
+ export interface CreateSectionTargetInput {
217
+ content: string;
218
+ uri: string;
219
+ /** Capture by current readable anchor, or by 1-based heading line. */
220
+ anchor?: string;
221
+ line?: number;
222
+ }
223
+
224
+ /**
225
+ * Create a bounded SectionTargetV1 for a heading in `content`.
226
+ * Returns null when the section cannot be found, or when a faithful
227
+ * bounded target cannot be produced (oversized uri/anchor/path/serialized).
228
+ */
229
+ export async function createSectionTarget(
230
+ input: CreateSectionTargetInput
231
+ ): Promise<SectionTargetV1 | null> {
232
+ const records = withQuotes(
233
+ input.content,
234
+ extractSectionRecords(input.content)
235
+ );
236
+ const record =
237
+ input.anchor !== undefined
238
+ ? records.find((entry) => entry.section.anchor === input.anchor)
239
+ : input.line !== undefined
240
+ ? records.find((entry) => entry.section.line === input.line)
241
+ : undefined;
242
+ if (!record) return null;
243
+
244
+ const sourceFingerprint = await fingerprintSourceContent(input.content);
245
+ const target: SectionTargetV1 = {
246
+ schemaVersion: SECTION_TARGET_SCHEMA_VERSION,
247
+ document: { uri: input.uri },
248
+ anchor: record.section.anchor,
249
+ headingPath: [...record.headingPath],
250
+ occurrence: record.occurrence,
251
+ quote: { ...record.quote },
252
+ sourceFingerprint,
253
+ hints: {
254
+ line: record.section.line,
255
+ startOffset: record.quoteStartOffset,
256
+ endOffset: record.quoteEndOffset,
257
+ },
258
+ };
259
+
260
+ // Fail closed: never truncate identity-bearing uri/anchor/path.
261
+ if (!isBoundedSectionTarget(target)) return null;
262
+ return target;
263
+ }
@@ -1,125 +1,70 @@
1
1
  /**
2
- * Shared section extraction and anchor helpers.
2
+ * Shared section extraction, anchors, and durable section targets.
3
3
  *
4
- * Browser-safe.
4
+ * Browser-safe (Web Crypto + standard string APIs only).
5
5
  *
6
6
  * @module src/core/sections
7
7
  */
8
8
 
9
- export interface DocumentSection {
10
- anchor: string;
11
- level: number;
12
- line: number;
13
- title: string;
14
- }
9
+ export type { DocumentSection } from "./section-parse";
10
+ export {
11
+ extractInclusiveLines,
12
+ extractSections,
13
+ headingForLine,
14
+ slugifySectionTitle,
15
+ } from "./section-parse";
15
16
 
16
- const HEADING_REGEX = /^(#{1,6})\s+(.+?)\s*#*\s*$/u;
17
- const FENCE_REGEX = /^ {0,3}(`{3,}|~{3,})(.*)$/u;
18
- const FENCE_CLOSE_REGEX = /^ {0,3}(`{3,}|~{3,})[\t ]*$/u;
17
+ export type {
18
+ CreateSectionTargetInput,
19
+ SectionTargetV1,
20
+ } from "./section-target";
21
+ export {
22
+ SECTION_TARGET_BOUNDS,
23
+ SECTION_TARGET_SCHEMA_VERSION,
24
+ createSectionTarget,
25
+ fingerprintSourceContent,
26
+ isBoundedSectionTarget,
27
+ } from "./section-target";
19
28
 
20
- interface OpenFence {
21
- marker: "`" | "~";
22
- length: number;
23
- }
29
+ export type {
30
+ ResolveSectionTargetInput,
31
+ SectionResolution,
32
+ SectionResolutionCandidate,
33
+ SectionResolutionStatus,
34
+ } from "./section-target-resolve";
35
+ export {
36
+ isNavigableSectionResolution,
37
+ resolveSectionTarget,
38
+ } from "./section-target-resolve";
24
39
 
25
- const fenceOpener = (line: string): OpenFence | null => {
26
- const match = FENCE_REGEX.exec(line);
27
- const run = match?.[1];
28
- const suffix = match?.[2] ?? "";
29
- if (!run || (run[0] === "`" && suffix.includes("`"))) return null;
30
- return { marker: run[0] as OpenFence["marker"], length: run.length };
31
- };
40
+ export type {
41
+ SectionCitationV1,
42
+ SectionResolutionDiagnostics,
43
+ SectionTargetCreateResult,
44
+ SectionTargetCreateSelector,
45
+ SectionTargetResolveResult,
46
+ TransportValidationResult,
47
+ } from "./section-target-transport";
48
+ export {
49
+ CANONICAL_URI_EXCEEDS_TRANSPORT_BOUNDS,
50
+ CITATION_EXCEEDS_TRANSPORT_BOUNDS,
51
+ SECTION_TARGET_TRANSPORT_BOUNDS,
52
+ isTransportBoundedCandidate,
53
+ isTransportBoundedCanonicalUri,
54
+ isTransportBoundedCitation,
55
+ parseSectionTargetCreateSelector,
56
+ parseSectionTargetResolveBody,
57
+ parseSectionTargetV1,
58
+ projectSectionTargetCreateResult,
59
+ projectSectionTargetResolveResult,
60
+ } from "./section-target-transport";
32
61
 
33
- const closesFence = (line: string, fence: OpenFence): boolean => {
34
- const run = FENCE_CLOSE_REGEX.exec(line)?.[1];
35
- return Boolean(run && run[0] === fence.marker && run.length >= fence.length);
36
- };
37
-
38
- export function slugifySectionTitle(title: string): string {
39
- return (
40
- title
41
- .normalize("NFC")
42
- .toLowerCase()
43
- .trim()
44
- .replaceAll(/[^\p{L}\p{N}\s-]/gu, "")
45
- .replaceAll(/\s+/g, "-")
46
- .replaceAll(/-+/g, "-")
47
- .replace(/^-|-$/g, "") || "section"
48
- );
49
- }
50
-
51
- export function extractSections(content: string): DocumentSection[] {
52
- const sections: DocumentSection[] = [];
53
- const counts = new Map<string, number>();
54
- const lines = content.split("\n");
55
- let openFence: OpenFence | null = null;
56
-
57
- for (const [index, line] of lines.entries()) {
58
- if (openFence) {
59
- if (closesFence(line, openFence)) openFence = null;
60
- continue;
61
- }
62
- const opener = fenceOpener(line);
63
- if (opener) {
64
- openFence = opener;
65
- continue;
66
- }
67
- const match = HEADING_REGEX.exec(line);
68
- if (!match) {
69
- continue;
70
- }
71
-
72
- const level = match[1]?.length ?? 0;
73
- const title = match[2]?.trim() ?? "";
74
- if (!title) {
75
- continue;
76
- }
77
-
78
- const baseAnchor = slugifySectionTitle(title);
79
- const count = (counts.get(baseAnchor) ?? 0) + 1;
80
- counts.set(baseAnchor, count);
81
- const anchor = count === 1 ? baseAnchor : `${baseAnchor}-${count}`;
82
-
83
- sections.push({
84
- anchor,
85
- level,
86
- line: index + 1,
87
- title,
88
- });
89
- }
90
-
91
- return sections;
92
- }
93
-
94
- /** Extract one inclusive, 1-based line range without normalizing source bytes. */
95
- export function extractInclusiveLines(
96
- content: string,
97
- startLine: number,
98
- endLine: number
99
- ): string | null {
100
- if (
101
- content.includes("\r") ||
102
- !Number.isSafeInteger(startLine) ||
103
- !Number.isSafeInteger(endLine) ||
104
- startLine < 1 ||
105
- endLine < startLine
106
- ) {
107
- return null;
108
- }
109
- const lines = content.split("\n");
110
- if (endLine > lines.length) return null;
111
- return lines.slice(startLine - 1, endLine).join("\n");
112
- }
113
-
114
- /** Find the nearest Markdown heading governing a 1-based source line. */
115
- export function headingForLine(
116
- sections: readonly DocumentSection[],
117
- line: number
118
- ): string | null {
119
- let heading: string | null = null;
120
- for (const section of sections) {
121
- if (section.line > line) break;
122
- heading = section.title;
123
- }
124
- return heading;
125
- }
62
+ export type { SectionTargetLinkDecodeFailure } from "./section-target-link";
63
+ export {
64
+ SECTION_TARGET_LINK_MAX_ENCODED_CHARS,
65
+ SECTION_TARGET_LINK_PARAM,
66
+ SECTION_TARGET_LINK_VERSION,
67
+ classifySectionTargetLinkDecodeFailure,
68
+ decodeSectionTargetLinkParam,
69
+ encodeSectionTargetLinkParam,
70
+ } from "./section-target-link";
package/src/mcp/AGENTS.md CHANGED
@@ -13,6 +13,7 @@ src/mcp/
13
13
  │ ├── vsearch.ts # gno_vsearch (vector)
14
14
  │ ├── query.ts # gno_query (hybrid)
15
15
  │ ├── get.ts # gno_get (single doc)
16
+ │ ├── sections.ts # gno_section (section target create/resolve)
16
17
  │ ├── multi-get.ts # gno_multi_get (batch)
17
18
  │ └── status.ts # gno_status
18
19
  └── resources/ # Resource implementations
package/src/mcp/CLAUDE.md CHANGED
@@ -13,6 +13,7 @@ src/mcp/
13
13
  │ ├── vsearch.ts # gno_vsearch (vector)
14
14
  │ ├── query.ts # gno_query (hybrid)
15
15
  │ ├── get.ts # gno_get (single doc)
16
+ │ ├── sections.ts # gno_section (section target create/resolve)
16
17
  │ ├── multi-get.ts # gno_multi_get (batch)
17
18
  │ └── status.ts # gno_status
18
19
  └── resources/ # Resource implementations
@@ -53,6 +53,7 @@ export const MCP_HTTP_EGRESS_TOOLS = {
53
53
  gno_remove_collection: "metadata",
54
54
  gno_rename_note: "metadata",
55
55
  gno_search: "snippet",
56
+ gno_section: "metadata",
56
57
  gno_similar: "snippet",
57
58
  gno_status: "metadata",
58
59
  gno_sync: "metadata",
@@ -67,6 +67,12 @@ import { handleMultiGet } from "./multi-get";
67
67
  import { handleQuery, handleQueryDiagnose } from "./query";
68
68
  import { handleRemoveCollection } from "./remove-collection";
69
69
  import { handleSearch } from "./search";
70
+ import {
71
+ handleSection,
72
+ SECTION_MCP_ANNOTATIONS,
73
+ sectionInputSchema,
74
+ sectionOutputSchema,
75
+ } from "./sections";
70
76
  import { handleStatus } from "./status";
71
77
  import { handleSync } from "./sync";
72
78
  import {
@@ -89,6 +95,10 @@ import {
89
95
  handleDuplicateNote,
90
96
  handleMoveNote,
91
97
  handleRenameNote,
98
+ MOVE_NOTE_MCP_ANNOTATIONS,
99
+ moveNoteInputSchema,
100
+ RENAME_NOTE_MCP_ANNOTATIONS,
101
+ renameNoteInputSchema,
92
102
  } from "./workspace-write";
93
103
 
94
104
  // ─────────────────────────────────────────────────────────────────────────────
@@ -116,6 +126,8 @@ export const MCP_TOOL_DESCRIPTIONS = {
116
126
  get: "Retrieve one document by gno:// URI, docid (#abc123), or collection/path. After search results include line, pass fromLine and lineCount to fetch only the relevant range before expanding to the full document.",
117
127
  multiGet:
118
128
  "Retrieve multiple documents by refs array or glob pattern. Use after gno_search/gno_query to batch top result URIs/docids; set maxBytes and lineNumbers to control context size.",
129
+ section:
130
+ "Create or resolve a durable SectionTargetV1 against one indexed document. action=create needs ref plus exactly one of anchor|line; action=resolve needs ref plus target. Exact/recovered include citation (uri, anchor, title, inclusive lines, fingerprint); ambiguous/stale/missing omit citation and are not safe to navigate or cite. Read-only — does not write or persist targets. Follow navigable ranges with gno_get fromLine/lineCount.",
119
131
  status:
120
132
  "Get index health: collection count, document count, chunk count, embedding backlog, and per-collection stats. Check first when vector/hybrid results look stale or unavailable.",
121
133
  context:
@@ -390,17 +402,6 @@ const createFolderInputSchema = z.object({
390
402
  parentPath: z.string().optional(),
391
403
  });
392
404
 
393
- const renameNoteInputSchema = z.object({
394
- ref: z.string().min(1, "ref cannot be empty"),
395
- name: z.string().min(1, "name cannot be empty"),
396
- });
397
-
398
- const moveNoteInputSchema = z.object({
399
- ref: z.string().min(1, "ref cannot be empty"),
400
- folderPath: z.string().min(1, "folderPath cannot be empty"),
401
- name: z.string().optional(),
402
- });
403
-
404
405
  const duplicateNoteInputSchema = z.object({
405
406
  ref: z.string().min(1, "ref cannot be empty"),
406
407
  folderPath: z.string().optional(),
@@ -1064,6 +1065,17 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
1064
1065
  (args) => handleGet(args, ctx)
1065
1066
  );
1066
1067
 
1068
+ server.registerTool(
1069
+ "gno_section",
1070
+ {
1071
+ description: MCP_TOOL_DESCRIPTIONS.section,
1072
+ inputSchema: sectionInputSchema,
1073
+ outputSchema: sectionOutputSchema,
1074
+ annotations: SECTION_MCP_ANNOTATIONS,
1075
+ },
1076
+ (args) => handleSection(args, ctx)
1077
+ );
1078
+
1067
1079
  server.tool(
1068
1080
  "gno_multi_get",
1069
1081
  MCP_TOOL_DESCRIPTIONS.multiGet,
@@ -1332,17 +1344,25 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
1332
1344
  (args) => handleCreateFolder(args, ctx)
1333
1345
  );
1334
1346
 
1335
- server.tool(
1347
+ server.registerTool(
1336
1348
  "gno_rename_note",
1337
- "Rename an editable note in place.",
1338
- renameNoteInputSchema.shape,
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
+ },
1339
1355
  (args) => handleRenameNote(args, ctx)
1340
1356
  );
1341
1357
 
1342
- server.tool(
1358
+ server.registerTool(
1343
1359
  "gno_move_note",
1344
- "Move an editable note to another folder in the same collection.",
1345
- moveNoteInputSchema.shape,
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
+ },
1346
1366
  (args) => handleMoveNote(args, ctx)
1347
1367
  );
1348
1368