pi-posthorse 0.8.1 → 0.8.2

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
@@ -17,12 +17,12 @@ Required qualification targets: latest stable official Pi and latest maintained
17
17
 
18
18
  ## Install
19
19
 
20
- Posthorse `0.8.1` requires Pi `>=1.0.0`. Update the host before installing this release; do not activate it on an older live fork.
20
+ Posthorse `0.8.2` requires Pi `>=1.0.0`. Update the host before installing this release; do not activate it on an older live fork.
21
21
 
22
22
  ```bash
23
- pi install npm:pi-posthorse@0.8.1 # from npm
24
- pi install git:github.com/fitchmultz/pi-posthorse@v0.8.1 # from Git
25
- pi -e npm:pi-posthorse@0.8.1 # try it for one run without installing
23
+ pi install npm:pi-posthorse@0.8.2 # from npm
24
+ pi install git:github.com/fitchmultz/pi-posthorse@v0.8.2 # from Git
25
+ pi -e npm:pi-posthorse@0.8.2 # try it for one run without installing
26
26
  ```
27
27
 
28
28
  Omit the version or tag to follow the latest release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-posthorse",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "description": "Posthorse: fresh context, same journey. No-summary context rollover for Pi, with sparse budget reminders, handoffs, notes, and history.",
6
6
  "keywords": [
@@ -213,7 +213,8 @@ export function toWindowedEntry(
213
213
  images: imagesOf(entryContent(entry)),
214
214
  };
215
215
  }
216
- export function windowProjection(): (entry: EntryLike) => WindowedEntry | undefined {
216
+ /** Window ancestry is metadata-only; extracting text/images is a separate operation. */
217
+ export function windowProjection(): (entry: EntryLike) => string {
217
218
  const windows = new Map<string, string>();
218
219
  return (entry) => {
219
220
  const inherited = windows.get(entry.parentId ?? "") ?? "initial";
@@ -221,13 +222,15 @@ export function windowProjection(): (entry: EntryLike) => WindowedEntry | undefi
221
222
  if (entry.id !== undefined && entry.id !== "") {
222
223
  windows.set(entry.id, windowId);
223
224
  }
224
- return toWindowedEntry(entry, windowId);
225
+ return windowId;
225
226
  };
226
227
  }
227
- export function* windowEntries(entries: readonly EntryLike[]): Generator<WindowedEntry> {
228
- const project = windowProjection();
229
- for (const entry of entries) {
230
- const item = project(entry);
228
+ export function* reverseWindowEntries(entries: readonly EntryLike[]): Generator<WindowedEntry> {
229
+ // Preserve parent-relative window labels without normalizing the whole branch.
230
+ // ponytail: ancestry still scans metadata; a native window-aware iterator could remove that cost.
231
+ const windows = entries.map(windowProjection());
232
+ for (let i = entries.length - 1; i >= 0; i--) {
233
+ const item = toWindowedEntry(entries[i], windows[i]);
231
234
  if (item !== undefined) {
232
235
  yield item;
233
236
  }
@@ -7,7 +7,7 @@ import {
7
7
  type HistoryHit,
8
8
  type ProjectedEntry,
9
9
  } from "./contracts.ts";
10
- import { historyHit, seenText, windowEntries } from "./history-entries.ts";
10
+ import { historyHit, seenText, reverseWindowEntries } from "./history-entries.ts";
11
11
  import { shortKey } from "./message-text.ts";
12
12
  import {
13
13
  filesContaining,
@@ -267,7 +267,7 @@ function branchMatches(
267
267
  accumulator: SearchProgress,
268
268
  ): number {
269
269
  let skipped = 0;
270
- for (const item of [...windowEntries(source.branch())].reverse()) {
270
+ for (const item of reverseWindowEntries(source.branch())) {
271
271
  const hit = historyHit(item, query);
272
272
  if (hit === undefined) {
273
273
  continue;
package/src/history.ts CHANGED
@@ -3,7 +3,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
3
3
  import { Type } from "typebox";
4
4
  import { toolCards } from "../ui.ts";
5
5
  import { requireValue, type PolicyContext, type WindowedEntry } from "./contracts.ts";
6
- import { windowEntries } from "./history-entries.ts";
6
+ import { toWindowedEntry, windowProjection } from "./history-entries.ts";
7
7
  import { historyReadPage, searchPage } from "./history-pages.ts";
8
8
  import { historyMatches, type HistorySource } from "./history-search.ts";
9
9
  import type { PageAccess } from "./paging.ts";
@@ -17,18 +17,26 @@ function entryReference(id: string): { readonly entryId: string; readonly fileKe
17
17
  fileKey: separator < 0 ? undefined : id.slice(separator + 1),
18
18
  };
19
19
  }
20
+ function branchEntry(source: HistorySource, id: string): WindowedEntry | undefined {
21
+ const project = windowProjection();
22
+ for (const entry of source.branch()) {
23
+ const windowId = project(entry);
24
+ const item = entry.id === id ? toWindowedEntry(entry, windowId) : undefined;
25
+ if (item !== undefined) {
26
+ return item;
27
+ }
28
+ }
29
+ return undefined;
30
+ }
20
31
  async function findEntry(
21
32
  source: HistorySource,
22
33
  id: string,
23
34
  signal?: AbortSignal,
24
35
  ): Promise<LocatedEntry> {
25
36
  const { entryId, fileKey } = entryReference(id);
26
- if (fileKey === undefined) {
27
- for (const item of windowEntries(source.branch())) {
28
- if (item.entry.id === entryId) {
29
- return { item, source: "" };
30
- }
31
- }
37
+ const current = fileKey === undefined ? branchEntry(source, entryId) : undefined;
38
+ if (current !== undefined) {
39
+ return { item: current, source: "" };
32
40
  }
33
41
  const candidates =
34
42
  fileKey === undefined && /^[\w-]+$/.test(entryId)
@@ -42,10 +50,8 @@ async function findEntry(
42
50
  fileKey,
43
51
  candidates: candidates === undefined ? undefined : (candidate) => candidates.has(candidate),
44
52
  })) {
45
- for await (const item of sessionWindowEntries(file, signal)) {
46
- if (item.entry.id === entryId) {
47
- return { item, source: relative(source.dir, file) };
48
- }
53
+ for await (const item of sessionWindowEntries(file, signal, entryId)) {
54
+ return { item, source: relative(source.dir, file) };
49
55
  }
50
56
  }
51
57
  throw new Error(
@@ -11,7 +11,7 @@ import {
11
11
  type WindowedEntry,
12
12
  } from "./contracts.ts";
13
13
  import { lfLines } from "./files.ts";
14
- import { windowProjection } from "./history-entries.ts";
14
+ import { toWindowedEntry, windowProjection } from "./history-entries.ts";
15
15
  import { shortKey } from "./message-text.ts";
16
16
 
17
17
  function execFileText(
@@ -141,6 +141,7 @@ function parsedEntry(line: string): EntryLike | undefined {
141
141
  export async function* sessionWindowEntries(
142
142
  file: string,
143
143
  signal?: AbortSignal,
144
+ entryId?: string,
144
145
  ): AsyncGenerator<WindowedEntry> {
145
146
  if (!existsSync(file)) {
146
147
  return;
@@ -151,7 +152,11 @@ export async function* sessionWindowEntries(
151
152
  if (entry === undefined) {
152
153
  continue;
153
154
  }
154
- const item = project(entry);
155
+ const windowId = project(entry);
156
+ if (entryId !== undefined && entry.id !== entryId) {
157
+ continue;
158
+ }
159
+ const item = toWindowedEntry(entry, windowId);
155
160
  if (item !== undefined) {
156
161
  yield item;
157
162
  }