@gotcos/glasses-server 6.22.0 → 6.22.1
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/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/server/lib/context-files.ts +51 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 6.22.1
|
|
2
|
+
|
|
3
|
+
Notes attached from somewhere else by a symlink are now read properly. Found by
|
|
4
|
+
Queen within hours of 6.22.0, on the very first real setup.
|
|
5
|
+
|
|
6
|
+
- **A symlinked subfolder or file was silently skipped.** `readdirSync` reports a
|
|
7
|
+
symlink as `isSymbolicLink()`, never as `isDirectory()` or `isFile()`, so the walk
|
|
8
|
+
ignored every link it met. A top-level `memory -> /elsewhere` link worked only by
|
|
9
|
+
accident, because the folder LOOKUP uses `statSync` and follows links while the
|
|
10
|
+
walk did not. Anything linked one level deeper vanished with no error at all.
|
|
11
|
+
- Attaching an existing store is a primary way to adopt this feature, not an edge
|
|
12
|
+
case, so a link now behaves like whatever it points at: a linked folder is walked,
|
|
13
|
+
a linked note is read, and a broken link is skipped without failing the read.
|
|
14
|
+
- **Cycles terminate.** Following links makes `memory/loop -> memory` fatal, so every
|
|
15
|
+
directory is now visited once by resolved real path. The same identity check stops
|
|
16
|
+
a store reached through two different links being counted twice.
|
|
17
|
+
- **No depth limit.** An earlier draft of this fix capped nesting at 16 levels; that
|
|
18
|
+
guarded nothing real (cycles terminate on identity, and the file cap already bounds
|
|
19
|
+
the work) while silently hiding notes nested deeper. Removed.
|
|
20
|
+
- The status counter and the reader walk the same way, so the header count and the
|
|
21
|
+
list can no longer disagree.
|
|
22
|
+
|
|
1
23
|
## 6.22.0
|
|
2
24
|
|
|
3
25
|
Memory and Threads now work without a Python bridge, a venv, or a vector
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// vector store. The files were always the substrate — which is exactly why an
|
|
20
20
|
// amendment must write only here and never to the derived store.
|
|
21
21
|
|
|
22
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
22
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs'
|
|
23
23
|
import { homedir } from 'node:os'
|
|
24
24
|
import { basename, dirname, extname, join, resolve } from 'node:path'
|
|
25
25
|
|
|
@@ -85,26 +85,72 @@ function findDir(root: string, names: readonly string[]): string | null {
|
|
|
85
85
|
return null
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** Extensions the readers treat as notes. */
|
|
89
|
+
const NOTE_EXTENSIONS = ['.md', '.markdown', '.txt']
|
|
90
|
+
|
|
88
91
|
/**
|
|
89
|
-
* Every markdown file under `dir`, recursively, bounded.
|
|
92
|
+
* Every markdown file under `dir`, recursively, bounded, FOLLOWING SYMLINKS.
|
|
90
93
|
*
|
|
91
94
|
* Any nesting is accepted on purpose — a user's notes may be organised by year, by
|
|
92
95
|
* project, or not at all, and rejecting a shape is what made COS Data unusable for
|
|
93
96
|
* anyone but its author.
|
|
97
|
+
*
|
|
98
|
+
* SYMLINKS ARE FIRST-CLASS, and that is a correction. A `Dirent` reports a symlink
|
|
99
|
+
* as `isSymbolicLink()`, NOT as `isDirectory()` or `isFile()`, so the first version
|
|
100
|
+
* of this walk silently skipped every linked file and linked subfolder. Queen hit
|
|
101
|
+
* exactly that on 2026-08-09: she wired `operations/memory` at her real note store
|
|
102
|
+
* with a symlink, which worked only because `findDir` uses `statSync` and follows
|
|
103
|
+
* links — anything linked one level deeper would have vanished with no error.
|
|
104
|
+
*
|
|
105
|
+
* Attaching notes that live somewhere else is a PRIMARY use case, not an edge case,
|
|
106
|
+
* so a link has to behave like the thing it points at.
|
|
107
|
+
*
|
|
108
|
+
* Cycles are the cost of following them: `a -> ../a` recurses forever. Every
|
|
109
|
+
* directory is recorded by its resolved real path and visited once, which also
|
|
110
|
+
* stops the same store being counted twice when two links reach it.
|
|
111
|
+
*
|
|
112
|
+
* There is deliberately NO depth limit. An earlier draft capped nesting at 16 as
|
|
113
|
+
* "bounding a pathological tree", but real-path identity already makes cycles
|
|
114
|
+
* terminate and MAX_FILES_SCANNED already bounds the work — so the cap's only real
|
|
115
|
+
* effect was to silently hide notes nested deeper than that. That is the same
|
|
116
|
+
* invisible-skip defect this function was just fixed for, moved further out, and a
|
|
117
|
+
* mutation removing the cap changed no test, which is how it got noticed.
|
|
94
118
|
*/
|
|
95
|
-
function walkMarkdown(
|
|
119
|
+
function walkMarkdown(
|
|
120
|
+
dir: string,
|
|
121
|
+
budget = { left: MAX_FILES_SCANNED },
|
|
122
|
+
seen: Set<string> = new Set(),
|
|
123
|
+
): string[] {
|
|
96
124
|
const out: string[] = []
|
|
125
|
+
// Identity by real path, so a link and its target are the same directory.
|
|
126
|
+
let real: string
|
|
127
|
+
try { real = realpathSync(dir) } catch { return out }
|
|
128
|
+
if (seen.has(real)) return out
|
|
129
|
+
seen.add(real)
|
|
130
|
+
|
|
97
131
|
let entries: import('node:fs').Dirent[]
|
|
98
132
|
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return out }
|
|
99
133
|
for (const entry of entries) {
|
|
100
134
|
if (budget.left <= 0) break
|
|
101
135
|
if (entry.name.startsWith('.')) continue
|
|
102
136
|
const full = join(dir, entry.name)
|
|
137
|
+
const isNote = NOTE_EXTENSIONS.includes(extname(entry.name).toLowerCase())
|
|
103
138
|
if (entry.isDirectory()) {
|
|
104
|
-
out.push(...walkMarkdown(full, budget))
|
|
105
|
-
} else if (entry.isFile()
|
|
139
|
+
out.push(...walkMarkdown(full, budget, seen))
|
|
140
|
+
} else if (entry.isFile()) {
|
|
141
|
+
if (!isNote) continue
|
|
106
142
|
budget.left -= 1
|
|
107
143
|
out.push(full)
|
|
144
|
+
} else if (entry.isSymbolicLink()) {
|
|
145
|
+
// Resolve to decide: a linked folder is walked, a linked note is read.
|
|
146
|
+
let target: import('node:fs').Stats
|
|
147
|
+
try { target = statSync(full) } catch { continue } // broken link, skip quietly
|
|
148
|
+
if (target.isDirectory()) {
|
|
149
|
+
out.push(...walkMarkdown(full, budget, seen))
|
|
150
|
+
} else if (target.isFile() && isNote) {
|
|
151
|
+
budget.left -= 1
|
|
152
|
+
out.push(full)
|
|
153
|
+
}
|
|
108
154
|
}
|
|
109
155
|
}
|
|
110
156
|
return out
|