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 +176 -131
- package/package.json +1 -1
- package/src/core/cache/workspace.ts +26 -9
- package/src/core/graph/build.ts +6 -1
- package/src/core/graph/current.ts +2 -2
- package/src/core/slug.ts +13 -0
- package/src/core/types.ts +1 -0
- package/src/core/vault.ts +251 -8
- package/src/core/view/tree.ts +14 -2
- package/src/web/client/api.ts +49 -2
- package/src/web/client/context/context.model.ts +155 -8
- package/src/web/client/dist/app.js +335 -125
- package/src/web/client/graph/dynamics.ts +7 -1
- package/src/web/client/graph/graph.model.ts +238 -94
- package/src/web/client/graph/positions.ts +17 -8
- package/src/web/client/graph/renderer.ts +2 -2
- package/src/web/client/note/Editor.tsx +9 -9
- package/src/web/client/note/Note.tsx +190 -12
- package/src/web/client/note/editor.model.ts +50 -0
- package/src/web/client/note/note.model.ts +334 -0
- package/src/web/client/shell/Columns.tsx +3 -1
- package/src/web/client/shell/ContextRail.tsx +52 -16
- package/src/web/client/shell/Header.tsx +53 -4
- package/src/web/client/shell/Shell.tsx +16 -2
- package/src/web/client/shell/icons.model.ts +101 -0
- package/src/web/client/shell/shell.model.ts +36 -1
- package/src/web/client/shell/theme.ts +309 -93
- package/src/web/client/tree/Tree.tsx +524 -10
- package/src/web/client/tree/tree.model.ts +182 -27
- package/src/web/server/page.ts +4 -4
- package/src/web/server/routes.ts +164 -16
- package/src/web/shared/layout.ts +138 -12
- package/src/web/shared/wire.ts +18 -0
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
|
|
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
|
-
**
|
|
14
|
+
**A local knowledge workspace you can talk to.**
|
|
15
15
|
|
|
16
|
-
pi-weave is
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
Requires Node **20.13 or newer**.
|
|
35
49
|
|
|
36
|
-
##
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
99
|
+
The browser workspace has four connected views:
|
|
45
100
|
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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-
|
|
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
|
-
|
|
122
|
+
This reads pi’s local session transcripts and creates generated notes under:
|
|
84
123
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
141
|
+
```bash
|
|
142
|
+
/weave-scan # fast structural index
|
|
143
|
+
/weave-scan deep # also summarize changed files with the active model
|
|
144
|
+
```
|
|
107
145
|
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
167
|
+
The included `weave-notepad` and `weave-explore` skills teach Pi when and how to use these tools.
|
|
128
168
|
|
|
129
|
-
|
|
169
|
+
## Files, privacy, and portability
|
|
130
170
|
|
|
131
|
-
|
|
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:
|
|
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
|
-
|
|
141
|
-
```
|
|
192
|
+
## Summary
|
|
142
193
|
|
|
143
|
-
|
|
194
|
+
Move toward OIDC while keeping a JWT compatibility window.
|
|
144
195
|
|
|
145
|
-
|
|
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
|
-
|
|
164
|
-
|
|
198
|
+
## Raw
|
|
199
|
+
<!-- NEVER edit below this line. Verbatim user input preserved here. -->
|
|
165
200
|
|
|
166
|
-
|
|
201
|
+
```
|
|
202
|
+
We probably want OIDC next quarter…
|
|
203
|
+
```
|
|
204
|
+
````
|
|
167
205
|
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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.
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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`.
|
package/src/core/graph/build.ts
CHANGED
|
@@ -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 = [
|
|
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.
|