pi-weave 0.1.23 → 0.2.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/README.md CHANGED
@@ -11,24 +11,56 @@
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
- **A local knowledge workspace you can talk to.**
14
+ **Your notes. Your agent's notes. One searchable knowledge workspace.**
15
15
 
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 words alongside a useful summary, and explore everything in `/weave-view`.
16
+ I've been chasing the right note-taking workflow for years. Notion, OneNote, Obsidian, and plenty of others.
17
17
 
18
- It also understands the repository you are working in. Personal notes live in a Markdown vault; repository knowledge lives in a disposable `.okf` index. Both are plain files that humans and agents can read.
18
+ With pi-weave, I've made one that finally answers the way I want to work today — especially now that AI agents are part of that work.
19
19
 
20
- ## Core capabilities
20
+ The idea is simple:
21
21
 
22
- - **Conversational note-taking.** Create and update notes through natural-language requests such as “start a note”, “add this”, or “remember that”.
23
- - **Verbatim narration with structured summaries.** During dictation, Pi preserves each spoken passage in an append-only `## Raw` section while maintaining an organized summary above it.
24
- - **Knowledge retrieval.** Pi searches existing notes when answering questions about previous decisions, people, projects, or meetings.
25
- - **Unified visual workspace.** `/weave-view` presents notes, links, repository structure, and provenance in a live browser interface.
26
- - **Optional session memory.** `/weave-scan sessions` turns changed pi transcripts into searchable notes with reusable technical takeaways.
27
- - **Repository exploration.** A lightweight, git-aware index gives Pi a structural overview of the current codebase before it reads files.
22
+ **You and your agent should share the same knowledge.**
28
23
 
29
- Nothing is captured silently. pi-weave creates or extends a personal note only when you ask it to.
24
+ Talk naturally and let your thoughts become structured notes. Write notes yourself. Let your agent capture decisions, discoveries, and
25
+ useful context. Bring important knowledge forward from previous sessions.
30
26
 
31
- ## Install
27
+ Then find it again — by searching the workspace yourself, or simply asking your agent.
28
+
29
+ Everything is local. Everything is readable. Your notes are ordinary files, not knowledge trapped inside another chat.
30
+
31
+ ## Think out loud
32
+
33
+ Dictation is one of my favorite ways to use AI.
34
+
35
+ Instead of stopping to organize every thought while I'm having it, I can just talk:
36
+
37
+ > Start a note about pi-weave positioning. I'm going to think out loud.
38
+
39
+ pi-weave keeps what I actually said while Pi continuously turns it into a useful, structured note.
40
+
41
+ The raw thought stays yours. The organization is handled for you.
42
+
43
+ And it doesn't end with human notes.
44
+
45
+ ## Your knowledge + your agent's knowledge
46
+
47
+ pi-weave gives humans and agents one workspace.
48
+
49
+ A note you dictate, something you write manually, a useful discovery made by an agent, or knowledge recovered from an earlier session can
50
+ all live together.
51
+
52
+ pi-weave keeps track of where knowledge came from, while making the whole workspace searchable by both you and your agent.
53
+
54
+ **The conversation can end. The knowledge doesn't have to.**
55
+
56
+ ## Built on Pi
57
+
58
+ pi-weave is built as an extension for [Pi](https://github.com/earendil-works/pi).
59
+
60
+ I've grown to love Pi precisely because it is such a lightweight agent harness. It gives the model tools and context without trying to
61
+ become the product itself, and its extensibility makes projects like pi-weave possible.
62
+
63
+ Install pi-weave:
32
64
 
33
65
  ```bash
34
66
  pi install npm:pi-weave
@@ -43,6 +75,8 @@ pi install /path/to/pi-weave
43
75
 
44
76
  Requires Node **20.13 or newer**.
45
77
 
78
+ Then just talk to Pi.
79
+
46
80
  ## Start taking notes
47
81
 
48
82
  Talk to Pi normally:
@@ -71,7 +105,8 @@ For each chunk, Pi:
71
105
  2. refreshes the structured summary above it;
72
106
  3. leaves the raw record untouched.
73
107
 
74
- This makes the note readable during the conversation without replacing your words with an AI reconstruction. Notes based on your dictation remain marked `source: human`; notes drafted by Pi are marked `source: agent`.
108
+ This makes the note readable during the conversation without replacing your words with an AI reconstruction. Notes based on your dictation
109
+ remain marked `source: human`; notes drafted by Pi are marked `source: agent`.
75
110
 
76
111
  Useful requests include:
77
112
 
@@ -86,9 +121,9 @@ Useful requests include:
86
121
 
87
122
  ## Keeping the vault connected
88
123
 
89
- A wiki-link resolves to nothing when it is written as a bare title — `[[Quarterly Roadmap]]` when the note lives at
90
- `planning/roadmap-2026` — or when it points at a note that was never written. Pi repairs those in one deterministic pass instead of
91
- rereading the vault and guessing.
124
+ A wiki-link resolves to nothing when it is written as a bare title — `[[Quarterly Roadmap]]` when the note lives at `planning/roadmap-2026`
125
+ — or when it points at a note that was never written. Pi repairs those in one deterministic pass instead of rereading the vault and
126
+ guessing.
92
127
 
93
128
  ```text
94
129
  You: Check the links in my notes.
@@ -112,13 +147,19 @@ Renaming or moving a note rewrites its inbound links automatically, so its backl
112
147
  The browser workspace has four connected views:
113
148
 
114
149
  - **Tree** — notes, folders, and repository structure, with text and provenance filters.
115
- - **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, authorship, and an action to open the source in `$EDITOR`.
150
+ - **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, authorship, click-to-edit, and an action to open the
151
+ source in `$EDITOR`.
116
152
  - **Graph** — a navigable map of notes, links, mentions, modules, and repository relationships. Selecting something updates every view.
117
153
  - **Context** — links, backlinks, tags, and code mentions for the current selection.
118
154
 
119
- Search with `⌘K` / `Ctrl K`. Press `?` for all shortcuts. The workspace updates as notes change on disk, so a note written by Pi appears without a reload. It follows the system theme by default and can be switched between light and dark.
155
+ Search with `⌘K` / `Ctrl K`. Press `?` for all shortcuts. The workspace updates as notes change on disk, so a note written by Pi appears
156
+ without a reload. It follows the system theme by default and can be switched between light and dark.
120
157
 
121
- The browser is read-only. Edit with `$EDITOR`, Obsidian, or the `weave_note` tool; unknown front-matter fields remain preserved.
158
+ **Click the note text to edit it** — the prose swaps for its Markdown source; `⌘S` / `Ctrl S` saves, `Esc` or **Done** closes. Selecting
159
+ text to copy and clicking a `[[wikilink]]` do what they always did; neither opens the editor. A save rewrites the body and nothing
160
+ else — unknown front-matter fields, key order, and the append-only `## Raw` tail of a dictated note all survive untouched, so a note edited
161
+ here and a note edited in Obsidian stay byte-compatible. Editing with `$EDITOR`, Obsidian, or the `weave_note` tool works as before; the
162
+ last writer wins, and the workspace picks up outside changes within a couple of seconds.
122
163
 
123
164
  `/weave-view tui` is the smaller, read-only terminal explorer: tree, focused neighborhood, details, and link health over the same graph.
124
165
 
@@ -129,7 +170,10 @@ The browser is read-only. Edit with `$EDITOR`, Obsidian, or the `weave_note` too
129
170
  /weave-scan sessions /path/to/history # explicit history root
130
171
  ```
131
172
 
132
- This opt-in scan treats a supplied file—or every bounded text file under a supplied directory—as opaque session material for the active model to interpret, then writes generated notes under `~/.okf/notes/sessions/`. That makes it usable with Claude Code, opencode, Codex, or exported history trees without requiring their schema or file extension. It skips unchanged files, captures outcomes plus reusable technical takeaways, works outside Git repositories, and can be stopped with `/weave-scan-cancel`.
173
+ This opt-in scan treats a supplied file—or every bounded text file under a supplied directory—as opaque session material for the active
174
+ model to interpret, then writes generated notes under `~/.okf/notes/sessions/`. That makes it usable with Claude Code, opencode, Codex, or
175
+ exported history trees without requiring their schema or file extension. It skips unchanged files, captures outcomes plus reusable technical
176
+ takeaways, works outside Git repositories, and can be stopped with `/weave-scan-cancel`.
133
177
 
134
178
  ## Repository knowledge
135
179
 
@@ -140,9 +184,11 @@ Inside a Git repository, pi-weave detects whether `<repo>/.okf/` is missing, fre
140
184
  /weave-scan deep # also summarize changed files with the active model
141
185
  ```
142
186
 
143
- The light index covers languages, packages, modules, entry points, and Git state. A deep scan adds short per-file summaries and only revisits files whose content changed.
187
+ The light index covers languages, packages, modules, entry points, and Git state. A deep scan adds short per-file summaries and only
188
+ revisits files whose content changed.
144
189
 
145
- The repository index is a cache, not a source of truth. Delete `.okf`, scan again, and nothing important is lost. pi-weave excludes it locally from Git by default.
190
+ The repository index is a cache, not a source of truth. Delete `.okf`, scan again, and nothing important is lost. pi-weave excludes it
191
+ locally from Git by default.
146
192
 
147
193
  ## Commands and tools
148
194
 
@@ -199,7 +245,9 @@ We probably want OIDC next quarter…
199
245
 
200
246
  Set `PI_WEAVE_VAULT` to use a different vault location.
201
247
 
202
- Reading, writing, searching, and viewing notes are local operations. Deep repository scans and session summaries send bounded input to whichever model you configured in pi. The browser workspace binds only to loopback, uses a per-session token, and shuts down with the pi session.
248
+ Reading, writing, searching, and viewing notes are local operations. Deep repository scans and session summaries send bounded input to
249
+ whichever model you configured in pi. The browser workspace binds only to loopback, uses a per-session token, and shuts down with the pi
250
+ session.
203
251
 
204
252
  The vault format, repository index, and skills are intentionally harness-agnostic. `src/core` contains no pi-specific imports.
205
253
 
@@ -219,7 +267,8 @@ npm run coverage
219
267
  npm run build:web
220
268
  ```
221
269
 
222
- Coverage must remain at or above **95%** for lines, branches, functions, and statements. If browser source changes, rebuild and commit `src/web/client/dist/app.js`.
270
+ Coverage must remain at or above **95%** for lines, branches, functions, and statements. If browser source changes, rebuild and commit
271
+ `src/web/client/dist/app.js`.
223
272
 
224
273
  Read [AGENTS.md](AGENTS.md) before contributing. Work on a feature branch; do not commit directly to `main`.
225
274
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-weave",
3
- "version": "0.1.23",
3
+ "version": "0.2.1",
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,
package/src/core/index.ts CHANGED
@@ -59,6 +59,7 @@ export {
59
59
  resolveHtmlPath,
60
60
  resolveNotePath,
61
61
  searchNotes,
62
+ setNoteBody,
62
63
  upsertNote,
63
64
  type LinkRepairResult,
64
65
  } from "./vault";
@@ -99,7 +99,7 @@ const RAW_HEADING_RE = new RegExp(`^${RAW_NOTES_HEADING}[ \\t]*\\r?$`, "m");
99
99
  * inside a ```` block would too — and each mistake ends the protected region
100
100
  * early, exposing example links in documentation to rewriting.
101
101
  */
102
- function fenceRanges(body: string): [number, number][] {
102
+ export function fenceRanges(body: string): [number, number][] {
103
103
  const ranges: [number, number][] = [];
104
104
  let open: { at: number; marker: string; length: number } | null = null;
105
105
  for (const match of body.matchAll(FENCE_RE)) {
@@ -134,7 +134,7 @@ function fenceRanges(body: string): [number, number][] {
134
134
  * refuse to repair — every link below it. Matching a whole heading line also
135
135
  * keeps `## Rawhide` from starting a tail.
136
136
  */
137
- function rawTailStart(body: string, fences: readonly [number, number][]): number {
137
+ export function rawTailStart(body: string, fences: readonly [number, number][]): number {
138
138
  let from = 0;
139
139
  for (;;) {
140
140
  const rest = body.slice(from);
package/src/core/vault.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  unquoteField,
11
11
  upsertFrontMatterFields,
12
12
  } from "./frontmatter";
13
- import { auditLinks, rewriteLinks, RAW_NOTES_HEADING, type LinkAudit, type LinkFix } from "./links/repair";
13
+ import { auditLinks, fenceRanges, rawTailStart, rewriteLinks, RAW_NOTES_HEADING, type LinkAudit, type LinkFix } from "./links/repair";
14
14
  import { withMutationQueue } from "./mutex";
15
15
  import { NOTES_DIR, OKF_MANIFEST } from "./paths";
16
16
  import { slugify, uniqueSlug } from "./slug";
@@ -596,6 +596,51 @@ export async function repairVaultLinks(root: string, options: { apply?: boolean
596
596
  });
597
597
  }
598
598
 
599
+ /**
600
+ * Keep the on-disk `## Raw` tail whatever the body says — it is append-only.
601
+ * `rawTailStart`, not `extractRawTail`: fence-aware, for untrusted input.
602
+ */
603
+ function preserveRawTail(currentBody: string, nextBody: string): string {
604
+ const tail = currentBody.slice(tailBoundary(currentBody)).trimEnd();
605
+ const head = nextBody.slice(0, tailBoundary(nextBody)).trim();
606
+ if (tail === "") return head;
607
+ // An emptied head leaves the tail alone, not a leading blank line.
608
+ return head === "" ? tail : `${head}\n\n${tail}`;
609
+ }
610
+
611
+ /** A `---` rule, alone on the line, with only blank space after it. */
612
+ const TAIL_RULE_RE = /(?:^|\n)-{3,}[ \t]*\r?\n\s*$/;
613
+
614
+ /**
615
+ * Where the tail's text begins — the `---` rule, not the heading
616
+ * `rawTailStart` returns. Splitting at the heading orphans the rule.
617
+ */
618
+ function tailBoundary(body: string): number {
619
+ const heading = rawTailStart(body, fenceRanges(body));
620
+ if (heading === body.length) return heading;
621
+ const rule = TAIL_RULE_RE.exec(body.slice(0, heading));
622
+ if (rule === null) return heading;
623
+ // `index` is the newline before the rule when there is one; step over it.
624
+ return rule.index === 0 && !body.startsWith("\n") ? 0 : rule.index + 1;
625
+ }
626
+
627
+ /**
628
+ * Replace a note's Markdown body; unknown front matter rides on
629
+ * `note.frontMatter` and only `updated` moves. Last write wins — no
630
+ * `expectedRevision`; docs/weave-workspace.md §11 P5.3 argues the trade.
631
+ */
632
+ export async function setNoteBody(root: string, slug: string, body: string, now = new Date()): Promise<VaultMutationResult> {
633
+ const path = resolveNotePath(root, slug);
634
+ if (path === null) return { ok: false, reason: "invalid" };
635
+ return withVaultLock(root, async () => {
636
+ const note = await getNote(root, slug);
637
+ if (note === null) return { ok: false, reason: "missing" };
638
+ const next = preserveRawTail(note.body, body);
639
+ await writeNote(path, slug, { ...note, updated: now.toISOString() }, next, note.frontMatter);
640
+ return { ok: true, slug };
641
+ });
642
+ }
643
+
599
644
  /** Rename a note in place and keep its front-matter title in sync. */
600
645
  export async function renameNote(root: string, slug: string, name: string, now = new Date()): Promise<VaultMutationResult> {
601
646
  const from = resolveNotePath(root, slug);
@@ -323,6 +323,12 @@ function mutation(fetchImpl: FetchLike, url: string, method: string, body?: unkn
323
323
  const noteUrl = (slug: string, suffix = ""): string => `/api/note/${encodeURIComponent(slug)}${suffix}`;
324
324
  const folderUrl = (path: string, suffix = ""): string => `/api/folder/${path.split("/").map(encodeURIComponent).join("/")}${suffix}`;
325
325
 
326
+ /**
327
+ * `POST /api/note/:slug` — save the note's Markdown body. No revision
328
+ * (last-write-wins, see core's `setNoteBody`), and a `MutationResult` rather
329
+ * than the note: the poll is what puts the new text back on screen.
330
+ */
331
+ export const saveNote = (fetchImpl: FetchLike, slug: string, body: string) => mutation(fetchImpl, noteUrl(slug), "POST", { body });
326
332
  export const renameNote = (fetchImpl: FetchLike, slug: string, name: string) => mutation(fetchImpl, noteUrl(slug, "/rename"), "POST", { name });
327
333
  export const moveNote = (fetchImpl: FetchLike, slug: string, folder: string | null) => mutation(fetchImpl, noteUrl(slug, "/move"), "POST", { folder });
328
334
  export const deleteNote = (fetchImpl: FetchLike, slug: string) => mutation(fetchImpl, noteUrl(slug), "DELETE");