backpass 0.1.9 → 0.1.11
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 +7 -1
- package/package.json +1 -1
- package/src/apply/lavish.js +4 -1
- package/src/discovery/adapters/pi.js +63 -4
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ backpass reads the local transcript stores of seven harnesses directly. No API,
|
|
|
84
84
|
| -------------- | ---------------------------------------------- | --------------------------------------------------- |
|
|
85
85
|
| **claude** | `~/.claude/projects/<munged-cwd>/<uuid>.jsonl` | per-line `cwd` |
|
|
86
86
|
| **codex** | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` | `cwd` + recorded `git.repository_url` |
|
|
87
|
-
| **pi** |
|
|
87
|
+
| **pi** | standalone and BB-managed Pi JSONL stores | session-header `cwd` |
|
|
88
88
|
| **opencode** | `~/.local/share/opencode/opencode.db` (sqlite) | `session.directory` |
|
|
89
89
|
| **grok** | `~/.grok/sessions/<encoded-cwd>/<uuid>/` | `summary.json` `cwd` + `git_remotes` |
|
|
90
90
|
| **cursor CLI** | `~/.cursor/chats/<md5(cwd)>/<uuid>/` | `meta.json` `cwd` |
|
|
@@ -95,6 +95,12 @@ relocated config dir does not hide its sessions. The variable is read from backp
|
|
|
95
95
|
environment: if you reach that profile through an alias that only prefixes `claude`, set it
|
|
96
96
|
for the backpass run too (`CLAUDE_CONFIG_DIR=~/.claude-work backpass`, or export it).
|
|
97
97
|
|
|
98
|
+
Pi collection covers standalone sessions under `~/.pi/agent/sessions/` and BB-managed Pi
|
|
99
|
+
sessions under `~/.bb/pi-bridge-sessions/`. It also honors `PI_CODING_AGENT_DIR`,
|
|
100
|
+
`PI_CODING_AGENT_SESSION_DIR`, `BB_DATA_DIR`, and `BB_PI_BRIDGE_SESSION_DIR` when they are
|
|
101
|
+
set in backpass's environment. When roots overlap, backpass scans every applicable layout
|
|
102
|
+
and reads each JSONL file once.
|
|
103
|
+
|
|
98
104
|
Hermes collection includes CLI and ACP sessions only. Gateway, cron, and WhatsApp sessions
|
|
99
105
|
are excluded because their recorded cwd belongs to the shared gateway process, not a project.
|
|
100
106
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backpass",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"packageManager": "pnpm@11.5.0",
|
|
5
5
|
"description": "Gradient descent for your agent memory - analyzes past agent session transcripts and proposes evidence-backed edits to AGENTS.md / CLAUDE.md",
|
|
6
6
|
"type": "module",
|
package/src/apply/lavish.js
CHANGED
|
@@ -26,7 +26,10 @@ export function injectPayload(template, payload, toolVersion) {
|
|
|
26
26
|
.replace(/\u2029/g, "\\u2029");
|
|
27
27
|
const tag = `<script>window.__BACKPASS_PROPOSAL__ = ${json};</script>`;
|
|
28
28
|
if (!template.includes("</head>")) return `${tag}\n${template}`;
|
|
29
|
-
|
|
29
|
+
// A function replacer, not a string one: a string second argument to `replace` treats
|
|
30
|
+
// `$&`, `` $` ``, `$'`, and `$$` in it as replacement patterns, and `tag` embeds
|
|
31
|
+
// untrusted proposal text that can contain any of those sequences verbatim.
|
|
32
|
+
return template.replace("</head>", () => `${tag}\n</head>`);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export function renderApplySurface(proposal, state, toolVersion) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -13,7 +14,9 @@ import {
|
|
|
13
14
|
} from "./shared.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
+
* Pi writes standalone sessions under
|
|
18
|
+
* `~/.pi/agent/sessions/<escaped-cwd>/<ISO-ts>_<uuid>.jsonl`. BB's Pi bridge writes the
|
|
19
|
+
* same JSONL shape directly under `<bb-data-dir>/pi-bridge-sessions/`.
|
|
17
20
|
*
|
|
18
21
|
* Line 1 is `{type:"session", cwd, id}`. Entries form a parent/child tree but arrive in
|
|
19
22
|
* order, so a linear read is faithful. `model_change` / `thinking_level_change` records
|
|
@@ -26,13 +29,69 @@ export function storeRoot() {
|
|
|
26
29
|
return home(".pi", "agent", "sessions");
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
function expandEnvPath(value) {
|
|
33
|
+
const trimmed = value?.trim();
|
|
34
|
+
if (!trimmed) return null;
|
|
35
|
+
if (trimmed === "~") return home();
|
|
36
|
+
if (trimmed.startsWith("~/")) return path.join(home(), trimmed.slice(2));
|
|
37
|
+
return path.resolve(trimmed);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function realpathOrResolve(value) {
|
|
41
|
+
try {
|
|
42
|
+
return fs.realpathSync(value);
|
|
43
|
+
} catch {
|
|
44
|
+
return path.resolve(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function storeSpecs() {
|
|
49
|
+
const specs = [
|
|
50
|
+
{ path: storeRoot(), direct: false, nested: true },
|
|
51
|
+
{ path: home(".bb", "pi-bridge-sessions"), direct: true, nested: false },
|
|
52
|
+
];
|
|
53
|
+
const piAgentDir = expandEnvPath(process.env.PI_CODING_AGENT_DIR);
|
|
54
|
+
if (piAgentDir) specs.push({ path: path.join(piAgentDir, "sessions"), direct: false, nested: true });
|
|
55
|
+
const piSessionDir = expandEnvPath(process.env.PI_CODING_AGENT_SESSION_DIR);
|
|
56
|
+
if (piSessionDir) specs.push({ path: piSessionDir, direct: true, nested: false });
|
|
57
|
+
const bbDataDir = expandEnvPath(process.env.BB_DATA_DIR);
|
|
58
|
+
if (bbDataDir) specs.push({ path: path.join(bbDataDir, "pi-bridge-sessions"), direct: true, nested: false });
|
|
59
|
+
const bridgeDir = expandEnvPath(process.env.BB_PI_BRIDGE_SESSION_DIR);
|
|
60
|
+
if (bridgeDir) specs.push({ path: bridgeDir, direct: true, nested: false });
|
|
61
|
+
|
|
62
|
+
const unique = new Map();
|
|
63
|
+
for (const spec of specs) {
|
|
64
|
+
const key = realpathOrResolve(spec.path);
|
|
65
|
+
const existing = unique.get(key);
|
|
66
|
+
if (existing) {
|
|
67
|
+
existing.direct ||= spec.direct;
|
|
68
|
+
existing.nested ||= spec.nested;
|
|
69
|
+
} else {
|
|
70
|
+
unique.set(key, spec);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...unique.values()];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function storeRoots() {
|
|
77
|
+
return storeSpecs().map((spec) => spec.path);
|
|
78
|
+
}
|
|
79
|
+
|
|
29
80
|
export function enumerate() {
|
|
30
81
|
const out = [];
|
|
31
|
-
|
|
32
|
-
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
for (const spec of storeSpecs()) {
|
|
84
|
+
const files = [
|
|
85
|
+
...(spec.direct ? listFiles(spec.path, ".jsonl") : []),
|
|
86
|
+
...(spec.nested ? listDirs(spec.path).flatMap((dir) => listFiles(dir, ".jsonl")) : []),
|
|
87
|
+
];
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const key = realpathOrResolve(file);
|
|
90
|
+
if (seen.has(key)) continue;
|
|
91
|
+
seen.add(key);
|
|
33
92
|
const stat = statOrNull(file);
|
|
34
93
|
if (!stat) continue;
|
|
35
|
-
out.push({ key
|
|
94
|
+
out.push({ key, path: file, mtimeMs: stat.mtimeMs, bytes: stat.size });
|
|
36
95
|
}
|
|
37
96
|
}
|
|
38
97
|
return out;
|