@wierdbytes/pi-dream 0.0.0-stage → 0.1.0
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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +102 -2
- package/agentsmd.ts +171 -0
- package/commands.ts +92 -0
- package/config.ts +194 -0
- package/curate.test.ts +138 -0
- package/curate.ts +610 -0
- package/digest.test.ts +62 -0
- package/digest.ts +323 -0
- package/gitops.ts +69 -0
- package/inbox.ts +56 -0
- package/index.ts +33 -0
- package/layout.ts +99 -0
- package/llm.ts +145 -0
- package/lock.ts +74 -0
- package/memindex.test.ts +74 -0
- package/memindex.ts +126 -0
- package/misc.test.ts +141 -0
- package/notes.test.ts +69 -0
- package/notes.ts +235 -0
- package/package.json +54 -4
- package/pipeline.ts +1149 -0
- package/project.ts +115 -0
- package/projects.test.ts +79 -0
- package/projects.ts +74 -0
- package/prompts.ts +159 -0
- package/report.ts +287 -0
- package/runner.ts +22 -0
- package/runtime.ts +428 -0
- package/scheduler.ts +133 -0
- package/search.test.ts +34 -0
- package/search.ts +153 -0
- package/secrets.ts +74 -0
- package/sessions.ts +176 -0
- package/setup.ts +148 -0
- package/state.ts +84 -0
- package/tools.ts +75 -0
package/report.ts
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dream report: the pull request body and `reports/<run>.md`.
|
|
3
|
+
*
|
|
4
|
+
* Written for a reviewer who wants to understand every change: what changed,
|
|
5
|
+
* why the curator wants it, which sessions back it, and what the independent
|
|
6
|
+
* fact check said — including the proposals it rejected.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Stage } from "./config.ts";
|
|
10
|
+
import type { Change } from "./curate.ts";
|
|
11
|
+
import type { IndexStat } from "./curate.ts";
|
|
12
|
+
import type { StageUsage } from "./llm.ts";
|
|
13
|
+
import type { Evidence } from "./notes.ts";
|
|
14
|
+
|
|
15
|
+
export const REPORT_MARK = "<!-- pi-dream:report -->";
|
|
16
|
+
|
|
17
|
+
export interface ReportSession {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
date: string;
|
|
20
|
+
project: string;
|
|
21
|
+
title: string;
|
|
22
|
+
outcome: string;
|
|
23
|
+
learnings: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ReportChange extends Change {
|
|
27
|
+
rationale: string;
|
|
28
|
+
evidence: Evidence[];
|
|
29
|
+
source: "curator" | "reviewer";
|
|
30
|
+
verdict?: { verdict: string; reason: string; corrected: boolean };
|
|
31
|
+
/** The curator's title when the fact checker rewrote it. */
|
|
32
|
+
proposedTitle?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ReportRejected {
|
|
36
|
+
ref: string;
|
|
37
|
+
op: string;
|
|
38
|
+
title: string;
|
|
39
|
+
reason: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ReportAgents {
|
|
43
|
+
project: string;
|
|
44
|
+
rel: string;
|
|
45
|
+
url?: string;
|
|
46
|
+
error?: string;
|
|
47
|
+
patch: string;
|
|
48
|
+
rationale: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ReportData {
|
|
52
|
+
runId: string;
|
|
53
|
+
date: string;
|
|
54
|
+
language: string;
|
|
55
|
+
models: Record<Stage, string>;
|
|
56
|
+
usage: Partial<Record<Stage, StageUsage>>;
|
|
57
|
+
sessions: ReportSession[];
|
|
58
|
+
sessionTitles: Record<string, string>;
|
|
59
|
+
inbox: number;
|
|
60
|
+
feedback: number;
|
|
61
|
+
unhandled: Array<{ comment: string; reason: string }>;
|
|
62
|
+
changes: ReportChange[];
|
|
63
|
+
rejected: ReportRejected[];
|
|
64
|
+
agents: ReportAgents[];
|
|
65
|
+
indexes: IndexStat[];
|
|
66
|
+
votes: number;
|
|
67
|
+
normalized: number;
|
|
68
|
+
problems: string[];
|
|
69
|
+
scopeTitles: Record<string, string>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const RU = {
|
|
73
|
+
title: "Сон",
|
|
74
|
+
sessions: "Сессий",
|
|
75
|
+
projects: "проектов",
|
|
76
|
+
inbox: "inbox",
|
|
77
|
+
comments: "комментариев ревью",
|
|
78
|
+
cost: "Стоимость",
|
|
79
|
+
models: "Модели",
|
|
80
|
+
added: "Новые заметки",
|
|
81
|
+
updated: "Обновлённые заметки",
|
|
82
|
+
moved: "Перенесены в другой scope",
|
|
83
|
+
superseded: "Замены (старая заметка закрыта, новая вместо неё)",
|
|
84
|
+
deprecated: "Устаревшие (закрыты без замены)",
|
|
85
|
+
removed: "Убрано из PR (ещё не было в main)",
|
|
86
|
+
confirmed: "Новые подтверждения существующих заметок",
|
|
87
|
+
merged: "Слито с существующими (дубликаты)",
|
|
88
|
+
skill: "Скиллы",
|
|
89
|
+
why: "зачем",
|
|
90
|
+
evidence: "доказательства",
|
|
91
|
+
check: "проверка",
|
|
92
|
+
corrected: "исправлено проверяющим",
|
|
93
|
+
proposed: "куратор предлагал",
|
|
94
|
+
reviewer: "по комментарию ревью",
|
|
95
|
+
agents: "Предложения для AGENTS.md / CLAUDE.md",
|
|
96
|
+
rejected: "Отклонено проверкой (в память не попало)",
|
|
97
|
+
unhandled: "Комментарии, которые не удалось выполнить",
|
|
98
|
+
indexes: "Что попадёт в контекст сессий (индексы MEMORY.md)",
|
|
99
|
+
tokens: "токенов",
|
|
100
|
+
lines: "строк",
|
|
101
|
+
over: "не поместилось в бюджет",
|
|
102
|
+
processed: "Обработанные сессии",
|
|
103
|
+
session: "сессия",
|
|
104
|
+
entries: "записи",
|
|
105
|
+
comment: "комментарий",
|
|
106
|
+
none: "Изменений памяти нет.",
|
|
107
|
+
howTo: `### Как ревьюить
|
|
108
|
+
- **Merge** — изменения попадут в память; индексы \`MEMORY.md\` подгружаются в новые сессии pi.
|
|
109
|
+
- **Правки прямо в PR** (файлы \`notes/*.md\`) — сон продолжит с твоей версии.
|
|
110
|
+
- **Комментарий в PR** — указание сну (править, убрать, перенести в другой проект); \`/dream review\` применит его сразу, иначе — следующий сон.
|
|
111
|
+
- **Закрыть без merge** — отклонить: эти заметки не будут предлагаться повторно без новых доказательств.
|
|
112
|
+
- Открыть сессию-доказательство: \`pi --session <id>\`.`,
|
|
113
|
+
votes: "оценок полезности заметок",
|
|
114
|
+
normalized: "заметок дополнено (id/даты)",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const EN: typeof RU = {
|
|
118
|
+
title: "Dream",
|
|
119
|
+
sessions: "Sessions",
|
|
120
|
+
projects: "projects",
|
|
121
|
+
inbox: "inbox",
|
|
122
|
+
comments: "review comments",
|
|
123
|
+
cost: "Cost",
|
|
124
|
+
models: "Models",
|
|
125
|
+
added: "New notes",
|
|
126
|
+
updated: "Updated notes",
|
|
127
|
+
moved: "Moved to another scope",
|
|
128
|
+
superseded: "Superseded (old note closed, replaced)",
|
|
129
|
+
deprecated: "Deprecated (closed, no replacement)",
|
|
130
|
+
removed: "Removed from the PR (never approved)",
|
|
131
|
+
confirmed: "New evidence for existing notes",
|
|
132
|
+
merged: "Merged into existing notes (duplicates)",
|
|
133
|
+
skill: "Skills",
|
|
134
|
+
why: "why",
|
|
135
|
+
evidence: "evidence",
|
|
136
|
+
check: "check",
|
|
137
|
+
corrected: "corrected by the fact checker",
|
|
138
|
+
proposed: "the curator proposed",
|
|
139
|
+
reviewer: "from a review comment",
|
|
140
|
+
agents: "AGENTS.md / CLAUDE.md proposals",
|
|
141
|
+
rejected: "Rejected by the fact check (not in memory)",
|
|
142
|
+
unhandled: "Comments that could not be applied",
|
|
143
|
+
indexes: "What sessions will load (MEMORY.md indexes)",
|
|
144
|
+
tokens: "tokens",
|
|
145
|
+
lines: "lines",
|
|
146
|
+
over: "over budget",
|
|
147
|
+
processed: "Processed sessions",
|
|
148
|
+
session: "session",
|
|
149
|
+
entries: "entries",
|
|
150
|
+
comment: "comment",
|
|
151
|
+
none: "No memory changes.",
|
|
152
|
+
howTo: `### How to review
|
|
153
|
+
- **Merge** — the changes become memory; the \`MEMORY.md\` indexes load into new pi sessions.
|
|
154
|
+
- **Edit in the PR** (\`notes/*.md\`) — the next dream continues from your version.
|
|
155
|
+
- **Comment on the PR** — an instruction for the dream (edit, drop, move to another project); \`/dream review\` applies it now, otherwise the next dream does.
|
|
156
|
+
- **Close without merging** — reject: these notes are not proposed again without new evidence.
|
|
157
|
+
- Open an evidence session: \`pi --session <id>\`.`,
|
|
158
|
+
votes: "note usefulness votes",
|
|
159
|
+
normalized: "notes completed (ids/dates)",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export function strings(language: string): typeof RU {
|
|
163
|
+
return language.toLowerCase().startsWith("ru") ? RU : EN;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function money(n: number): string {
|
|
167
|
+
return `$${n.toFixed(n < 1 ? 3 : 2)}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function evidenceLine(e: Evidence, d: ReportData, t: typeof RU): string {
|
|
171
|
+
if (e.s.startsWith("inbox:")) return `inbox \`${e.s.slice(6)}\``;
|
|
172
|
+
if (e.s.startsWith("pr#")) return `${t.comment} ${e.s}`;
|
|
173
|
+
const title = d.sessionTitles[e.s];
|
|
174
|
+
const ids = e.e.length ? `, ${t.entries} ${e.e.map((x) => `\`${x}\``).join(" ")}` : "";
|
|
175
|
+
const kind = e.kind === "user" ? " 👤" : "";
|
|
176
|
+
return `${t.session}${title ? ` «${title}»` : ""} ${e.date ?? ""} \`${e.s.slice(0, 8)}\`${ids}${kind}`.replace(/\s+/g, " ");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function scopeLabel(scope: string, d: ReportData): string {
|
|
180
|
+
return d.scopeTitles[scope] ?? scope;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function prTitle(d: ReportData): string {
|
|
184
|
+
const c = (k: string) => d.changes.filter((x) => x.kind === k).length;
|
|
185
|
+
const parts = [
|
|
186
|
+
c("added") && `+${c("added")}`,
|
|
187
|
+
c("updated") + c("moved") && `~${c("updated") + c("moved")}`,
|
|
188
|
+
c("superseded") && `⇄${c("superseded")}`,
|
|
189
|
+
c("deprecated") + c("removed") && `−${c("deprecated") + c("removed")}`,
|
|
190
|
+
c("confirmed") + c("merged") && `✓${c("confirmed") + c("merged")}`,
|
|
191
|
+
c("skill") && `skills ${c("skill")}`,
|
|
192
|
+
d.agents.length && `AGENTS.md ${d.agents.length}`,
|
|
193
|
+
].filter(Boolean);
|
|
194
|
+
return `pi-dream ${d.date}: ${parts.join(" ") || "review"}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function renderReport(d: ReportData): string {
|
|
198
|
+
const t = strings(d.language);
|
|
199
|
+
const out: string[] = [REPORT_MARK, `# ${t.title} ${d.date} · \`${d.runId}\``, ""];
|
|
200
|
+
const projects = new Set(d.sessions.map((s) => s.project)).size;
|
|
201
|
+
const cost = Object.values(d.usage).reduce((s, u) => s + (u?.cost ?? 0), 0);
|
|
202
|
+
out.push(
|
|
203
|
+
`**${t.sessions}:** ${d.sessions.length} (${t.projects}: ${projects}) · **${t.inbox}:** ${d.inbox} · **${t.comments}:** ${d.feedback}` +
|
|
204
|
+
(d.votes ? ` · ${t.votes}: ${d.votes}` : "") +
|
|
205
|
+
(d.normalized ? ` · ${t.normalized}: ${d.normalized}` : ""),
|
|
206
|
+
);
|
|
207
|
+
const usage = (Object.entries(d.usage) as Array<[Stage, StageUsage]>)
|
|
208
|
+
.map(([stage, u]) => `${stage} ${money(u.cost)} (${u.calls}×)`)
|
|
209
|
+
.join(", ");
|
|
210
|
+
out.push(`**${t.cost}:** ${money(cost)}${usage ? ` — ${usage}` : ""}`);
|
|
211
|
+
out.push(`**${t.models}:** cards \`${d.models.cards}\`, curator \`${d.models.analyze}\`, check \`${d.models.verify}\``, "");
|
|
212
|
+
|
|
213
|
+
const groups: Array<[string, string]> = [
|
|
214
|
+
["added", t.added],
|
|
215
|
+
["updated", t.updated],
|
|
216
|
+
["moved", t.moved],
|
|
217
|
+
["superseded", t.superseded],
|
|
218
|
+
["deprecated", t.deprecated],
|
|
219
|
+
["removed", t.removed],
|
|
220
|
+
["skill", t.skill],
|
|
221
|
+
["confirmed", t.confirmed],
|
|
222
|
+
["merged", t.merged],
|
|
223
|
+
];
|
|
224
|
+
if (!d.changes.length && !d.agents.length) out.push(`_${t.none}_`, "");
|
|
225
|
+
for (const [kind, heading] of groups) {
|
|
226
|
+
const list = d.changes.filter((c) => c.kind === kind);
|
|
227
|
+
if (!list.length) continue;
|
|
228
|
+
out.push(`## ${heading}`, "");
|
|
229
|
+
for (const c of list) {
|
|
230
|
+
const id = c.newId ? `\`${c.noteId}\` → \`${c.newId}\`` : c.noteId ? `\`${c.noteId}\`` : "";
|
|
231
|
+
out.push(`- **${c.title}** ${id} — _${scopeLabel(c.scope, d)}_${c.source === "reviewer" ? ` · ${t.reviewer}` : ""}`);
|
|
232
|
+
if (c.rationale) out.push(` - ${t.why}: ${c.rationale}`);
|
|
233
|
+
if (c.evidence.length) out.push(` - ${t.evidence}: ${c.evidence.map((e) => evidenceLine(e, d, t)).join("; ")}`);
|
|
234
|
+
if (c.verdict)
|
|
235
|
+
out.push(` - ${t.check}: **${c.verdict.verdict}**${c.verdict.corrected ? ` (${t.corrected})` : ""} — ${c.verdict.reason}`);
|
|
236
|
+
if (c.proposedTitle && c.proposedTitle !== c.title) out.push(` - ${t.proposed}: «${c.proposedTitle}»`);
|
|
237
|
+
if (c.note) out.push(` - ${c.note}`);
|
|
238
|
+
}
|
|
239
|
+
out.push("");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (d.agents.length) {
|
|
243
|
+
out.push(`## ${t.agents}`, "");
|
|
244
|
+
for (const a of d.agents) {
|
|
245
|
+
out.push(`- **${a.project}** \`${a.rel}\`: ${a.url ?? `⚠️ ${a.error ?? ""}`}`);
|
|
246
|
+
for (const r of a.rationale) out.push(` - ${r}`);
|
|
247
|
+
if (!a.url) out.push("", a.patch, "");
|
|
248
|
+
}
|
|
249
|
+
out.push("");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (d.rejected.length) {
|
|
253
|
+
out.push(`<details><summary><b>${t.rejected}: ${d.rejected.length}</b></summary>`, "");
|
|
254
|
+
for (const r of d.rejected) out.push(`- \`${r.op}\` ${r.title} — ${r.reason}`);
|
|
255
|
+
out.push("", "</details>", "");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (d.unhandled.length) {
|
|
259
|
+
out.push(`## ${t.unhandled}`, "");
|
|
260
|
+
for (const u of d.unhandled) out.push(`- ${t.comment} \`${u.comment}\`: ${u.reason}`);
|
|
261
|
+
out.push("");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (d.indexes.length) {
|
|
265
|
+
out.push(`## ${t.indexes}`, "");
|
|
266
|
+
for (const i of d.indexes) {
|
|
267
|
+
const over = i.dropped.length ? ` · ⚠️ ${t.over}: ${i.dropped.length}` : "";
|
|
268
|
+
out.push(`- _${scopeLabel(i.scope, d)}_: ${i.tokens}/${i.budget} ${t.tokens}, ${i.included.length} ${t.lines}${over}`);
|
|
269
|
+
}
|
|
270
|
+
out.push("");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (d.sessions.length) {
|
|
274
|
+
out.push(`<details><summary><b>${t.processed}: ${d.sessions.length}</b></summary>`, "");
|
|
275
|
+
out.push("| date | project | session | outcome | learnings |", "|---|---|---|---|---|");
|
|
276
|
+
for (const s of d.sessions)
|
|
277
|
+
out.push(`| ${s.date} | ${s.project} | ${s.title.replace(/\|/g, "\\|")} \`${s.sessionId.slice(0, 8)}\` | ${s.outcome} | ${s.learnings} |`);
|
|
278
|
+
out.push("", "</details>", "");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (d.problems.length) {
|
|
282
|
+
out.push("<details><summary>diagnostics</summary>", "", ...d.problems.map((p) => `- ${p}`), "", "</details>", "");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
out.push(t.howTo, "");
|
|
286
|
+
return out.join("\n");
|
|
287
|
+
}
|
package/runner.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry point of the headless dream process. Not listed in `pi.extensions`:
|
|
3
|
+
* the scheduler starts
|
|
4
|
+
*
|
|
5
|
+
* pi -p --no-session --no-extensions -e <provider ext> -e runner.ts "/pi-dream-run --run-id …"
|
|
6
|
+
*
|
|
7
|
+
* so the dream runs with pi's model registry and credentials but without the
|
|
8
|
+
* user's other extensions (sounds, voice, status bars) and without writing a
|
|
9
|
+
* session file of its own.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { parseRunArgs, runDream } from "./pipeline.ts";
|
|
14
|
+
|
|
15
|
+
export default function piDreamRunner(pi: ExtensionAPI) {
|
|
16
|
+
pi.registerCommand("pi-dream-run", {
|
|
17
|
+
description: "pi-dream: run a dream in this process (used by the background runner)",
|
|
18
|
+
handler: async (args, ctx) => {
|
|
19
|
+
await runDream(ctx, parseRunArgs(args ?? ""));
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|