@gmickel/gno 1.34.6 → 1.35.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 (38) hide show
  1. package/README.md +12 -1
  2. package/browser-extension/artifacts/{gno-browser-clipper-v1.34.6.zip → gno-browser-clipper-v1.35.0.zip} +0 -0
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.35.0.zip.sha256 +1 -0
  4. package/browser-extension/dist/manifest.json +1 -1
  5. package/package.json +1 -1
  6. package/spec/cli.md +37 -0
  7. package/spec/output-schemas/mcp-job-status.schema.json +6 -2
  8. package/src/config/index.ts +4 -0
  9. package/src/config/types.ts +14 -0
  10. package/src/core/path-rules.ts +34 -0
  11. package/src/ingestion/index.ts +21 -0
  12. package/src/ingestion/record-container.ts +23 -1
  13. package/src/ingestion/source-availability/darwin-io.ts +295 -0
  14. package/src/ingestion/source-availability/darwin-path.ts +58 -0
  15. package/src/ingestion/source-availability/directory.ts +402 -0
  16. package/src/ingestion/source-availability/index.ts +74 -0
  17. package/src/ingestion/source-availability/readers.ts +360 -0
  18. package/src/ingestion/source-availability/resolve.ts +28 -0
  19. package/src/ingestion/source-availability/types.ts +170 -0
  20. package/src/ingestion/sync.ts +197 -24
  21. package/src/ingestion/types.ts +45 -3
  22. package/src/ingestion/walker.ts +263 -5
  23. package/src/serve/public/globals.built.css +1 -1
  24. package/src/serve/watch-reconciliation-fallback-disk.ts +239 -100
  25. package/src/serve/watch-reconciliation-fallback.ts +35 -5
  26. package/src/serve/watch-reconciliation-shared.ts +8 -3
  27. package/src/serve/watch-reconciliation.ts +7 -0
  28. package/src/serve/watch-service-flush.ts +10 -0
  29. package/src/serve/watch-service-lifecycle.ts +2 -0
  30. package/src/serve/watch-service-snapshot.ts +27 -3
  31. package/src/serve/watch-service.ts +1 -0
  32. package/src/serve/watch-snapshot-availability.ts +51 -0
  33. package/src/serve/watch-snapshot-handles.ts +117 -37
  34. package/src/serve/watch-snapshot-libc.ts +141 -22
  35. package/src/serve/watch-snapshot-ops.ts +151 -9
  36. package/src/serve/watch-snapshot-scan.ts +3 -0
  37. package/src/serve/watch-snapshot-types.ts +45 -3
  38. package/browser-extension/artifacts/gno-browser-clipper-v1.34.6.zip.sha256 +0 -1
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Directory-boundary availability classification for local-mode traversal.
3
+ * Amortized per directory (never a naive per-file availability syscall).
4
+ *
5
+ * @module src/ingestion/source-availability/directory
6
+ */
7
+
8
+ // node:path — Bun has no path join/dirname helpers
9
+ import { dirname, join, relative, sep } from "node:path";
10
+
11
+ import {
12
+ type DarwinIoPolicyPort,
13
+ type DarwinStatPort,
14
+ loadDarwinIo,
15
+ SF_DATALESS,
16
+ withNoMaterializePolicy,
17
+ } from "./darwin-io";
18
+ import {
19
+ classifyDarwinFileProviderPath,
20
+ type DarwinFileProviderPathSupport,
21
+ } from "./darwin-path";
22
+ import {
23
+ type DirectoryAvailabilityPort,
24
+ type DirectoryAvailabilityResult,
25
+ type DirectoryReadResult,
26
+ type SourceAvailabilityCode,
27
+ type SourceAvailabilityMode,
28
+ type SynchronousDirectoryRead,
29
+ isUnprovenAbsenceCode,
30
+ sourceAvailabilityMessage,
31
+ } from "./types";
32
+
33
+ /** Injectable deps for unit tests; production uses loadDarwinIo(). */
34
+ export type LocalDirectoryDeps = {
35
+ platform?: string;
36
+ policy?: DarwinIoPolicyPort | null;
37
+ stat?: DarwinStatPort | null;
38
+ pathSupport?: (absPath: string) => DarwinFileProviderPathSupport;
39
+ };
40
+
41
+ /** `any` mode: directories are always considered available for descent. */
42
+ export class AnyDirectoryAvailability implements DirectoryAvailabilityPort {
43
+ readonly mode: SourceAvailabilityMode = "any";
44
+
45
+ async classify(_absPath: string): Promise<DirectoryAvailabilityResult> {
46
+ return { kind: "available" };
47
+ }
48
+
49
+ readDirectory<T>(
50
+ _absPath: string,
51
+ read: SynchronousDirectoryRead<T>
52
+ ): DirectoryReadResult<T> {
53
+ return { kind: "available", value: read() };
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Local mode: classify directories via SF_DATALESS under no-materialization
59
+ * policy before descent. Fail closed when support/policy cannot be proven.
60
+ */
61
+ export class LocalDirectoryAvailability implements DirectoryAvailabilityPort {
62
+ readonly mode: SourceAvailabilityMode = "local";
63
+ private readonly platform: string;
64
+ private readonly policy: DarwinIoPolicyPort | null;
65
+ private readonly stat: DarwinStatPort | null;
66
+ private readonly pathSupport: (
67
+ absPath: string
68
+ ) => DarwinFileProviderPathSupport;
69
+
70
+ constructor(deps: LocalDirectoryDeps = {}) {
71
+ this.platform = deps.platform ?? process.platform;
72
+ if (deps.policy !== undefined || deps.stat !== undefined) {
73
+ this.policy = deps.policy ?? null;
74
+ this.stat = deps.stat ?? null;
75
+ } else if (this.platform === "darwin") {
76
+ const loaded = loadDarwinIo();
77
+ this.policy = loaded?.policy ?? null;
78
+ this.stat = loaded?.stat ?? null;
79
+ } else {
80
+ this.policy = null;
81
+ this.stat = null;
82
+ }
83
+ this.pathSupport = deps.pathSupport ?? classifyDarwinFileProviderPath;
84
+ }
85
+
86
+ async classify(absPath: string): Promise<DirectoryAvailabilityResult> {
87
+ const supportError = this.validateSupport(absPath);
88
+ if (supportError) {
89
+ return supportError;
90
+ }
91
+
92
+ const policy = this.policy as DarwinIoPolicyPort;
93
+ const stat = this.stat as DarwinStatPort;
94
+ let wrapped: ReturnType<
95
+ typeof withNoMaterializePolicy<DirectoryAvailabilityResult>
96
+ >;
97
+ try {
98
+ wrapped = withNoMaterializePolicy(
99
+ () => classifyFlags(absPath, stat),
100
+ policy
101
+ );
102
+ } catch (error) {
103
+ return {
104
+ kind: "error",
105
+ code: "SOURCE_AVAILABILITY_UNKNOWN",
106
+ message: sourceAvailabilityMessage(
107
+ "SOURCE_AVAILABILITY_UNKNOWN",
108
+ error instanceof Error ? error.message : "directory_classify_failed"
109
+ ),
110
+ };
111
+ }
112
+ if (!wrapped.ok) {
113
+ return {
114
+ kind: "error",
115
+ code: "SOURCE_AVAILABILITY_POLICY_FAILED",
116
+ message: sourceAvailabilityMessage(
117
+ "SOURCE_AVAILABILITY_POLICY_FAILED",
118
+ wrapped.error
119
+ ),
120
+ };
121
+ }
122
+ return wrapped.value;
123
+ }
124
+
125
+ readDirectory<T>(
126
+ absPath: string,
127
+ read: SynchronousDirectoryRead<T>
128
+ ): DirectoryReadResult<T> {
129
+ const supportError = this.validateSupport(absPath);
130
+ if (supportError) {
131
+ return supportError;
132
+ }
133
+
134
+ const policy = this.policy as DarwinIoPolicyPort;
135
+ const stat = this.stat as DarwinStatPort;
136
+ try {
137
+ const wrapped = withNoMaterializePolicy(() => {
138
+ const classified = classifyFlags(absPath, stat);
139
+ if (classified.kind !== "available") {
140
+ return classified;
141
+ }
142
+ return { kind: "available" as const, value: read() };
143
+ }, policy);
144
+ if (wrapped.ok) {
145
+ return wrapped.value;
146
+ }
147
+ return {
148
+ kind: "error",
149
+ code: "SOURCE_AVAILABILITY_POLICY_FAILED",
150
+ message: sourceAvailabilityMessage(
151
+ "SOURCE_AVAILABILITY_POLICY_FAILED",
152
+ wrapped.error
153
+ ),
154
+ };
155
+ } catch (error) {
156
+ return {
157
+ kind: "error",
158
+ code: "SOURCE_AVAILABILITY_UNKNOWN",
159
+ message: sourceAvailabilityMessage(
160
+ "SOURCE_AVAILABILITY_UNKNOWN",
161
+ error instanceof Error ? error.message : "directory_read_failed"
162
+ ),
163
+ };
164
+ }
165
+ }
166
+
167
+ private validateSupport(
168
+ absPath: string
169
+ ): Exclude<DirectoryAvailabilityResult, { kind: "available" }> | null {
170
+ if (this.platform !== "darwin") {
171
+ return {
172
+ kind: "error",
173
+ code: "SOURCE_AVAILABILITY_UNSUPPORTED",
174
+ message: sourceAvailabilityMessage(
175
+ "SOURCE_AVAILABILITY_UNSUPPORTED",
176
+ `platform=${this.platform}`
177
+ ),
178
+ };
179
+ }
180
+ if (!this.policy || !this.stat) {
181
+ return {
182
+ kind: "error",
183
+ code: "SOURCE_AVAILABILITY_POLICY_FAILED",
184
+ message: sourceAvailabilityMessage(
185
+ "SOURCE_AVAILABILITY_POLICY_FAILED",
186
+ "darwin_io_unavailable"
187
+ ),
188
+ };
189
+ }
190
+ const pathSupport = this.pathSupport(absPath);
191
+ if (pathSupport === "unsupported") {
192
+ return {
193
+ kind: "error",
194
+ code: "SOURCE_AVAILABILITY_UNSUPPORTED",
195
+ message: sourceAvailabilityMessage(
196
+ "SOURCE_AVAILABILITY_UNSUPPORTED",
197
+ "path is outside the physically evidenced macOS File Provider layouts"
198
+ ),
199
+ };
200
+ }
201
+ if (pathSupport === "unknown") {
202
+ return {
203
+ kind: "error",
204
+ code: "SOURCE_AVAILABILITY_UNKNOWN",
205
+ message: sourceAvailabilityMessage(
206
+ "SOURCE_AVAILABILITY_UNKNOWN",
207
+ "path support could not be established"
208
+ ),
209
+ };
210
+ }
211
+ return null;
212
+ }
213
+ }
214
+
215
+ function classifyFlags(
216
+ absPath: string,
217
+ stat: DarwinStatPort
218
+ ): DirectoryAvailabilityResult {
219
+ const flags = stat.lstatFlags(absPath);
220
+ if (!flags.ok) {
221
+ const errno = flags.errno;
222
+ if (errno === 13 || errno === 1) {
223
+ return {
224
+ kind: "error",
225
+ code: "PERMISSION",
226
+ message: sourceAvailabilityMessage("PERMISSION", `errno=${errno}`),
227
+ errno,
228
+ };
229
+ }
230
+ if (errno === 2) {
231
+ return {
232
+ kind: "error",
233
+ code: "NOT_FOUND",
234
+ message: sourceAvailabilityMessage("NOT_FOUND", `errno=${errno}`),
235
+ errno,
236
+ };
237
+ }
238
+ return {
239
+ kind: "error",
240
+ code: "SOURCE_AVAILABILITY_UNKNOWN",
241
+ message: sourceAvailabilityMessage(
242
+ "SOURCE_AVAILABILITY_UNKNOWN",
243
+ `lstat_failed errno=${errno}`
244
+ ),
245
+ errno,
246
+ };
247
+ }
248
+ if ((flags.stFlags & SF_DATALESS) !== 0) {
249
+ return {
250
+ kind: "dataless",
251
+ code: "DATALESS_DIRECTORY",
252
+ message: sourceAvailabilityMessage(
253
+ "DATALESS_DIRECTORY",
254
+ `st_flags=${flags.stFlags}`
255
+ ),
256
+ };
257
+ }
258
+ return { kind: "available" };
259
+ }
260
+
261
+ export function createDirectoryAvailability(
262
+ mode: SourceAvailabilityMode,
263
+ deps: LocalDirectoryDeps = {}
264
+ ): DirectoryAvailabilityPort {
265
+ if (mode === "any") {
266
+ return new AnyDirectoryAvailability();
267
+ }
268
+ return new LocalDirectoryAvailability(deps);
269
+ }
270
+
271
+ /** Cache one operation's directory classifications by absolute path. */
272
+ export function memoizeDirectoryAvailability(
273
+ port: DirectoryAvailabilityPort
274
+ ): DirectoryAvailabilityPort {
275
+ if (port.mode === "any") {
276
+ return port;
277
+ }
278
+ const cache = new Map<string, Promise<DirectoryAvailabilityResult>>();
279
+ return {
280
+ mode: port.mode,
281
+ classify: (absPath: string) => {
282
+ const cached = cache.get(absPath);
283
+ if (cached) {
284
+ return cached;
285
+ }
286
+ const pending = port.classify(absPath);
287
+ cache.set(absPath, pending);
288
+ return pending;
289
+ },
290
+ readDirectory: (absPath, read) => port.readDirectory(absPath, read),
291
+ };
292
+ }
293
+
294
+ /** True when a directory classification must refuse descent and preserve index. */
295
+ export function isUnprovenDirectoryResult(
296
+ result: DirectoryAvailabilityResult
297
+ ): result is Exclude<DirectoryAvailabilityResult, { kind: "available" }> {
298
+ if (result.kind === "dataless") {
299
+ return true;
300
+ }
301
+ if (result.kind === "error") {
302
+ return isUnprovenAbsenceCode(result.code);
303
+ }
304
+ return false;
305
+ }
306
+
307
+ export function directoryResultCode(
308
+ result: Exclude<DirectoryAvailabilityResult, { kind: "available" }>
309
+ ): SourceAvailabilityCode {
310
+ return result.code;
311
+ }
312
+
313
+ /**
314
+ * Walk ancestors from collection root to the parent of `relPath`, classifying
315
+ * each directory once. Returns the first unproven prefix, if any.
316
+ */
317
+ export async function findUnprovenAvailabilityPrefix(
318
+ rootAbs: string,
319
+ relPath: string,
320
+ classifier: DirectoryAvailabilityPort
321
+ ): Promise<{
322
+ absPath: string;
323
+ relPath: string;
324
+ code: SourceAvailabilityCode;
325
+ message: string;
326
+ } | null> {
327
+ if (classifier.mode === "any") {
328
+ return null;
329
+ }
330
+
331
+ const normalized = relPath.replaceAll("\\", "/").replace(/^\/+/, "");
332
+ const segments =
333
+ normalized.length === 0
334
+ ? []
335
+ : normalized.split("/").filter((segment) => segment.length > 0);
336
+
337
+ // Include root and each intermediate directory; exclude the leaf file name.
338
+ const dirRels: string[] = [""];
339
+ for (let index = 0; index < Math.max(0, segments.length - 1); index += 1) {
340
+ const next = segments.slice(0, index + 1).join("/");
341
+ dirRels.push(next);
342
+ }
343
+
344
+ for (const dirRel of dirRels) {
345
+ const absPath = dirRel === "" ? rootAbs : join(rootAbs, dirRel);
346
+ const classified = await classifier.classify(absPath);
347
+ if (classified.kind === "available") {
348
+ continue;
349
+ }
350
+ return {
351
+ absPath,
352
+ relPath: dirRel,
353
+ code: directoryResultCode(classified),
354
+ message: classified.message,
355
+ };
356
+ }
357
+ return null;
358
+ }
359
+
360
+ /** True when `relPath` is exactly `prefix` or a descendant of it. */
361
+ export function relPathUnderPrefix(relPath: string, prefix: string): boolean {
362
+ const path = relPath.replaceAll("\\", "/");
363
+ const base = prefix.replaceAll("\\", "/");
364
+ if (base === "") {
365
+ return true;
366
+ }
367
+ return path === base || path.startsWith(`${base}/`);
368
+ }
369
+
370
+ export function relPathUnderAnyPrefix(
371
+ relPath: string,
372
+ prefixes: readonly string[]
373
+ ): boolean {
374
+ for (const prefix of prefixes) {
375
+ if (relPathUnderPrefix(relPath, prefix)) {
376
+ return true;
377
+ }
378
+ }
379
+ return false;
380
+ }
381
+
382
+ /** Parent collection-relative directory of a file path (`""` for root files). */
383
+ export function parentRelDir(relPath: string): string {
384
+ const normalized = relPath.replaceAll("\\", "/");
385
+ const parent = dirname(normalized);
386
+ if (parent === "." || parent === sep) {
387
+ return "";
388
+ }
389
+ return parent === "\\" ? "" : parent.replaceAll("\\", "/");
390
+ }
391
+
392
+ /** Relative path of `absPath` under `rootAbs`, or null when outside. */
393
+ export function posixRelUnderRoot(
394
+ rootAbs: string,
395
+ absPath: string
396
+ ): string | null {
397
+ const rel = relative(rootAbs, absPath);
398
+ if (rel === ".." || rel.startsWith(`..${sep}`) || rel.startsWith("/")) {
399
+ return null;
400
+ }
401
+ return rel.split(sep).join("/");
402
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Source-availability policy and guarded content-read boundary.
3
+ *
4
+ * @module src/ingestion/source-availability
5
+ */
6
+
7
+ export {
8
+ DARWIN_EACCES,
9
+ DARWIN_EDEADLK,
10
+ DARWIN_ENOENT,
11
+ DARWIN_ELOOP,
12
+ DARWIN_EPERM,
13
+ DARWIN_STAT_BUF_SIZE,
14
+ DARWIN_ST_FLAGS_OFFSET,
15
+ classifyGuardedReadErrno,
16
+ IOPOL_MATERIALIZE_DATALESS_FILES_OFF,
17
+ IOPOL_SCOPE_PROCESS,
18
+ IOPOL_TYPE_VFS_MATERIALIZE_DATALESS_FILES,
19
+ loadDarwinIo,
20
+ resetDarwinIoCachesForTests,
21
+ SF_DATALESS,
22
+ withNoMaterializePolicy,
23
+ } from "./darwin-io";
24
+ export type {
25
+ DarwinFileIoPort,
26
+ DarwinIoBundle,
27
+ DarwinIoPolicyPort,
28
+ DarwinStatPort,
29
+ } from "./darwin-io";
30
+ export { classifyDarwinFileProviderPath } from "./darwin-path";
31
+ export type { DarwinFileProviderPathSupport } from "./darwin-path";
32
+ export {
33
+ AnyDirectoryAvailability,
34
+ createDirectoryAvailability,
35
+ findUnprovenAvailabilityPrefix,
36
+ isUnprovenDirectoryResult,
37
+ LocalDirectoryAvailability,
38
+ memoizeDirectoryAvailability,
39
+ parentRelDir,
40
+ posixRelUnderRoot,
41
+ relPathUnderAnyPrefix,
42
+ relPathUnderPrefix,
43
+ } from "./directory";
44
+ export type { LocalDirectoryDeps } from "./directory";
45
+ export {
46
+ AnySourceContentReader,
47
+ bytesAsAsyncIterable,
48
+ createSourceContentReader,
49
+ LocalSourceContentReader,
50
+ } from "./readers";
51
+ export type { LocalReaderDeps } from "./readers";
52
+ export { resolveSourceAvailability } from "./resolve";
53
+ export {
54
+ DEFAULT_SOURCE_AVAILABILITY,
55
+ isSourceAvailabilitySkip,
56
+ isUnprovenAbsenceCode,
57
+ SOURCE_AVAILABILITY_CODES,
58
+ SOURCE_AVAILABILITY_MODES,
59
+ SOURCE_AVAILABILITY_SKIP_CODES,
60
+ SOURCE_AVAILABILITY_UNPROVEN_PREFIX_CODES,
61
+ sourceAvailabilityMessage,
62
+ } from "./types";
63
+ export type {
64
+ DirectoryAvailabilityPort,
65
+ DirectoryAvailabilityResult,
66
+ DirectoryReadResult,
67
+ SourceAvailabilityCode,
68
+ SourceAvailabilityMode,
69
+ SourceContentReaderPort,
70
+ SourceReadFailure,
71
+ SourceReadResult,
72
+ SourceReadSuccess,
73
+ SynchronousDirectoryRead,
74
+ } from "./types";