pi-weave 0.1.10 → 0.1.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # pi-weave
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/EranYonai/pi-weave/main/docs/pi-weave-logo.png" alt="pi-weave — an agent-native knowledge workspace" width="220"/>
4
+ <img src="https://raw.githubusercontent.com/EranYonai/pi-weave/main/docs/pi-weave-logo.png" alt="pi-weave" width="220"/>
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -11,190 +11,235 @@
11
11
  <a href="./LICENSE"><img alt="license: MIT" src="https://img.shields.io/npm/l/pi-weave?color=green"></a>
12
12
  </p>
13
13
 
14
- **An agent-native knowledge workspace for your life and your code.**
14
+ **A local knowledge workspace you can talk to.**
15
15
 
16
- pi-weave is a [pi](https://github.com/earendil-works/pi) extension with two faces that are secretly one:
16
+ pi-weave is an extension for [pi](https://github.com/earendil-works/pi). Ask Pi to take notes while you think out loud, keep your exact
17
+ words alongside a useful summary, and explore everything in `/weave-view`.
17
18
 
18
- 1. **A smart notepad.** A persistent vault of knowledge decisions, ideas, people, meetings stored as plain Markdown notes with YAML
19
- front matter under `~/.okf/notes/`. Your agent reads and writes it *with* you through the `weave_note` tool; everything stays editable by
20
- hand in any editor.
19
+ It also understands the repository you are working in. Personal notes live in a Markdown vault; repository knowledge lives in a disposable
20
+ `.okf` index. Both are plain files that humans and agents can read.
21
21
 
22
- 2. **A repository exploration engine.** A derived, git-aware knowledge index of the repo you are standing in, living at `<repo>/.okf/` —
23
- structure, languages, packages, modules, entry points, and staleness state. Built and read through the `weave_repo` tool. Rebuildable,
24
- disposable, never the source of truth.
22
+ ## Core capabilities
25
23
 
26
- One rule spans both: **everything is equally readable by humans and agents.** Markdown and JSON on disk, no opaque database, no lock-in
27
- format. Every generated artefact carries provenance (`human`, `agent` or `generated`), so agent-written content never masquerades as
28
- something you wrote.
24
+ - **Conversational note-taking.** Create and update notes through natural-language requests such as “start a note”, “add this”, or “remember
25
+ that”.
26
+ - **Verbatim narration with structured summaries.** During dictation, Pi preserves each spoken passage in an append-only `## Raw` section
27
+ while maintaining an organized summary above it.
28
+ - **Knowledge retrieval.** Pi searches existing notes when answering questions about previous decisions, people, projects, or meetings.
29
+ - **Unified visual workspace.** `/weave-view` presents notes, links, repository structure, and provenance in a live browser interface.
30
+ - **Optional session memory.** `/weave-scan sessions` converts prior pi sessions into searchable notes and skips unchanged transcripts.
31
+ - **Repository exploration.** A lightweight, git-aware index gives Pi a structural overview of the current codebase before it reads files.
29
32
 
33
+ Nothing is captured silently. pi-weave creates or extends a personal note only when you ask it to.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pi install npm:pi-weave
30
39
  ```
31
- 🕸️ vault:12 · my-project:ok ← pi's status line when weave is active
40
+
41
+ Other install sources:
42
+
43
+ ```bash
44
+ pi install git:github.com/EranYonai/pi-weave
45
+ pi install /path/to/pi-weave
32
46
  ```
33
47
 
34
- See [docs/design.md](docs/design.md) for the reasoning behind all of it.
48
+ Requires Node **20.13 or newer**.
35
49
 
36
- ## Install
50
+ ## Start taking notes
51
+
52
+ Talk to Pi normally:
53
+
54
+ ```text
55
+ You: Start a note called Authentication migration.
56
+
57
+ You: We probably want OIDC next quarter, but existing JWT clients need
58
+ a compatibility window.
59
+
60
+ You: Add that the gateway team owns the migration plan.
61
+
62
+ You: What open questions are in this note?
63
+ ```
64
+
65
+ For live narration or interview notes, tell Pi that you are dictating:
66
+
67
+ ```text
68
+ You: Start a note for this interview. I’m going to narrate; keep my words
69
+ verbatim and organize the note as we go.
70
+ ```
71
+
72
+ For each chunk, Pi:
73
+
74
+ 1. appends your words unchanged to the note’s `## Raw` tail;
75
+ 2. refreshes the structured summary above it;
76
+ 3. leaves the raw record untouched.
77
+
78
+ This makes the note readable during the conversation without replacing your words with an AI reconstruction. Notes based on your dictation
79
+ remain marked `source: human`; notes drafted by Pi are marked `source: agent`.
80
+
81
+ Useful requests include:
82
+
83
+ | Say this | What happens |
84
+ |---|---|
85
+ | “Start a note about…” | Creates a Markdown note in the vault |
86
+ | “Add this to the … note” | Finds the existing note and appends to it |
87
+ | “Clean up” or “finalize this note” | Reorganizes the readable body and preserves the raw tail |
88
+ | “What did we decide about…?” | Searches the vault, then reads the relevant notes |
89
+ | “Remember that…” | Stores durable knowledge for a future session |
90
+
91
+ ## `/weave-view`
37
92
 
38
93
  ```bash
39
- pi install npm:pi-weave # from npm (recommended)
40
- pi install git:github.com/EranYonai/pi-weave # from git
41
- pi install /path/to/pi-weave # local path
94
+ /weave-view # open the browser workspace
95
+ /weave-view --no-open # start it and print the URL
96
+ /weave-view tui # terminal UI for SSH or browser-free use
42
97
  ```
43
98
 
44
- Requires Node **>= 20.13.0**. For development against a checkout: `pi -e ./src/pi/index.ts`.
99
+ The browser workspace has four connected views:
45
100
 
46
- On session start pi-weave detects the repository you are in, checks whether `.okf` exists and is fresh, and reports it in the status footer
47
- a filled `●` marks weave as active. An unindexed repository gets a one-line nudge; a stale one gets a warning.
101
+ - **Tree** notes, folders, session memories, and repository structure. Filter by text or provenance, create folders, drag notes between
102
+ folders, and use the context menu to rename or delete.
103
+ - **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, and authorship. Click the body or press `⌘E` / `Ctrl E`
104
+ to edit; save with `⌘S` / `Ctrl S`.
105
+ - **Graph** — a navigable map of notes, links, mentions, modules, and repository relationships. Selecting something updates every view.
106
+ - **Context** — links, backlinks, tags, and code mentions for the current selection.
48
107
 
49
- ## Tools and commands
108
+ Search with `⌘K` / `Ctrl K`. Press `?` for all shortcuts. The workspace updates as notes change on disk, so a note written by Pi appears
109
+ without a reload. It follows the system theme by default and can be switched between light and dark.
50
110
 
51
- | Surface | Name | Purpose |
52
- |---|---|---|
53
- | Tool | `weave_note` | `list` / `get` / `add` / `append` / `finalize` / `search` over vault notes |
54
- | Tool | `weave_repo` | `status` / `scan` / `overview` of the `.okf` repository index |
55
- | Command | `/weave` | workspace dashboard (vault + repository) |
56
- | Command | `/weave-view` | open the knowledge workspace in your browser |
57
- | Command | `/weave-scan` | build or refresh the repository index (light) |
58
- | Command | `/weave-scan deep` | light index plus model-written per-file summaries (opt-in, incremental, background) |
59
- | Command | `/weave-scan sessions` | summarize pi session history into the vault as memory notes (incremental, background) |
60
- | Command | `/weave-scan-cancel` | stop an in-flight `/weave-scan deep` or `sessions` run |
61
- | Skill | `weave-notepad` | how the agent should take good notes |
62
- | Skill | `weave-explore` | how the agent should explore repositories |
63
-
64
- `/weave-scan deep` refreshes the light index and then writes a short model summary per file to `.okf/repository/summaries/`, skipping files
65
- whose content hash has not changed since their last summary. It costs tokens, so it never runs implicitly — and it runs in the background,
66
- so `/weave-scan-cancel` can stop it mid-flight.
67
-
68
- `/weave-scan sessions` is the repo-agnostic sibling (docs/session-scan.md): it reads every pi session transcript under
69
- `~/.pi/agent/sessions/`, hashes each file while reading it, and writes one generated vault note per changed session under
70
- `~/.okf/notes/sessions/` — pi's memory as first-class, wikilinked notes in an inner folder of the vault graph. Unchanged transcripts cost no
71
- LLM calls at all, and older layouts migrate automatically on the next scan.
72
-
73
- ## The workspace
74
-
75
- `/weave-view` opens a browser knowledge workspace over the same graph the tools see — the vault and the repository index as one model.
111
+ The editor checks revisions before saving. If the note changed elsewhere, it asks whether to reload or overwrite instead of silently losing
112
+ work. Unknown front-matter fields are preserved, so the same vault remains safe to edit with Obsidian or a text editor.
113
+
114
+ `/weave-view tui` is the smaller, read-only terminal explorer: tree, focused neighborhood, details, and link health over the same graph.
115
+
116
+ ## Remember past pi sessions
76
117
 
77
118
  ```bash
78
- /weave-view # browser workspace (default), opens a tab
79
- /weave-view --no-open # same server, just prints the URL
80
- /weave-view tui # the in-terminal explorer instead
119
+ /weave-scan sessions
81
120
  ```
82
121
 
83
- Three resizable columns and a context rail:
122
+ This reads pi’s local session transcripts and creates generated notes under:
84
123
 
85
- - **Tree** — an expandable containment tree over notes and repository structure, with a filter box and provenance cycling.
86
- - **Note** — the selected note rendered with [marked](https://marked.js.org) and sanitised with DOMPurify. `[[wikilinks]]` navigate inside
87
- the workspace; links with no target render as ghosts rather than dead text.
88
- - **Graph** — [sigma.js](https://www.sigmajs.org) v3 on WebGL with a [d3-force](https://d3js.org/d3-force) layout: neighbourhood highlight
89
- on selection, semantic zoom that reveals labels as you go in, and cluster collapse as real graph reduction rather than hiding.
90
- - **Context rail** — links, backlinks, tags and mentions for whatever is selected, every entry clickable.
124
+ ```text
125
+ ~/.okf/notes/sessions/
126
+ ```
127
+
128
+ Each note records what was asked, what happened, and any open threads. The scan is:
129
+
130
+ - **opt-in** — it never runs automatically;
131
+ - **incremental** — unchanged transcripts use no model calls;
132
+ - **cancellable** — run `/weave-scan-cancel`;
133
+ - **repo-independent** — it works from any directory.
91
134
 
92
- Selecting anywhere highlights everywhere: the tree, the note body, the graph and the rail are lenses onto one selection. Updates arrive live
93
- over SSE as files change on disk, so an agent writing a note shows up without a refresh.
135
+ Session summaries use the active pi model and are marked `source: generated`.
94
136
 
95
- `⌘K` opens a search palette spanning both faces (notes ranked with snippets, repository nodes by label). The whole workspace is keyboard
96
- drivable — `⌘1/2/3` focus a column, `/` filters the tree, `g` fits the graph, `Esc` clears, and `?` lists the rest. Column widths persist.
137
+ ## Repository knowledge
97
138
 
98
- The workspace is **read-first**, but no longer read-only. `⌘E` toggles the note column between read and edit, `⌘S` saves, and every save
99
- carries the revision read at load — a stale one gets a `409` and a choice of reload, overwrite or keep editing. The draft is never silently
100
- discarded or clobbered: a remote change arriving over SSE for the note you are editing is recorded rather than applied, and comes back as
101
- that same `409` when you save.
139
+ Inside a Git repository, pi-weave detects whether `<repo>/.okf/` is missing, fresh, or stale.
102
140
 
103
- Front matter the engine does not own survives a browser save **byte-identically** — `aliases`, `cssclass` and a `tags:` block list all come
104
- back unchanged and in place, with `updated:` the only line a save moves. That is the P5 exit criterion, and
105
- `tests/web/editor.roundtrip.test.ts` drives it through the real client, over a real socket, into a real vault — it is what makes editing
106
- here safe alongside Obsidian.
141
+ ```bash
142
+ /weave-scan # fast structural index
143
+ /weave-scan deep # also summarize changed files with the active model
144
+ ```
107
145
 
108
- Rename and delete have routes, client functions and tests but **no UI**, deliberately: the vault has no trash, so the confirmation flow
109
- around a destructive button is a design decision rather than a wiring task. Notes are still authorable through the `weave_note` tool or by
110
- hand, and the note toolbar's "Open in $EDITOR" hands the file to yours. `/weave-view tui` is the read-only in-terminal explorer — the same
111
- model, a containment tree, a 1-hop focus view, node detail and a link-health surface, for when you are on the far end of an SSH session.
146
+ The light index covers languages, packages, modules, entry points, and Git state. A deep scan adds short per-file summaries and only
147
+ revisits files whose content changed.
112
148
 
113
- ### The local server
149
+ The repository index is a cache, not a source of truth. Delete `.okf`, scan again, and nothing important is lost. pi-weave excludes it
150
+ locally from Git by default.
114
151
 
115
- The workspace server is deliberately small and deliberately paranoid, because loopback is not an authorisation boundary — any local process
116
- can reach the port, and any website you visit can try to via DNS rebinding. Four layers:
152
+ ## Commands and tools
117
153
 
118
- 1. Binds `127.0.0.1` on an ephemeral port. Never `0.0.0.0`.
119
- 2. A `Host` header allowlist (`127.0.0.1:PORT`, `localhost:PORT`, `[::1]:PORT`), which is what actually stops rebinding.
120
- 3. A 256-bit per-session token, handed off once in the URL and exchanged for an `HttpOnly; SameSite=Strict` cookie via a redirect that drops
121
- it from the address bar. Compared in constant time.
122
- 4. `Origin` validated when present, and required on anything that is not a `GET` or `HEAD`.
154
+ Most people only need natural language and `/weave-view`.
123
155
 
124
- The page is served under a nonce-only CSP — `default-src 'none'`, no `unsafe-inline`, no `unsafe-eval`, no CORS headers at all. The server
125
- shuts itself down after 30 minutes with no client attached, and always at the end of the pi session.
156
+ | Surface | Name | Purpose |
157
+ |---|---|---|
158
+ | Command | `/weave-view` | Open the browser or terminal workspace |
159
+ | Command | `/weave` | Show vault and repository status |
160
+ | Command | `/weave-scan` | Build or refresh the repository index |
161
+ | Command | `/weave-scan deep` | Add incremental model-written file summaries |
162
+ | Command | `/weave-scan sessions` | Turn pi session history into vault notes |
163
+ | Command | `/weave-scan-cancel` | Stop a deep or session scan |
164
+ | Tool | `weave_note` | List, read, add, append, finalize, and search notes |
165
+ | Tool | `weave_repo` | Check, scan, and summarize the repository index |
126
166
 
127
- ## The formats
167
+ The included `weave-notepad` and `weave-explore` skills teach Pi when and how to use these tools.
128
168
 
129
- Vault note (`~/.okf/notes/auth-boundary.md`):
169
+ ## Files, privacy, and portability
130
170
 
131
- ```markdown
171
+ Personal notes are ordinary Markdown files:
172
+
173
+ ```text
174
+ ~/.okf/
175
+ └── notes/
176
+ ├── authentication-migration.md
177
+ └── sessions/
178
+ └── plan-the-release.md
179
+ ```
180
+
181
+ A note has small YAML front matter followed by Markdown:
182
+
183
+ ````markdown
132
184
  ---
133
- title: Auth boundary decision
185
+ title: Authentication migration
134
186
  created: 2026-08-22T09:00:00.000Z
135
187
  updated: 2026-08-22T09:30:00.000Z
136
188
  tags: [auth, security]
137
189
  source: human
138
190
  ---
139
191
 
140
- JWT validation happens at the gateway because…
141
- ```
192
+ ## Summary
142
193
 
143
- Repository index (`<repo>/.okf/`):
194
+ Move toward OIDC while keeping a JWT compatibility window.
144
195
 
145
- ```text
146
- .okf/
147
- ├── okf.json # format version + generator + source: generated
148
- └── repository/
149
- ├── identity.json # name, remotes, default branch
150
- ├── git.json # HEAD sha + branch + changed-file content hashes (staleness anchor)
151
- └── structure.json # languages, packages, modules, entry points
152
- ```
153
-
154
- The `.okf` index is **derived**: delete it, rescan, lose nothing. By default it is excluded from git locally (`.git/info/exclude`);
155
- committing it to share with a team is a deliberate opt-in. The vault location can be overridden with `PI_WEAVE_VAULT`.
156
-
157
- ## Zero runtime dependencies
158
-
159
- `package.json` declares no `dependencies`. The four peers (`@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`,
160
- `@earendil-works/pi-tui`, `typebox`) are supplied by the pi harness, which loads `src/pi/index.ts` as TypeScript directly — installing
161
- pi-weave runs no build step.
196
+ ---
162
197
 
163
- The browser client cannot work that way, so preact, sigma, graphology, d3-force, marked and DOMPurify are **devDependencies** bundled into a
164
- committed artifact at `src/web/client/dist/app.js`. They are inputs to a build, not runtime requirements of the package.
198
+ ## Raw
199
+ <!-- NEVER edit below this line. Verbatim user input preserved here. -->
165
200
 
166
- ## For other agent harnesses
201
+ ```
202
+ We probably want OIDC next quarter…
203
+ ```
204
+ ````
167
205
 
168
- The skills follow the [Agent Skills standard](https://agentskills.io/specification), and the on-disk artefacts and `src/core` are
169
- harness-agnostic by design: `src/core` may not import anything pi-specific. Claude Code and opencode adapters are on the roadmap
170
- ([docs/design.md](docs/design.md) §21).
206
+ Set `PI_WEAVE_VAULT` to use a different vault location.
171
207
 
172
- ## Documentation
208
+ Reading, writing, searching, and viewing notes are local operations. Deep repository scans and session summaries send their bounded input to
209
+ whichever model you configured in pi. The browser workspace binds only to loopback, uses a per-session token, and shuts down with the pi
210
+ session.
173
211
 
174
- | Where | What |
175
- |---|---|
176
- | [docs/design.md](docs/design.md) | the design document — *why* pi-weave is shaped this way |
177
- | [docs/weave-workspace.md](docs/weave-workspace.md) | the browser workspace: library choices with measurements, security model, phases |
178
- | [docs/weave-view-tui-design.md](docs/weave-view-tui-design.md) | the in-terminal explorer |
179
- | [AGENTS.md](AGENTS.md) | contributor and agent rules — read before changing anything |
212
+ The vault format, repository index, and skills are intentionally harness-agnostic. `src/core` contains no pi-specific imports.
180
213
 
181
214
  ## Development
182
215
 
183
216
  ```bash
184
217
  npm install
185
- npm run typecheck # tsc --noEmit, strict, both projects
186
- npm test # vitest run
187
- npm run coverage # the 95% gate (lines, branches, functions, statements)
188
- npm run build:web # rebuild the committed browser bundle
189
- npm run check # typecheck + bundle drift check + coverage — run this before committing
218
+ npm run check
190
219
  ```
191
220
 
192
- Two rules worth knowing before you send a patch. Coverage must stay at or above **95%** on every metric; the gate is enforced by vitest
193
- thresholds and `npm run check` fails below it. And the committed web bundle must match its source — `npm run check` rebuilds it in memory
194
- and byte-compares, so run `npm run build:web` and commit the result whenever you touch `src/web/`.
221
+ Useful individual commands:
222
+
223
+ ```bash
224
+ npm run typecheck
225
+ npm test
226
+ npm run coverage
227
+ npm run build:web
228
+ ```
229
+
230
+ Coverage must remain at or above **95%** for lines, branches, functions, and statements. If browser source changes, rebuild and commit
231
+ `src/web/client/dist/app.js`.
232
+
233
+ Read [AGENTS.md](AGENTS.md) before contributing. Work on a feature branch; do not commit directly to `main`.
234
+
235
+ ## More detail
195
236
 
196
- Never commit to `main`; branch, then open a PR. See [AGENTS.md](AGENTS.md) for the rest.
237
+ - [Design](docs/design.md) product and architecture
238
+ - [Notepad skill](skills/weave-notepad/SKILL.md) — capture, narration, and provenance behavior
239
+ - [Browser workspace](docs/weave-workspace.md) — UI architecture and security model
240
+ - [Session scanning](docs/session-scan.md) — incremental session memory
241
+ - [Repository exploration skill](skills/weave-explore/SKILL.md) — how Pi uses the index
197
242
 
198
- ## Licence
243
+ ## License
199
244
 
200
- [MIT](LICENSE).
245
+ [MIT](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-weave",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "An agent-native knowledge workspace for your life and your code. Smart notepad + repository exploration, readable by humans and agents alike.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -33,7 +33,7 @@ import { buildGraph, DEFAULT_MAX_NOTES, type BuildGraphInput } from "../graph/bu
33
33
  import type { GraphModel } from "../graph/model";
34
34
  import { readRepositorySide } from "../graph/current";
35
35
  import { withMutationQueue } from "../mutex";
36
- import { getNote, statNotes } from "../vault";
36
+ import { getNote, listNoteFolders, statNotes } from "../vault";
37
37
  import type { Note } from "../types";
38
38
 
39
39
  /**
@@ -136,11 +136,16 @@ function classify(
136
136
  ): { scope: InvalidationScope; slug: string | null } {
137
137
  const path = resolve(absPath);
138
138
  const rel = relative(resolve(opts.vaultRoot, NOTES_DIR), path);
139
- // Directly inside the notes dir (flat vault: no separator in the relative
140
- // path) and Markdown — anything else in there is not a note.
141
- if (rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel) && !rel.includes(sep)) {
142
- if (rel.endsWith(".md")) return { scope: "vault", slug: rel.slice(0, -".md".length) };
143
- return { scope: "none", slug: null };
139
+ if (rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel)) {
140
+ if (rel.endsWith(".md")) {
141
+ const slug = rel.slice(0, -".md".length).split(sep).join("/");
142
+ return { scope: "vault", slug };
143
+ }
144
+ const base = rel.split(sep).pop() ?? rel;
145
+ if (base.startsWith(".") || (base.includes(".") && !base.endsWith(".md"))) {
146
+ return { scope: "none", slug: null };
147
+ }
148
+ return { scope: "vault", slug: null };
144
149
  }
145
150
  return { scope: within(path, resolve(opts.cwd)) ? "repo" : "none", slug: null };
146
151
  }
@@ -179,6 +184,7 @@ export class WorkspaceCache {
179
184
  * matches the uncached build exactly.
180
185
  */
181
186
  private fileCount = 0;
187
+ private folders: string[] = [];
182
188
  private repo: CachedRepo | null = null;
183
189
  /**
184
190
  * The last snapshot handed out, reused verbatim when a build proves nothing
@@ -368,7 +374,12 @@ export class WorkspaceCache {
368
374
  // graph has no node for (§4.3).
369
375
  const kept = notes.slice(0, DEFAULT_MAX_NOTES);
370
376
  const input: BuildGraphInput = {
371
- vault: { root: this.vaultRoot, exists: true, noteCount: this.fileCount },
377
+ vault: {
378
+ root: this.vaultRoot,
379
+ exists: true,
380
+ noteCount: this.fileCount,
381
+ ...(this.folders.length > 0 ? { folders: this.folders } : {}),
382
+ },
372
383
  notes: kept,
373
384
  repository: repo?.repository ?? null,
374
385
  };
@@ -418,7 +429,9 @@ export class WorkspaceCache {
418
429
  * that disappeared are evicted, so the map never outgrows the vault.
419
430
  */
420
431
  private async refreshNotes(): Promise<Note[]> {
421
- const stats = await statNotes(this.vaultRoot);
432
+ const previousFolders = this.folders;
433
+ const [stats, folders] = await Promise.all([statNotes(this.vaultRoot), listNoteFolders(this.vaultRoot)]);
434
+ this.folders = folders;
422
435
  const previousCount = this.notes.size;
423
436
  const previousFileCount = this.fileCount;
424
437
  this.fileCount = stats.length;
@@ -446,7 +459,11 @@ export class WorkspaceCache {
446
459
  // A note vanished if the map shrank without a compensating read; the
447
460
  // file count moving covers a malformed file appearing or disappearing,
448
461
  // which changes the vault node's `notes` detail without ever parsing.
449
- this.notesChanged = read > 0 || next.size !== previousCount || this.fileCount !== previousFileCount;
462
+ const foldersChanged =
463
+ folders.length !== previousFolders.length ||
464
+ folders.some((f, i) => f !== previousFolders[i]);
465
+ this.notesChanged =
466
+ read > 0 || next.size !== previousCount || this.fileCount !== previousFileCount || foldersChanged;
450
467
  this.notes = next;
451
468
  // `statNotes` yields readdir (slug-ascending) order and sort is stable,
452
469
  // so ties break by slug — identical to `readVault`.
@@ -140,7 +140,12 @@ function buildVaultSide(
140
140
  // the tree renders any `contains` chain, so the kind reuse needs no client
141
141
  // change. Deterministic: dirs sorted, parents before children.
142
142
  const folderIds = new Map<string, string>();
143
- const noteDirs = [...new Set(kept.map((n) => n.slug.split("/").slice(0, -1).join("/")))]
143
+ const noteDirs = [
144
+ ...new Set([
145
+ ...(input.vault.folders ?? []),
146
+ ...kept.map((n) => n.slug.split("/").slice(0, -1).join("/")),
147
+ ]),
148
+ ]
144
149
  .filter((d) => d.length > 0)
145
150
  .sort();
146
151
  const notesIn = (dir: string): number => kept.filter((n) => n.slug.startsWith(`${dir}/`)).length;
@@ -102,10 +102,10 @@ export async function readRepositorySide(
102
102
  * a third readdir) is now N reads and one readdir (weave-workspace §4.1).
103
103
  */
104
104
  export async function buildCurrentGraph(cwd: string, vaultRoot: string = resolveVaultRoot()): Promise<GraphModel> {
105
- const { notes, fileCount } = await readVault(vaultRoot);
105
+ const { notes, fileCount, folders } = await readVault(vaultRoot);
106
106
 
107
107
  const input: BuildGraphInput = {
108
- vault: { root: vaultRoot, exists: true, noteCount: fileCount },
108
+ vault: { root: vaultRoot, exists: true, noteCount: fileCount, ...(folders ? { folders } : {}) },
109
109
  notes: notes.slice(0, DEFAULT_MAX_NOTES),
110
110
  repository: null,
111
111
  };
package/src/core/slug.ts CHANGED
@@ -15,6 +15,19 @@ export function slugify(title: string): string {
15
15
  return slug.length > 0 ? slug : "note";
16
16
  }
17
17
 
18
+ /**
19
+ * Slugify each segment of a relative path while preserving directory separators.
20
+ */
21
+ export function slugifyPath(path: string): string {
22
+ const parts = path
23
+ .split(/[\/\\]+/)
24
+ .map((s) => s.trim())
25
+ .filter((s) => s.length > 0)
26
+ .map(slugify)
27
+ .filter((s) => s.length > 0);
28
+ return parts.length > 0 ? parts.join("/") : "note";
29
+ }
30
+
18
31
  /**
19
32
  * Find a free slug in the vault given a desired base, appending -2, -3, ...
20
33
  * `exists` is injected so this stays pure and trivially testable.
package/src/core/types.ts CHANGED
@@ -174,6 +174,7 @@ export interface VaultStatus {
174
174
  root: string;
175
175
  exists: boolean;
176
176
  noteCount: number;
177
+ folders?: string[];
177
178
  }
178
179
 
179
180
  /** Status of the repository half of the workspace. */