pi-weave 0.3.7 → 0.3.9

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 (69) hide show
  1. package/README.md +17 -16
  2. package/package.json +4 -3
  3. package/skills/weave-explore/SKILL.md +3 -5
  4. package/skills/weave-notepad/SKILL.md +30 -4
  5. package/skills/weave-notepad/references/second-thoughts.md +57 -0
  6. package/src/core/graph/build.ts +1 -1
  7. package/src/core/graph/current.ts +10 -8
  8. package/src/core/history.ts +417 -0
  9. package/src/core/index.ts +19 -0
  10. package/src/core/links/repair.ts +5 -68
  11. package/src/core/noteAction.ts +72 -2
  12. package/src/core/openInEditor.ts +3 -8
  13. package/src/core/repoAction.ts +20 -61
  14. package/src/core/repoIndex.ts +38 -0
  15. package/src/core/trail.ts +124 -0
  16. package/src/core/types.ts +1 -3
  17. package/src/core/vault.ts +116 -29
  18. package/src/core/view/focus.ts +2 -2
  19. package/src/core/view/index.ts +2 -3
  20. package/src/core/view/rawTail.ts +153 -0
  21. package/src/core/workspace.ts +20 -26
  22. package/src/core/workspaceCommands.ts +14 -14
  23. package/src/opencode/tools.ts +3 -3
  24. package/src/opencode/tui.ts +1 -7
  25. package/src/pi/index.ts +68 -80
  26. package/src/pi/tools/noteTool.ts +1 -1
  27. package/src/pi/tools/repoTool.ts +3 -6
  28. package/src/pi/viewer/web/run.ts +6 -8
  29. package/src/web/client/api.ts +103 -5
  30. package/src/web/client/dist/app.js +183 -52
  31. package/src/web/client/graph/Graph.tsx +23 -2
  32. package/src/web/client/graph/column.model.ts +1 -1
  33. package/src/web/client/graph/graph.model.ts +69 -7
  34. package/src/web/client/graph/renderer.ts +19 -3
  35. package/src/web/client/note/Note.tsx +171 -20
  36. package/src/web/client/note/Replay.tsx +128 -0
  37. package/src/web/client/note/drafts.ts +50 -9
  38. package/src/web/client/note/note.model.ts +17 -0
  39. package/src/web/client/note/replay.model.ts +173 -0
  40. package/src/web/client/shell/ContextRail.tsx +7 -0
  41. package/src/web/client/shell/Settings.tsx +21 -1
  42. package/src/web/client/shell/Shell.tsx +34 -10
  43. package/src/web/client/shell/keys.model.ts +20 -7
  44. package/src/web/client/shell/theme.ts +118 -1
  45. package/src/web/client/shell/workspace.css.ts +4 -0
  46. package/src/web/client/trail/TrailStrip.tsx +55 -0
  47. package/src/web/client/trail/trail.model.ts +195 -0
  48. package/src/web/client/trail/trail.ts +45 -0
  49. package/src/web/client/tree/Tree.tsx +7 -3
  50. package/src/web/client/tree/tree.model.ts +2 -5
  51. package/src/web/server/page.ts +1 -1
  52. package/src/web/server/routes.ts +83 -3
  53. package/src/web/shared/logo.ts +1 -1
  54. package/src/web/shared/view.ts +10 -4
  55. package/src/web/shared/wire.ts +74 -1
  56. package/src/core/view/health.ts +0 -160
  57. package/src/pi/viewer/tui/bodyStore.ts +0 -95
  58. package/src/pi/viewer/tui/branding.ts +0 -14
  59. package/src/pi/viewer/tui/model.ts +0 -390
  60. package/src/pi/viewer/tui/openNote.ts +0 -11
  61. package/src/pi/viewer/tui/run.ts +0 -71
  62. package/src/pi/viewer/tui/surface/base.ts +0 -203
  63. package/src/pi/viewer/tui/surface/detail.ts +0 -315
  64. package/src/pi/viewer/tui/surface/explore.ts +0 -241
  65. package/src/pi/viewer/tui/surface/focus.ts +0 -139
  66. package/src/pi/viewer/tui/surface/health.ts +0 -102
  67. package/src/pi/viewer/tui/theme.ts +0 -90
  68. package/src/pi/viewer/tui/workspace.ts +0 -53
  69. package/src/pi/viewer/tui/workspaceRoot.ts +0 -389
package/README.md CHANGED
@@ -113,7 +113,7 @@ You: Start a note for this interview. I’m going to narrate; keep my words
113
113
 
114
114
  For each chunk, the agent:
115
115
 
116
- 1. appends your words unchanged to the note’s `## Raw` tail;
116
+ 1. appends your words unchanged to the note’s `## Raw` tail, starting with the very first chunk;
117
117
  2. refreshes the structured summary above it;
118
118
  3. leaves the raw record untouched.
119
119
 
@@ -152,12 +152,13 @@ Renaming or moving a note rewrites its inbound links automatically, so its backl
152
152
 
153
153
  ```bash
154
154
  /weave-view # open the browser workspace
155
- /weave-view --no-open # start it and print the URL
156
- /weave-view tui # Pi only: terminal UI for SSH or browser-free use
155
+ /weave-view --no-open # start it and print the URL (handy over SSH)
157
156
  ```
158
157
 
159
158
  Browse and search your notes, follow links, or explore the graph. Use tabs and split panes to keep related notes side by side.
160
159
  Notes update within a couple of seconds as your agent writes them.
160
+ While your agent works, the notes it searches, reads, and writes light up in the tree and graph, and fade after about a minute.
161
+ The **This turn** list in the Context sidebar shows those steps in order; click an earlier turn to replay it.
161
162
 
162
163
  Ask your agent to write, organize, and connect notes, or click **New note** on an empty tab to start one yourself.
163
164
  Click **Edit** to change Markdown and `⌘S` / `Ctrl S` to save. Your original words in `## Raw` stay intact.
@@ -165,7 +166,8 @@ Tabs, layout, and settings restore when you return and survive package updates f
165
166
  unsaved drafts last only while the browser stays open.
166
167
 
167
168
  Use **Settings** for themes, fonts, layout, and a manual vault ZIP backup. Press `?` for keyboard shortcuts.
168
- `/weave-view tui` opens a smaller, read-only terminal explorer in Pi.
169
+
170
+ Every change to a note is saved to `<vault>/history/`, whether you, your agent, or another editor made it. Press **History** on a note and drag the slider (or press play) to watch it being written, Raw tail included. Turn this off in **Settings → Editor → Note history**.
169
171
 
170
172
  ## Remember sessions
171
173
 
@@ -182,18 +184,18 @@ technical takeaways, works outside Git repositories, and can be stopped with `/w
182
184
 
183
185
  ## Repository knowledge
184
186
 
185
- Inside a Git repository, pi-weave detects whether `<repo>/.okf/` is missing, fresh, or stale.
187
+ Inside a Git repository, `/weave-scan` asks your session model for a short summary of each file, so you and your agent can find your way
188
+ around without reading everything. It only revisits files whose content changed since the last run.
186
189
 
187
190
  ```bash
188
- /weave-scan # fast structural index
189
- /weave-scan deep # also summarize changed files with the active model
191
+ /weave-scan # summarize changed files with the active model
190
192
  ```
191
193
 
192
- The light index covers languages, packages, modules, entry points, and Git state. A deep scan adds short per-file summaries and only
193
- revisits files whose content changed.
194
+ pi-weave also keeps a small structural index of the repository (languages, packages, modules, entry points, Git state). It feeds the graph
195
+ and links notes to the files they mention. You never build it by hand: it is refreshed automatically whenever something needs it.
194
196
 
195
- The repository index is a cache, not a source of truth. Delete `.okf`, scan again, and nothing important is lost. pi-weave excludes it
196
- locally from Git by default.
197
+ Everything under `<repo>/.okf/` is a cache, not a source of truth. Delete it and nothing important is lost. pi-weave excludes it locally
198
+ from Git by default.
197
199
 
198
200
  ## Commands and tools
199
201
 
@@ -201,14 +203,13 @@ Most people only need natural language and `/weave-view`.
201
203
 
202
204
  | Surface | Name | Purpose |
203
205
  |---|---|---|
204
- | Command | `/weave-view` | Open the browser workspace (`tui` is a Pi-only argument) |
206
+ | Command | `/weave-view` | Open the browser workspace |
205
207
  | Command | `/weave` | Show vault and repository status |
206
- | Command | `/weave-scan` | Build or refresh the repository index |
207
- | Command | `/weave-scan deep` | Add incremental model-written file summaries |
208
+ | Command | `/weave-scan` | Add incremental model-written file summaries |
208
209
  | Command | `/weave-scan sessions [path]` | Turn changed session-history files into durable memory notes |
209
- | Command | `/weave-scan-cancel` | Stop a deep or session scan |
210
+ | Command | `/weave-scan-cancel` | Stop a running scan |
210
211
  | Tool | `weave_note` | List, read, add, append, finalize, and search notes |
211
- | Tool | `weave_repo` | Check, scan, and summarize the repository index |
212
+ | Tool | `weave_repo` | Describe the repository structure for the agent |
212
213
 
213
214
  The included `weave-notepad` and `weave-explore` skills teach Pi and OpenCode when and how to use these tools. In OpenCode, `/weave` shows
214
215
  vault/repository status and scan progress appears in toasts; Pi keeps its persistent status line. V1 slash commands use its standard prompt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-weave",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Local-first Markdown memory and knowledge graph for Pi and OpenCode: preserve decisions, recall sessions, and explore notes with code.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -59,6 +59,7 @@
59
59
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
60
60
  "build:web": "node scripts/build-web.mjs",
61
61
  "build:web:check": "node scripts/build-web.mjs --check",
62
+ "e2e": "vitest run --config vitest.e2e.config.ts",
62
63
  "check": "npm run typecheck && npm run build:web:check && npm run coverage",
63
64
  "eval:notepad": "node scripts/eval-notepad-search.mjs",
64
65
  "rewrap:md": "node scripts/rewrap-md.mjs",
@@ -68,14 +69,13 @@
68
69
  "peerDependencies": {
69
70
  "@earendil-works/pi-ai": "*",
70
71
  "@earendil-works/pi-coding-agent": "*",
71
- "@earendil-works/pi-tui": "*",
72
72
  "typebox": "*"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@earendil-works/pi-ai": "^0.84.2",
76
76
  "@earendil-works/pi-coding-agent": "^0.84.2",
77
- "@earendil-works/pi-tui": "^0.84.2",
78
77
  "@opencode-ai/plugin": "1.18.29",
78
+ "@opencode-ai/sdk": "1.18.29",
79
79
  "@opencode/plugin": "^2.0.19",
80
80
  "@types/d3-force": "^3.0.10",
81
81
  "@types/node": "^24.0.0",
@@ -86,6 +86,7 @@
86
86
  "graphology": "^0.26.0",
87
87
  "graphology-types": "^0.24.8",
88
88
  "marked": "^18.0.10",
89
+ "playwright-core": "1.56.1",
89
90
  "preact": "^10.29.8",
90
91
  "sigma": "^3.0.3",
91
92
  "typebox": "1.3.7",
@@ -9,17 +9,15 @@ Use `weave_repo`. Without the tool, read `<repo>/.okf/` directly. The index is a
9
9
 
10
10
  ## Workflow
11
11
 
12
- 1. Check `action=status`: if missing, offer a scan or scan when exploration was requested; if stale, rescan; if fresh, read
13
- `action=overview`.
12
+ 1. Read `action=overview`. It builds or refreshes the structure index itself, so there is nothing to scan first.
14
13
  2. Use the overview's languages, packages, modules, and entry points to select relevant paths instead of listing the whole repository.
15
14
  3. Read available `.okf/repository/summaries/` sidecars before opening full files.
16
15
  4. Inspect the relevant code and cite indexed paths in your answer.
17
16
 
18
17
  ## Commands
19
18
 
20
- - `/weave-scan` refreshes the index (`weave_repo` action=scan).
21
- - `/weave-scan deep` generates file summaries using the session model. Offer it when summaries are missing or stale; never run it
22
- implicitly. Only changed content is summarized again.
19
+ - `/weave-scan` generates file summaries using the session model. Offer it when summaries would help and the overview shows none;
20
+ never run it implicitly. Only changed content is summarized again.
23
21
  - `/weave-view` opens the browser workspace.
24
22
  - `/weave-scan sessions [path]` writes session memories to the vault, not the repository index; see `weave-notepad`.
25
23
 
@@ -26,9 +26,10 @@ Use `weave_note`. Without the tool, edit the files directly (`PI_WEAVE_VAULT` ov
26
26
 
27
27
  During live dictation or interviews, keep the organized note current after every append; do not wait until the session ends:
28
28
 
29
- 1. `append` with `raw: true` preserves the user's words verbatim under `## Raw`. Never silently reword dictation.
30
- 2. Immediately `finalize` the body above the tail: front-loaded summary, sections, decisions, questions, tasks, entities, and links
31
- reflecting everything said so far.
29
+ 1. Start the note with `add` and `raw: true`, so its first words land in `## Raw` too. Then `append` each chunk with `raw: true`.
30
+ Never silently reword dictation.
31
+ 2. Immediately `finalize` the body above the tail: TLDR, sections, decisions, questions, tasks, entities, and links reflecting
32
+ everything said so far.
32
33
 
33
34
  Outside dictation, finalize only on request.
34
35
 
@@ -36,7 +37,7 @@ Outside dictation, finalize only on request.
36
37
 
37
38
  - Keep an append-only raw section at the bottom: `---`, then `## Raw`, then the notice shown below.
38
39
  - Fence verbatim input in code blocks. Prepend each subsequent block with `<!-- appended YYYY-MM-DD HH:MM -->`.
39
- - Use `append` with `raw: true` when available; it maintains this format and creates the tail if missing. Do not hand-format it.
40
+ - Use `add` or `append` with `raw: true` when available; they maintain this format and create the tail if missing. Do not hand-format it.
40
41
  - `finalize` changes only the body above the tail. Never rewrite, remove, or move words out of the raw tail. If no tail exists, preserve the
41
42
  entire previous body as a new raw tail before restructuring; the tool does this automatically.
42
43
 
@@ -58,6 +59,31 @@ When editing files directly, preserve the same format:
58
59
  ```
59
60
  ````
60
61
 
62
+ ## Second thoughts
63
+
64
+ When the user states a decision, check the `search` hits you already made and the target note's decisions for an earlier **human**
65
+ decision that it contradicts. Do not run extra searches or reads for this.
66
+
67
+ - Keep the bar high: an explicit decision against an explicit decision on the same subject. Overlapping facts, refinements, and
68
+ compatible choices are not conflicts. When unsure, stay silent.
69
+ - Capture first, then ask. Save the user's words as usual, then ask one short question quoting the earlier decision with its date and
70
+ slug: replace it, or are both true? At most one question per turn.
71
+ - `source` is per note, not per line, so judge each decision by where it came from. It is human when the user's words for it are in the
72
+ raw tail, or when it sits in a `source: human` note outside an "Agent addendum". Anything else counts as agent.
73
+ - Conflicting agent or `generated` decisions do not get a question; the human statement already wins. Mention them in one line.
74
+ - If the user says it replaces the old one, strike the old decision through instead of deleting it, and link the new one:
75
+ `~~OIDC next quarter~~ (superseded 2026-10-11 → [[auth-sso]])`, where the link names the note that holds the new decision. When the
76
+ new decision is in the same note, drop the link: `(superseded 2026-10-11)`. A note should not link to itself.
77
+ - Never edit the raw tail to supersede anything. `finalize` keeps superseded lines, struck through, in the body.
78
+
79
+ See [references/second-thoughts.md](references/second-thoughts.md) for example exchanges.
80
+
81
+ ## TLDR on finalize
82
+
83
+ Every finalized note opens with a TLDR directly under the title: a `**TLDR:**` block of 1–8 lines on what the note says and what
84
+ matters most. Bullets and lists are fine. Always list action items, if any, as `- [ ]` checkboxes. Rewrite it on each `finalize` so it reflects the whole
85
+ note; never stack a second one.
86
+
61
87
  ## Retrieve
62
88
 
63
89
  - Known slug: `get`. Otherwise, one targeted `search` with the user's terms; never list the vault to find a note.
@@ -0,0 +1,57 @@
1
+ # Second thoughts
2
+
3
+ Catch a decision that contradicts an earlier human decision while the user is there to answer in one word. Silence is the default; a
4
+ false alarm costs more than a missed one.
5
+
6
+ ## Conflict: ask once, after saving
7
+
8
+ ```text
9
+ User: Add to the auth note that we're going with SAML.
10
+
11
+ Agent: (search "auth" → auth-migration; Decisions has "2026-09-03: OIDC next quarter, JWT compatibility window", source: human)
12
+ (append to auth-migration)
13
+
14
+ Agent: Added. One thing: on Sep 3 you decided "OIDC next quarter, JWT compatibility window" (auth-migration, Decisions).
15
+ Should SAML replace that decision, or are both true?
16
+
17
+ User: Replace it.
18
+
19
+ Agent: (edit the body above the raw tail)
20
+ Updated. The Sep 3 decision is now marked superseded.
21
+ ```
22
+
23
+ The Decisions section afterwards:
24
+
25
+ ```markdown
26
+ ## Decisions
27
+
28
+ - ~~2026-09-03: OIDC next quarter, JWT compatibility window~~ (superseded 2026-10-11)
29
+ - 2026-10-11: Go with SAML.
30
+ ```
31
+
32
+ The new decision is in the same note, so there is no link. When it lives in another note, link it:
33
+ `(superseded 2026-10-11 → [[auth-sso]])`.
34
+
35
+ If the user answers "both are true", leave both lines as they are and say so.
36
+
37
+ ## No question
38
+
39
+ | New statement | Earlier note | Why silent |
40
+ |---|---|---|
41
+ | "Use SAML for the admin portal" | human: "OIDC for the customer app" | Different subjects |
42
+ | "Use Okta as the OIDC provider" | human: "OIDC next quarter" | Compatible; it builds on the decision |
43
+ | "We're going with SAML" | generated session note: "team leaning OIDC" | Not a human decision; mention it in one line |
44
+ | "SAML might be simpler" | human: "OIDC next quarter" | Not a decision |
45
+ | "We're going with SAML" | "OIDC next quarter" in the Agent addendum of a `source: human` note | Agent-written line; mention it in one line |
46
+
47
+ ## Mixed authorship
48
+
49
+ `source` describes who created the note, not each line in it. A decision the user dictated into an agent note is human: its words are in
50
+ the raw tail. A decision in the Agent addendum of a human note is the agent's. Check the raw tail or the addendum before deciding whether to
51
+ ask.
52
+
53
+ ## Rules of thumb
54
+
55
+ - Use the hits from the one `search` the capture already made; no extra calls to look for conflicts.
56
+ - At most one question per turn, even if several decisions conflict. Name the strongest; mention others in the same question.
57
+ - Never delete the old decision and never touch the raw tail. Superseded lines survive `finalize`, struck through.
@@ -11,7 +11,7 @@
11
11
  import type { HtmlArtifact, Note, RepoIndex, StalenessReport, VaultStatus } from "../types";
12
12
  import { createHash } from "node:crypto";
13
13
  import type { SummaryRecord } from "../summaries";
14
- import type { EdgeKind, GraphEdge, GraphModel, GraphNode } from "./model";
14
+ import type { GraphEdge, GraphModel, GraphNode } from "./model";
15
15
  import { buildPathIndex, resolveMentions, type PathIndex } from "./mentions";
16
16
  import { extractWikilinks } from "./wikilinks";
17
17
 
@@ -15,7 +15,7 @@ import { join, resolve, sep } from "node:path";
15
15
  // that cache, so going through the barrel would close an import cycle.
16
16
  import { findGitRoot } from "../git";
17
17
  import { resolveVaultRoot } from "../paths";
18
- import { assessStaleness, readRepoIndex } from "../repoIndex";
18
+ import { ensureRepoIndex } from "../repoIndex";
19
19
  import { readSummaryMap } from "../summaries";
20
20
  import type { Note } from "../types";
21
21
  import { getNote, readVault, resolveNotePath } from "../vault";
@@ -72,7 +72,7 @@ export async function readOkfFileForView(cwd: string, rel: string): Promise<{ pa
72
72
 
73
73
  /**
74
74
  * Assemble the repository half of the graph input for `cwd`, or null when
75
- * cwd is not inside a git repository with a readable `.okf` index. Shared
75
+ * cwd is not inside a git repository with at least one commit. Shared
76
76
  * with `src/core/cache/workspace` so the cached and uncached paths cannot
77
77
  * drift in what they read.
78
78
  */
@@ -81,19 +81,21 @@ export async function readRepositorySide(
81
81
  ): Promise<Pick<BuildGraphInput, "repository" | "summaries"> | null> {
82
82
  const repoRoot = await findGitRoot(cwd);
83
83
  if (repoRoot === null) return null;
84
- const index = await readRepoIndex(repoRoot);
85
- if (index === null) return null;
84
+ // The structure index is plumbing: built or refreshed here when missing or
85
+ // stale, so the repository side of the graph never waits on a manual scan.
86
+ const repository = await ensureRepoIndex(repoRoot);
87
+ if (repository === null) return null;
86
88
  return {
87
- repository: { index, staleness: await assessStaleness(repoRoot) },
89
+ repository,
88
90
  summaries: await readSummaryMap(repoRoot), // deep-scan sidecars, read live
89
91
  };
90
92
  }
91
93
 
92
94
  /**
93
95
  * Assemble the fresh graph from disk. Called on every viewer fetch
94
- * (no caching). Reads the complete vault and, when cwd is an indexed git
95
- * repository, the repo index + deep-scan summary sidecars. Degrades to a vault-only graph when
96
- * the repo has no index or the index is corrupt.
96
+ * (no caching). Reads the complete vault and, when cwd is inside a git
97
+ * repository, the repo index (refreshed on demand) + deep-scan summary
98
+ * sidecars. Degrades to a vault-only graph when the repo has no commits.
97
99
  *
98
100
  * One read per note: `readVault` returns bodies *and* the file count, so the
99
101
  * old `listNotes` → `getNote`-per-slug → `noteCount` sequence (2N reads plus