@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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "gno://schemas/section-target-create-result@1.0",
|
|
4
|
+
"title": "GNO Section Target Create Result",
|
|
5
|
+
"description": "Response from creating a durable SectionTargetV1 against a stored document. Canonical uri always comes from the resolved document.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["uri", "target"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"uri": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"maxLength": 1024,
|
|
14
|
+
"description": "Canonical stored document URI"
|
|
15
|
+
},
|
|
16
|
+
"target": {
|
|
17
|
+
"$ref": "gno://schemas/section-target@1.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "gno://schemas/section-target-resolve-result@1.0",
|
|
4
|
+
"title": "GNO Section Target Resolve Result",
|
|
5
|
+
"description": "Conservative section-target resolution result shared by REST and SDK. Citation is present only for navigable exact/recovered statuses. Diagnostics candidates are filtered/capped without truncating identity strings.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"required": [
|
|
11
|
+
"uri",
|
|
12
|
+
"status",
|
|
13
|
+
"currentFingerprint",
|
|
14
|
+
"target",
|
|
15
|
+
"diagnostics",
|
|
16
|
+
"citation"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"uri": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"minLength": 1,
|
|
22
|
+
"maxLength": 1024
|
|
23
|
+
},
|
|
24
|
+
"status": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"enum": ["exact", "recovered"]
|
|
27
|
+
},
|
|
28
|
+
"currentFingerprint": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
31
|
+
},
|
|
32
|
+
"target": {
|
|
33
|
+
"$ref": "gno://schemas/section-target@1.0"
|
|
34
|
+
},
|
|
35
|
+
"diagnostics": {
|
|
36
|
+
"$ref": "#/definitions/diagnostics"
|
|
37
|
+
},
|
|
38
|
+
"citation": {
|
|
39
|
+
"$ref": "#/definitions/citation"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"required": [
|
|
47
|
+
"uri",
|
|
48
|
+
"status",
|
|
49
|
+
"currentFingerprint",
|
|
50
|
+
"target",
|
|
51
|
+
"diagnostics"
|
|
52
|
+
],
|
|
53
|
+
"properties": {
|
|
54
|
+
"uri": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"minLength": 1,
|
|
57
|
+
"maxLength": 1024
|
|
58
|
+
},
|
|
59
|
+
"status": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"enum": ["ambiguous", "stale", "missing"]
|
|
62
|
+
},
|
|
63
|
+
"currentFingerprint": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
66
|
+
},
|
|
67
|
+
"target": {
|
|
68
|
+
"$ref": "gno://schemas/section-target@1.0"
|
|
69
|
+
},
|
|
70
|
+
"diagnostics": {
|
|
71
|
+
"$ref": "#/definitions/diagnostics"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"definitions": {
|
|
77
|
+
"safePositiveInteger": {
|
|
78
|
+
"type": "integer",
|
|
79
|
+
"minimum": 1,
|
|
80
|
+
"maximum": 9007199254740991
|
|
81
|
+
},
|
|
82
|
+
"safeNonNegativeInteger": {
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"minimum": 0,
|
|
85
|
+
"maximum": 9007199254740991
|
|
86
|
+
},
|
|
87
|
+
"diagnostics": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"additionalProperties": false,
|
|
90
|
+
"properties": {
|
|
91
|
+
"reason": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 1,
|
|
94
|
+
"maxLength": 256
|
|
95
|
+
},
|
|
96
|
+
"candidates": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"maxItems": 32,
|
|
99
|
+
"items": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"additionalProperties": false,
|
|
102
|
+
"required": [
|
|
103
|
+
"anchor",
|
|
104
|
+
"line",
|
|
105
|
+
"title",
|
|
106
|
+
"headingPath",
|
|
107
|
+
"occurrence"
|
|
108
|
+
],
|
|
109
|
+
"properties": {
|
|
110
|
+
"anchor": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"minLength": 1,
|
|
113
|
+
"maxLength": 512
|
|
114
|
+
},
|
|
115
|
+
"line": {
|
|
116
|
+
"$ref": "#/definitions/safePositiveInteger"
|
|
117
|
+
},
|
|
118
|
+
"title": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"minLength": 1,
|
|
121
|
+
"maxLength": 512
|
|
122
|
+
},
|
|
123
|
+
"headingPath": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"minItems": 1,
|
|
126
|
+
"maxItems": 6,
|
|
127
|
+
"items": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"minLength": 1,
|
|
130
|
+
"maxLength": 512
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"occurrence": {
|
|
134
|
+
"$ref": "#/definitions/safePositiveInteger"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"candidateCount": {
|
|
140
|
+
"$ref": "#/definitions/safeNonNegativeInteger",
|
|
141
|
+
"description": "Total candidate count before transport filtering/capping"
|
|
142
|
+
},
|
|
143
|
+
"candidatesTruncated": {
|
|
144
|
+
"type": "boolean",
|
|
145
|
+
"description": "True when any candidate was omitted by bounds filtering or the 32-item cap"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"dependencies": {
|
|
149
|
+
"candidates": ["candidateCount", "candidatesTruncated"],
|
|
150
|
+
"candidateCount": ["candidatesTruncated", "candidates"],
|
|
151
|
+
"candidatesTruncated": ["candidateCount", "candidates"]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"citation": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"additionalProperties": false,
|
|
157
|
+
"required": [
|
|
158
|
+
"uri",
|
|
159
|
+
"anchor",
|
|
160
|
+
"title",
|
|
161
|
+
"lineStart",
|
|
162
|
+
"lineEnd",
|
|
163
|
+
"sourceFingerprint"
|
|
164
|
+
],
|
|
165
|
+
"properties": {
|
|
166
|
+
"uri": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"minLength": 1,
|
|
169
|
+
"maxLength": 1024
|
|
170
|
+
},
|
|
171
|
+
"anchor": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"minLength": 1,
|
|
174
|
+
"maxLength": 512
|
|
175
|
+
},
|
|
176
|
+
"title": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"minLength": 1,
|
|
179
|
+
"maxLength": 512
|
|
180
|
+
},
|
|
181
|
+
"lineStart": {
|
|
182
|
+
"$ref": "#/definitions/safePositiveInteger"
|
|
183
|
+
},
|
|
184
|
+
"lineEnd": {
|
|
185
|
+
"$ref": "#/definitions/safePositiveInteger"
|
|
186
|
+
},
|
|
187
|
+
"sourceFingerprint": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "gno://schemas/section-target@1.0",
|
|
4
|
+
"title": "GNO Section Target V1",
|
|
5
|
+
"description": "Bounded, versioned, content-derived section locator for conservative citation recovery. Does not embed full section bodies.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"schemaVersion",
|
|
10
|
+
"document",
|
|
11
|
+
"anchor",
|
|
12
|
+
"headingPath",
|
|
13
|
+
"occurrence",
|
|
14
|
+
"quote",
|
|
15
|
+
"sourceFingerprint",
|
|
16
|
+
"hints"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"schemaVersion": {
|
|
20
|
+
"const": "1",
|
|
21
|
+
"description": "Section target schema version"
|
|
22
|
+
},
|
|
23
|
+
"document": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["uri"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"uri": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1,
|
|
31
|
+
"maxLength": 1024,
|
|
32
|
+
"description": "Canonical document URI"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"anchor": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"minLength": 1,
|
|
39
|
+
"maxLength": 512,
|
|
40
|
+
"description": "Human-readable heading anchor at capture time"
|
|
41
|
+
},
|
|
42
|
+
"headingPath": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"description": "NFC-normalized heading ancestry including the section title",
|
|
45
|
+
"minItems": 1,
|
|
46
|
+
"maxItems": 6,
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"minLength": 1,
|
|
50
|
+
"maxLength": 512
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"occurrence": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"minimum": 1,
|
|
56
|
+
"maximum": 9007199254740991,
|
|
57
|
+
"description": "1-based occurrence among sections with the same headingPath"
|
|
58
|
+
},
|
|
59
|
+
"quote": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"required": ["exact", "prefix", "suffix"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"exact": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"maxLength": 96,
|
|
67
|
+
"description": "Bounded exact quote (section body preferred, else heading title)"
|
|
68
|
+
},
|
|
69
|
+
"prefix": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"maxLength": 32,
|
|
72
|
+
"description": "Bounded prefix context immediately before exact"
|
|
73
|
+
},
|
|
74
|
+
"suffix": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"maxLength": 32,
|
|
77
|
+
"description": "Bounded suffix context immediately after exact"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"sourceFingerprint": {
|
|
82
|
+
"$ref": "#/definitions/sha256",
|
|
83
|
+
"description": "SHA-256 hex fingerprint of the full source document content at capture"
|
|
84
|
+
},
|
|
85
|
+
"hints": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"required": ["line", "startOffset", "endOffset"],
|
|
89
|
+
"description": "Line and code-unit offset hints only; never identity",
|
|
90
|
+
"properties": {
|
|
91
|
+
"line": {
|
|
92
|
+
"type": "integer",
|
|
93
|
+
"minimum": 1,
|
|
94
|
+
"maximum": 9007199254740991,
|
|
95
|
+
"description": "1-based heading line at capture"
|
|
96
|
+
},
|
|
97
|
+
"startOffset": {
|
|
98
|
+
"type": "integer",
|
|
99
|
+
"minimum": 0,
|
|
100
|
+
"maximum": 9007199254740991,
|
|
101
|
+
"description": "UTF-16 code-unit start offset of the exact quote at capture"
|
|
102
|
+
},
|
|
103
|
+
"endOffset": {
|
|
104
|
+
"type": "integer",
|
|
105
|
+
"minimum": 0,
|
|
106
|
+
"maximum": 9007199254740991,
|
|
107
|
+
"description": "UTF-16 code-unit end offset of the exact quote at capture"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"definitions": {
|
|
113
|
+
"sha256": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "gno://schemas/section@1.0",
|
|
4
|
+
"title": "GNO MCP Section Tool Result",
|
|
5
|
+
"description": "Action-discriminated MCP gno_section structured output. Create returns the shared create-result semantics; resolve returns the shared resolve-result semantics (citation only for exact/recovered).",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"required": ["schemaVersion", "action", "uri", "target"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"schemaVersion": {
|
|
13
|
+
"const": "1.0"
|
|
14
|
+
},
|
|
15
|
+
"action": {
|
|
16
|
+
"const": "create"
|
|
17
|
+
},
|
|
18
|
+
"uri": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"minLength": 1,
|
|
21
|
+
"maxLength": 1024
|
|
22
|
+
},
|
|
23
|
+
"target": {
|
|
24
|
+
"$ref": "gno://schemas/section-target@1.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"required": [
|
|
32
|
+
"schemaVersion",
|
|
33
|
+
"action",
|
|
34
|
+
"uri",
|
|
35
|
+
"status",
|
|
36
|
+
"currentFingerprint",
|
|
37
|
+
"target",
|
|
38
|
+
"diagnostics",
|
|
39
|
+
"citation"
|
|
40
|
+
],
|
|
41
|
+
"properties": {
|
|
42
|
+
"schemaVersion": {
|
|
43
|
+
"const": "1.0"
|
|
44
|
+
},
|
|
45
|
+
"action": {
|
|
46
|
+
"const": "resolve"
|
|
47
|
+
},
|
|
48
|
+
"uri": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"minLength": 1,
|
|
51
|
+
"maxLength": 1024
|
|
52
|
+
},
|
|
53
|
+
"status": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"enum": ["exact", "recovered"]
|
|
56
|
+
},
|
|
57
|
+
"currentFingerprint": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
60
|
+
},
|
|
61
|
+
"target": {
|
|
62
|
+
"$ref": "gno://schemas/section-target@1.0"
|
|
63
|
+
},
|
|
64
|
+
"diagnostics": {
|
|
65
|
+
"$ref": "gno://schemas/section-target-resolve-result@1.0#/definitions/diagnostics"
|
|
66
|
+
},
|
|
67
|
+
"citation": {
|
|
68
|
+
"$ref": "gno://schemas/section-target-resolve-result@1.0#/definitions/citation"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type": "object",
|
|
74
|
+
"additionalProperties": false,
|
|
75
|
+
"required": [
|
|
76
|
+
"schemaVersion",
|
|
77
|
+
"action",
|
|
78
|
+
"uri",
|
|
79
|
+
"status",
|
|
80
|
+
"currentFingerprint",
|
|
81
|
+
"target",
|
|
82
|
+
"diagnostics"
|
|
83
|
+
],
|
|
84
|
+
"properties": {
|
|
85
|
+
"schemaVersion": {
|
|
86
|
+
"const": "1.0"
|
|
87
|
+
},
|
|
88
|
+
"action": {
|
|
89
|
+
"const": "resolve"
|
|
90
|
+
},
|
|
91
|
+
"uri": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 1,
|
|
94
|
+
"maxLength": 1024
|
|
95
|
+
},
|
|
96
|
+
"status": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"enum": ["ambiguous", "stale", "missing"]
|
|
99
|
+
},
|
|
100
|
+
"currentFingerprint": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
103
|
+
},
|
|
104
|
+
"target": {
|
|
105
|
+
"$ref": "gno://schemas/section-target@1.0"
|
|
106
|
+
},
|
|
107
|
+
"diagnostics": {
|
|
108
|
+
"$ref": "gno://schemas/section-target-resolve-result@1.0#/definitions/diagnostics"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
@@ -24,6 +24,19 @@ function isTextLikeMime(mime: string): boolean {
|
|
|
24
24
|
return mime.startsWith("text/");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* True when a document may carry markdown/wiki/HTML references in mirror text.
|
|
29
|
+
* Used by refactor inventory completeness to fail closed on missing mirrors.
|
|
30
|
+
* Non-text binaries (pdf/images/…) are omitted — they cannot hold those refs.
|
|
31
|
+
*/
|
|
32
|
+
export function isTextLikeReferenceDocument(
|
|
33
|
+
sourceExt: string,
|
|
34
|
+
sourceMime: string
|
|
35
|
+
): boolean {
|
|
36
|
+
const ext = sourceExt.toLowerCase();
|
|
37
|
+
return EDITABLE_EXTENSIONS.has(ext) || isTextLikeMime(sourceMime);
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
export function getDocumentCapabilities(input: {
|
|
28
41
|
sourceExt: string;
|
|
29
42
|
sourceMime: string;
|
package/src/core/file-ops.ts
CHANGED
|
@@ -27,13 +27,25 @@ export async function atomicWrite(
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
type AtomicCreateContent =
|
|
31
|
+
| string
|
|
32
|
+
| Blob
|
|
33
|
+
| ArrayBuffer
|
|
34
|
+
| Uint8Array
|
|
35
|
+
| ReturnType<typeof Bun.file>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Exclusively create `path` via temp write + hard-link.
|
|
39
|
+
* EEXIST (including a pre-existing symlink) fails closed without following it.
|
|
40
|
+
*/
|
|
30
41
|
export async function atomicCreate(
|
|
31
42
|
path: string,
|
|
32
|
-
content:
|
|
43
|
+
content: AtomicCreateContent
|
|
33
44
|
): Promise<void> {
|
|
34
45
|
const tempPath = `${path}.tmp.${crypto.randomUUID()}`;
|
|
35
46
|
await Bun.write(tempPath, content);
|
|
36
47
|
try {
|
|
48
|
+
// node:fs/promises link — exclusive create; no Bun equivalent
|
|
37
49
|
await link(tempPath, path);
|
|
38
50
|
} catch (e) {
|
|
39
51
|
await unlink(tempPath).catch(() => {
|
|
@@ -78,6 +90,122 @@ export async function createFolderPath(path: string): Promise<void> {
|
|
|
78
90
|
await mkdir(path, { recursive: true });
|
|
79
91
|
}
|
|
80
92
|
|
|
93
|
+
/** Sibling staging/backup path beside a user file (never under a different root). */
|
|
94
|
+
export function siblingRefactorPath(
|
|
95
|
+
path: string,
|
|
96
|
+
kind: "stage" | "backup",
|
|
97
|
+
token: string
|
|
98
|
+
): string {
|
|
99
|
+
return `${path}.gno-rf-${kind}.${token}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Exclusively create a stage artifact. Pre-existing symlink/file at stagePath
|
|
104
|
+
* fails closed (EEXIST) and is never followed/overwritten.
|
|
105
|
+
*/
|
|
106
|
+
export async function writeStagedFileContent(
|
|
107
|
+
stagePath: string,
|
|
108
|
+
content: string
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
await atomicCreate(stagePath, content);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Exclusively copy live bytes into a sibling backup path.
|
|
115
|
+
* Pre-existing symlink/file at backupPath fails closed (EEXIST).
|
|
116
|
+
*/
|
|
117
|
+
export async function backupFileToSibling(
|
|
118
|
+
sourcePath: string,
|
|
119
|
+
backupPath: string
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
await atomicCreate(backupPath, Bun.file(sourcePath));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Replace an existing path from a staged sibling via rename.
|
|
126
|
+
* Callers must verify live fingerprints before invoking.
|
|
127
|
+
*/
|
|
128
|
+
export async function commitStagedFileReplace(
|
|
129
|
+
stagePath: string,
|
|
130
|
+
targetPath: string
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
await rename(stagePath, targetPath);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a new target from a staged sibling without overwrite.
|
|
137
|
+
* Uses hard-link create semantics so an occupied target fails closed.
|
|
138
|
+
*/
|
|
139
|
+
export async function commitStagedFileExclusive(
|
|
140
|
+
stagePath: string,
|
|
141
|
+
targetPath: string
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
// node:fs/promises link — exclusive create; no Bun equivalent
|
|
144
|
+
await link(stagePath, targetPath);
|
|
145
|
+
await unlink(stagePath).catch(() => {
|
|
146
|
+
/* stage cleanup best-effort after durable target exists */
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** @deprecated Prefer commitStagedFileReplace / commitStagedFileExclusive. */
|
|
151
|
+
export async function commitStagedFile(
|
|
152
|
+
stagePath: string,
|
|
153
|
+
targetPath: string
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
await commitStagedFileReplace(stagePath, targetPath);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Restore original bytes by replacing the target path itself (rename),
|
|
160
|
+
* never following a symlink that may have been swapped onto the target.
|
|
161
|
+
*/
|
|
162
|
+
export async function restoreFileFromBackup(
|
|
163
|
+
backupPath: string,
|
|
164
|
+
targetPath: string
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const tempPath = `${targetPath}.gno-rf-restore.${crypto.randomUUID()}`;
|
|
167
|
+
await Bun.write(tempPath, Bun.file(backupPath));
|
|
168
|
+
try {
|
|
169
|
+
// node:fs/promises rename — replaces the directory entry (symlink-safe)
|
|
170
|
+
await rename(tempPath, targetPath);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
await unlink(tempPath).catch(() => {
|
|
173
|
+
/* ignore cleanup errors */
|
|
174
|
+
});
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isEnoent(error: unknown): boolean {
|
|
180
|
+
return (
|
|
181
|
+
error instanceof Error &&
|
|
182
|
+
"code" in error &&
|
|
183
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Best-effort cleanup removal; ignores all errors (never used for commit). */
|
|
188
|
+
export async function removePathIfExists(path: string): Promise<void> {
|
|
189
|
+
// node:fs/promises unlink — structure op, no Bun equivalent
|
|
190
|
+
await unlink(path).catch(() => {
|
|
191
|
+
/* ignore cleanup errors */
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Required removal: ignores only ENOENT. Permission/I/O failures throw so
|
|
197
|
+
* callers cannot report success with both source and target present.
|
|
198
|
+
*/
|
|
199
|
+
export async function removePathRequired(path: string): Promise<void> {
|
|
200
|
+
// node:fs/promises unlink — structure op, no Bun equivalent
|
|
201
|
+
try {
|
|
202
|
+
await unlink(path);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (isEnoent(error)) return;
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
81
209
|
type TrashFileDeps = {
|
|
82
210
|
homeDir?: string;
|
|
83
211
|
platform?: ReturnType<typeof getPlatform>;
|