portable-agent-layer 0.70.0 → 0.72.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/README.md +5 -1
- package/assets/schema/pal-settings.schema.json +4 -0
- package/assets/skills/onboarding/SKILL.md +109 -0
- package/assets/skills/projects/SKILL.md +11 -2
- package/assets/templates/pal-settings.json +1 -0
- package/package.json +5 -1
- package/src/cli/index.ts +39 -12
- package/src/cli/migrate.ts +1 -1
- package/src/cli/personal-context.ts +67 -0
- package/src/cli/server.ts +13 -7
- package/src/cli/setup-identity.ts +13 -1
- package/src/cli/skill.ts +1 -1
- package/src/hooks/CompactRecover.ts +28 -86
- package/src/hooks/LedgerUnapplied.ts +3 -28
- package/src/hooks/LoadContext.ts +33 -60
- package/src/hooks/SecurityValidator.ts +16 -109
- package/src/hooks/handlers/agenda.ts +223 -0
- package/src/hooks/handlers/failure-principle.ts +19 -44
- package/src/hooks/handlers/inject-retrieval.ts +6 -2
- package/src/hooks/handlers/session-intelligence.ts +13 -70
- package/src/hooks/lib/agenda-store.ts +41 -0
- package/src/hooks/lib/capture-store.ts +103 -0
- package/src/hooks/lib/compact-recall.ts +89 -0
- package/src/hooks/lib/failure-principle.ts +98 -0
- package/src/hooks/lib/ledger-hook.ts +35 -0
- package/src/hooks/lib/ledger.ts +48 -1
- package/src/hooks/lib/paths.ts +0 -1
- package/src/hooks/lib/projects.ts +16 -1
- package/src/hooks/lib/security-gate.ts +159 -0
- package/src/hooks/lib/serves.ts +60 -0
- package/src/hooks/lib/session-context.ts +74 -0
- package/src/hooks/lib/stop.ts +14 -0
- package/src/hooks/lib/telos-goals.ts +144 -0
- package/src/hooks/lib/telos-topics.ts +68 -0
- package/src/hooks/lib/token-usage.ts +3 -1
- package/src/hooks/lib/wall-clock.ts +58 -0
- package/src/tools/agent/algorithm-reflect.ts +28 -97
- package/src/tools/agent/analyze.ts +19 -120
- package/src/tools/agent/handoff-note.ts +40 -70
- package/src/tools/agent/project.ts +47 -136
- package/src/tools/agent/relationship-note.ts +27 -46
- package/src/tools/agent/synthesize.ts +1 -1
- package/src/tools/agent/thread.ts +43 -123
- package/src/tools/control-room/data.ts +332 -0
- package/src/tools/control-room/matrix.ts +182 -0
- package/src/tools/control-room/server.ts +150 -0
- package/src/tools/control-room/ui/agenda.tsx +43 -0
- package/src/tools/control-room/ui/agents.tsx +67 -0
- package/src/tools/control-room/ui/app.css +857 -0
- package/src/tools/control-room/ui/app.tsx +74 -0
- package/src/tools/control-room/ui/board.tsx +82 -0
- package/src/tools/control-room/ui/format.ts +31 -0
- package/src/tools/control-room/ui/handoffs.tsx +37 -0
- package/src/tools/control-room/ui/index.html +19 -0
- package/src/tools/control-room/ui/ledger.tsx +137 -0
- package/src/tools/control-room/ui/matrix.tsx +117 -0
- package/src/tools/control-room/ui/panel.tsx +60 -0
- package/src/tools/control-room/ui/signal.tsx +161 -0
- package/src/tools/ledger/view.ts +3 -0
- package/src/tools/lib/algorithm-reflect.ts +84 -0
- package/src/tools/lib/analyze-report.ts +120 -0
- package/src/tools/lib/handoff-note.ts +88 -0
- package/src/tools/lib/note-flags.ts +59 -0
- package/src/tools/lib/project-isc.ts +151 -0
- package/src/tools/lib/relationship-reflect.ts +402 -0
- package/src/tools/lib/self-model.ts +499 -0
- package/src/tools/lib/session-usage.ts +216 -0
- package/src/tools/lib/skill-doctor.ts +457 -0
- package/src/tools/lib/thread.ts +119 -0
- package/src/tools/lib/token-report.ts +173 -0
- package/src/tools/lib/transcript-usage.ts +42 -0
- package/src/tools/lib/usage-buckets.ts +329 -0
- package/src/tools/relationship-reflect.ts +48 -412
- package/src/tools/self-model.ts +76 -558
- package/src/tools/session-summary.ts +8 -215
- package/src/tools/skill-doctor.ts +9 -444
- package/src/tools/token-cost.ts +18 -428
- package/assets/templates/ledger-page.html +0 -213
- package/src/cli/setup-telos.ts +0 -52
- package/src/hooks/lib/setup.ts +0 -60
- package/src/tools/ledger/server.ts +0 -111
|
@@ -13,371 +13,49 @@
|
|
|
13
13
|
* bun run tool:reflect -- --dry-run # Preview without writing
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { existsSync, mkdirSync,
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { resolve } from "node:path";
|
|
18
18
|
import { parseArgs } from "node:util";
|
|
19
|
-
import {
|
|
20
|
-
addEvidence,
|
|
21
|
-
createOpinion,
|
|
22
|
-
findSimilarOpinion,
|
|
23
|
-
readOpinions,
|
|
24
|
-
saveOpinion,
|
|
25
|
-
setLastReflectDate,
|
|
26
|
-
} from "../hooks/lib/opinions";
|
|
19
|
+
import { readOpinions, saveOpinion, setLastReflectDate } from "../hooks/lib/opinions";
|
|
27
20
|
import { palHome } from "../hooks/lib/paths";
|
|
28
|
-
import { similarity } from "../hooks/lib/text-similarity";
|
|
29
21
|
import { emit } from "./lib/emit";
|
|
22
|
+
import {
|
|
23
|
+
consoleLines,
|
|
24
|
+
formatReport,
|
|
25
|
+
highConfidenceLines,
|
|
26
|
+
loadNotes,
|
|
27
|
+
loadRatings,
|
|
28
|
+
planPromotions,
|
|
29
|
+
reportPath,
|
|
30
|
+
} from "./lib/relationship-reflect";
|
|
31
|
+
|
|
32
|
+
const HELP = `
|
|
33
|
+
RelationshipReflect — Periodic reflection + opinion promotion
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
interface Rating {
|
|
34
|
-
ts: string;
|
|
35
|
-
rating: number;
|
|
36
|
-
context: string;
|
|
37
|
-
source: "explicit" | "implicit";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface ParsedNote {
|
|
41
|
-
type: "W" | "O" | "Session";
|
|
42
|
-
text: string;
|
|
43
|
-
confidence?: number;
|
|
44
|
-
date: string;
|
|
45
|
-
time: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface OpinionChange {
|
|
49
|
-
statement: string;
|
|
50
|
-
action: "created" | "strengthened";
|
|
51
|
-
oldConfidence?: number;
|
|
52
|
-
newConfidence: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ── Note Parsing ──
|
|
56
|
-
|
|
57
|
-
export function loadNotes(daysBack: number): ParsedNote[] {
|
|
58
|
-
const relationshipDir = resolve(palHome(), "memory", "relationship");
|
|
59
|
-
if (!existsSync(relationshipDir)) return [];
|
|
60
|
-
|
|
61
|
-
const cutoff = new Date();
|
|
62
|
-
cutoff.setDate(cutoff.getDate() - daysBack);
|
|
63
|
-
const notes: ParsedNote[] = [];
|
|
64
|
-
|
|
65
|
-
for (const monthDir of readdirSync(relationshipDir).sort().reverse()) {
|
|
66
|
-
if (!/^\d{4}-\d{2}$/.test(monthDir)) continue;
|
|
67
|
-
const monthPath = resolve(relationshipDir, monthDir);
|
|
68
|
-
|
|
69
|
-
let files: string[];
|
|
70
|
-
try {
|
|
71
|
-
files = readdirSync(monthPath)
|
|
72
|
-
.filter((f) => f.endsWith(".md"))
|
|
73
|
-
.sort()
|
|
74
|
-
.reverse();
|
|
75
|
-
} catch {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
for (const file of files) {
|
|
80
|
-
const dateStr = file.replace(".md", "");
|
|
81
|
-
if (new Date(dateStr) < cutoff) continue;
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const content = readFileSync(resolve(monthPath, file), "utf-8");
|
|
85
|
-
let currentTime = "";
|
|
86
|
-
|
|
87
|
-
for (const line of content.split("\n")) {
|
|
88
|
-
const timeMatch = new RegExp(/^## (\d{2}:\d{2})/).exec(line);
|
|
89
|
-
if (timeMatch) {
|
|
90
|
-
currentTime = timeMatch[1];
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// O(c=0.85): text or B(c=0.85): text
|
|
95
|
-
const obMatch = new RegExp(/^- ([OB])\(c=([\d.]+)\):\s*(.+)$/).exec(line);
|
|
96
|
-
if (obMatch) {
|
|
97
|
-
notes.push({
|
|
98
|
-
type: obMatch[1] as "O" | "Session",
|
|
99
|
-
confidence: Number.parseFloat(obMatch[2]),
|
|
100
|
-
text: obMatch[3],
|
|
101
|
-
date: dateStr,
|
|
102
|
-
time: currentTime,
|
|
103
|
-
});
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// W: text
|
|
108
|
-
const worldMatch = new RegExp(/^- W:\s*(.+)$/).exec(line);
|
|
109
|
-
if (worldMatch) {
|
|
110
|
-
notes.push({
|
|
111
|
-
type: "W",
|
|
112
|
-
text: worldMatch[1],
|
|
113
|
-
date: dateStr,
|
|
114
|
-
time: currentTime,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
} catch {
|
|
119
|
-
// skip
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return notes;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── Ratings ──
|
|
128
|
-
|
|
129
|
-
export function loadRatings(daysBack: number): Rating[] {
|
|
130
|
-
const ratingsFile = resolve(palHome(), "memory", "signals", "ratings.jsonl");
|
|
131
|
-
if (!existsSync(ratingsFile)) return [];
|
|
132
|
-
|
|
133
|
-
const cutoff = new Date();
|
|
134
|
-
cutoff.setDate(cutoff.getDate() - daysBack);
|
|
135
|
-
|
|
136
|
-
return readFileSync(ratingsFile, "utf-8")
|
|
137
|
-
.split("\n")
|
|
138
|
-
.filter((l) => l.trim())
|
|
139
|
-
.map((l) => {
|
|
140
|
-
try {
|
|
141
|
-
return JSON.parse(l) as Rating;
|
|
142
|
-
} catch {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
.filter(
|
|
147
|
-
(r): r is Rating => r !== null && new Date(r.ts).getTime() >= cutoff.getTime()
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ── Opinion Promotion ──
|
|
152
|
-
|
|
153
|
-
export function promoteToOpinions(notes: ParsedNote[], dryRun: boolean): OpinionChange[] {
|
|
154
|
-
const changes: OpinionChange[] = [];
|
|
155
|
-
const opinions = readOpinions();
|
|
156
|
-
|
|
157
|
-
const opinionNotes = notes.filter((n) => n.type === "O");
|
|
158
|
-
|
|
159
|
-
// Group similar notes together
|
|
160
|
-
const groups = new Map<string, ParsedNote[]>();
|
|
161
|
-
for (const note of opinionNotes) {
|
|
162
|
-
let matched = false;
|
|
163
|
-
for (const [key, group] of groups) {
|
|
164
|
-
if (similarity(note.text, key) >= 0.3) {
|
|
165
|
-
group.push(note);
|
|
166
|
-
matched = true;
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (!matched) {
|
|
171
|
-
groups.set(note.text, [note]);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
for (const [representative, group] of groups) {
|
|
176
|
-
const existing = findSimilarOpinion(representative, opinions);
|
|
177
|
-
|
|
178
|
-
if (existing) {
|
|
179
|
-
let updated = existing;
|
|
180
|
-
for (const note of group) {
|
|
181
|
-
updated = addEvidence(updated, "supporting", note.text.slice(0, 120));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (updated.confidence !== existing.confidence) {
|
|
185
|
-
changes.push({
|
|
186
|
-
statement: existing.statement,
|
|
187
|
-
action: "strengthened",
|
|
188
|
-
oldConfidence: existing.confidence,
|
|
189
|
-
newConfidence: updated.confidence,
|
|
190
|
-
});
|
|
191
|
-
if (!dryRun) saveOpinion(updated);
|
|
192
|
-
}
|
|
193
|
-
} else if (group.length >= 2) {
|
|
194
|
-
let opinion = createOpinion(representative, group[0].text.slice(0, 120));
|
|
195
|
-
for (const note of group.slice(1)) {
|
|
196
|
-
opinion = addEvidence(opinion, "supporting", note.text.slice(0, 120));
|
|
197
|
-
}
|
|
198
|
-
changes.push({
|
|
199
|
-
statement: representative,
|
|
200
|
-
action: "created",
|
|
201
|
-
newConfidence: opinion.confidence,
|
|
202
|
-
});
|
|
203
|
-
if (!dryRun) saveOpinion(opinion);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return changes;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── Analysis ──
|
|
211
|
-
|
|
212
|
-
interface OpinionSummary {
|
|
213
|
-
text: string;
|
|
214
|
-
occurrences: number;
|
|
215
|
-
avgConfidence: number;
|
|
216
|
-
dates: string[];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function groupNoteOccurrences(notes: ParsedNote[]): OpinionSummary[] {
|
|
220
|
-
const opNotes = notes.filter((n) => n.type === "O");
|
|
221
|
-
const groups = new Map<
|
|
222
|
-
string,
|
|
223
|
-
{ confidences: number[]; dates: string[]; text: string }
|
|
224
|
-
>();
|
|
225
|
-
|
|
226
|
-
for (const note of opNotes) {
|
|
227
|
-
let matched = false;
|
|
228
|
-
for (const [key, group] of groups) {
|
|
229
|
-
if (similarity(note.text, key) >= 0.3) {
|
|
230
|
-
if (note.confidence !== undefined) group.confidences.push(note.confidence);
|
|
231
|
-
group.dates.push(note.date);
|
|
232
|
-
matched = true;
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (!matched) {
|
|
237
|
-
groups.set(note.text, {
|
|
238
|
-
text: note.text,
|
|
239
|
-
confidences: note.confidence !== undefined ? [note.confidence] : [],
|
|
240
|
-
dates: [note.date],
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return [...groups.values()]
|
|
246
|
-
.map((g) => ({
|
|
247
|
-
text: g.text,
|
|
248
|
-
occurrences: g.dates.length,
|
|
249
|
-
avgConfidence:
|
|
250
|
-
g.confidences.length > 0
|
|
251
|
-
? g.confidences.reduce((a, b) => a + b, 0) / g.confidences.length
|
|
252
|
-
: 0,
|
|
253
|
-
dates: [...new Set(g.dates)],
|
|
254
|
-
}))
|
|
255
|
-
.sort((a, b) => b.occurrences - a.occurrences);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function correlateRatings(ratings: Rating[]): string[] {
|
|
259
|
-
const correlations: string[] = [];
|
|
260
|
-
|
|
261
|
-
const lowRatings = ratings.filter((r) => r.rating <= 4);
|
|
262
|
-
const highRatings = ratings.filter((r) => r.rating >= 7);
|
|
263
|
-
|
|
264
|
-
if (lowRatings.length > 0) {
|
|
265
|
-
const lowContexts = lowRatings
|
|
266
|
-
.slice(0, 3)
|
|
267
|
-
.map((r) => `"${r.context.slice(0, 60)}"`)
|
|
268
|
-
.join(", ");
|
|
269
|
-
correlations.push(
|
|
270
|
-
`${lowRatings.length} low ratings (<=4) — common contexts: ${lowContexts}`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
if (highRatings.length > 0) {
|
|
274
|
-
const highContexts = highRatings
|
|
275
|
-
.slice(0, 3)
|
|
276
|
-
.map((r) => `"${r.context.slice(0, 60)}"`)
|
|
277
|
-
.join(", ");
|
|
278
|
-
correlations.push(
|
|
279
|
-
`${highRatings.length} high ratings (>=7) — common contexts: ${highContexts}`
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (ratings.length > 0) {
|
|
284
|
-
const explicitCount = ratings.filter((r) => r.source === "explicit").length;
|
|
285
|
-
const implicitCount = ratings.filter((r) => r.source === "implicit").length;
|
|
286
|
-
correlations.push(`Source mix: ${explicitCount} explicit, ${implicitCount} implicit`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return correlations;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ── Report ──
|
|
293
|
-
|
|
294
|
-
export function formatReport(
|
|
295
|
-
period: string,
|
|
296
|
-
notes: ParsedNote[],
|
|
297
|
-
ratings: Rating[],
|
|
298
|
-
opinionChanges: OpinionChange[]
|
|
299
|
-
): string {
|
|
300
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
301
|
-
const avgRating =
|
|
302
|
-
ratings.length > 0 ? ratings.reduce((s, r) => s + r.rating, 0) / ratings.length : 0;
|
|
303
|
-
const summaries = groupNoteOccurrences(notes);
|
|
304
|
-
const worldFacts = notes.filter((n) => n.type === "W").map((n) => n.text);
|
|
305
|
-
const ratingInsights = correlateRatings(ratings);
|
|
306
|
-
|
|
307
|
-
const lines: string[] = [
|
|
308
|
-
"# Relationship Reflection",
|
|
309
|
-
"",
|
|
310
|
-
`**Period:** ${period}`,
|
|
311
|
-
`**Generated:** ${date}`,
|
|
312
|
-
`**Notes analyzed:** ${notes.length}`,
|
|
313
|
-
`**Ratings analyzed:** ${ratings.length}`,
|
|
314
|
-
`**Average Rating:** ${avgRating.toFixed(1)}/10`,
|
|
315
|
-
"",
|
|
316
|
-
"---",
|
|
317
|
-
"",
|
|
318
|
-
];
|
|
319
|
-
|
|
320
|
-
if (opinionChanges.length > 0) {
|
|
321
|
-
lines.push("## Opinion Changes", "");
|
|
322
|
-
for (const change of opinionChanges) {
|
|
323
|
-
if (change.action === "created") {
|
|
324
|
-
lines.push(
|
|
325
|
-
`- **NEW** (${Math.round(change.newConfidence * 100)}%): ${change.statement}`
|
|
326
|
-
);
|
|
327
|
-
} else {
|
|
328
|
-
lines.push(
|
|
329
|
-
`- **+** ${Math.round(change.oldConfidence ?? 0 * 100)}% → ${Math.round(change.newConfidence * 100)}%: ${change.statement}`
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
lines.push("");
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (summaries.length > 0) {
|
|
337
|
-
lines.push("## Recurring Opinions", "");
|
|
338
|
-
for (const op of summaries) {
|
|
339
|
-
lines.push(
|
|
340
|
-
`- **${op.text}**`,
|
|
341
|
-
` Seen ${op.occurrences}x | Avg confidence: ${op.avgConfidence.toFixed(2)} | Dates: ${op.dates.join(", ")}`,
|
|
342
|
-
""
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (worldFacts.length > 0) {
|
|
348
|
-
lines.push("## World Facts Observed", "");
|
|
349
|
-
for (const fact of worldFacts.slice(0, 10)) {
|
|
350
|
-
lines.push(`- ${fact}`);
|
|
351
|
-
}
|
|
352
|
-
lines.push("");
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (ratingInsights.length > 0) {
|
|
356
|
-
lines.push("## Rating Insights", "");
|
|
357
|
-
for (const insight of ratingInsights) {
|
|
358
|
-
lines.push(`- ${insight}`);
|
|
359
|
-
}
|
|
360
|
-
lines.push("");
|
|
361
|
-
}
|
|
35
|
+
Reads recent relationship notes and ratings. Promotes recurring
|
|
36
|
+
observations (O type) into tracked opinions with confidence scoring.
|
|
362
37
|
|
|
363
|
-
|
|
364
|
-
|
|
38
|
+
Usage:
|
|
39
|
+
bun run tool:reflect Reflect on last 7 days (default)
|
|
40
|
+
bun run tool:reflect -- --month Reflect on last 30 days
|
|
41
|
+
bun run tool:reflect -- --dry-run Preview without writing
|
|
365
42
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
43
|
+
Output:
|
|
44
|
+
- Updates memory/relationship/opinions.json (confidence tracking)
|
|
45
|
+
- Creates reflection report in memory/relationship/reflections/
|
|
46
|
+
`;
|
|
369
47
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const filename = `${date}_${slug}-reflection.md`;
|
|
373
|
-
const filepath = resolve(reflectionDir, filename);
|
|
48
|
+
const relationshipDir = () => resolve(palHome(), "memory", "relationship");
|
|
49
|
+
const ratingsFile = () => resolve(palHome(), "memory", "signals", "ratings.jsonl");
|
|
374
50
|
|
|
51
|
+
function saveReport(report: string, period: string): string {
|
|
52
|
+
const dir = resolve(relationshipDir(), "reflections");
|
|
53
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
54
|
+
const filepath = reportPath(dir, period);
|
|
375
55
|
writeFileSync(filepath, report, "utf-8");
|
|
376
56
|
return filepath;
|
|
377
57
|
}
|
|
378
58
|
|
|
379
|
-
// ── CLI ──
|
|
380
|
-
|
|
381
59
|
function run() {
|
|
382
60
|
const { values } = parseArgs({
|
|
383
61
|
args: Bun.argv.slice(2),
|
|
@@ -389,21 +67,7 @@ function run() {
|
|
|
389
67
|
});
|
|
390
68
|
|
|
391
69
|
if (values.help) {
|
|
392
|
-
console.log(
|
|
393
|
-
RelationshipReflect — Periodic reflection + opinion promotion
|
|
394
|
-
|
|
395
|
-
Reads recent relationship notes and ratings. Promotes recurring
|
|
396
|
-
observations (O type) into tracked opinions with confidence scoring.
|
|
397
|
-
|
|
398
|
-
Usage:
|
|
399
|
-
bun run tool:reflect Reflect on last 7 days (default)
|
|
400
|
-
bun run tool:reflect -- --month Reflect on last 30 days
|
|
401
|
-
bun run tool:reflect -- --dry-run Preview without writing
|
|
402
|
-
|
|
403
|
-
Output:
|
|
404
|
-
- Updates memory/relationship/opinions.json (confidence tracking)
|
|
405
|
-
- Creates reflection report in memory/relationship/reflections/
|
|
406
|
-
`);
|
|
70
|
+
console.log(HELP);
|
|
407
71
|
process.exit(0);
|
|
408
72
|
}
|
|
409
73
|
|
|
@@ -411,8 +75,8 @@ Output:
|
|
|
411
75
|
const period = values.month ? "Monthly" : "Weekly";
|
|
412
76
|
const dryRun = values["dry-run"] ?? false;
|
|
413
77
|
|
|
414
|
-
const notes = loadNotes(daysBack);
|
|
415
|
-
const ratings = loadRatings(daysBack);
|
|
78
|
+
const notes = loadNotes(relationshipDir(), daysBack);
|
|
79
|
+
const ratings = loadRatings(ratingsFile(), daysBack);
|
|
416
80
|
|
|
417
81
|
emit.ok(`Loaded ${notes.length} notes from last ${daysBack} days`);
|
|
418
82
|
emit.ok(`Loaded ${ratings.length} ratings`);
|
|
@@ -422,54 +86,26 @@ Output:
|
|
|
422
86
|
process.exit(0);
|
|
423
87
|
}
|
|
424
88
|
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
const avgRating =
|
|
428
|
-
ratings.length > 0 ? ratings.reduce((s, r) => s + r.rating, 0) / ratings.length : 0;
|
|
429
|
-
emit.ok(`\nAverage Rating: ${avgRating.toFixed(1)}/10`);
|
|
89
|
+
const plan = planPromotions(notes, readOpinions());
|
|
90
|
+
if (!dryRun) for (const opinion of plan.toSave) saveOpinion(opinion);
|
|
430
91
|
|
|
431
|
-
const
|
|
432
|
-
emit.ok(`Observations: ${summaries.length} unique`);
|
|
433
|
-
|
|
434
|
-
if (opinionChanges.length > 0) {
|
|
435
|
-
emit.ok("\nOpinion changes:");
|
|
436
|
-
for (const change of opinionChanges) {
|
|
437
|
-
if (change.action === "created") {
|
|
438
|
-
emit.ok(
|
|
439
|
-
` + NEW (${Math.round(change.newConfidence * 100)}%) ${change.statement.slice(0, 80)}`
|
|
440
|
-
);
|
|
441
|
-
} else {
|
|
442
|
-
emit.ok(
|
|
443
|
-
` ~ ${Math.round(change.oldConfidence ?? 0 * 100)}% → ${Math.round(change.newConfidence * 100)}% ${change.statement.slice(0, 80)}`
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
emit.ok("\nNo opinion changes");
|
|
449
|
-
}
|
|
92
|
+
for (const line of consoleLines(notes, ratings, plan.changes)) emit.ok(line);
|
|
450
93
|
|
|
451
94
|
if (dryRun) {
|
|
452
95
|
emit.data("[DRY RUN] Would write reflection report + update opinions");
|
|
453
|
-
|
|
454
|
-
const report = formatReport(period, notes, ratings, opinionChanges);
|
|
455
|
-
const filepath = writeReport(report, period);
|
|
456
|
-
setLastReflectDate(new Date().toISOString().slice(0, 10));
|
|
457
|
-
emit.receipt(filepath, {
|
|
458
|
-
period,
|
|
459
|
-
notes: notes.length,
|
|
460
|
-
ratings: ratings.length,
|
|
461
|
-
opinionChanges: opinionChanges.length,
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
const opinions = readOpinions();
|
|
465
|
-
const high = opinions.filter((o) => o.confidence >= 0.85);
|
|
466
|
-
if (high.length > 0) {
|
|
467
|
-
emit.ok("\nHigh-confidence opinions (injected into context):");
|
|
468
|
-
for (const o of high) {
|
|
469
|
-
emit.ok(` [${Math.round(o.confidence * 100)}%] ${o.statement.slice(0, 80)}`);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
96
|
+
return;
|
|
472
97
|
}
|
|
98
|
+
|
|
99
|
+
const filepath = saveReport(formatReport(period, notes, ratings, plan.changes), period);
|
|
100
|
+
setLastReflectDate(new Date().toISOString().slice(0, 10));
|
|
101
|
+
emit.receipt(filepath, {
|
|
102
|
+
period,
|
|
103
|
+
notes: notes.length,
|
|
104
|
+
ratings: ratings.length,
|
|
105
|
+
opinionChanges: plan.changes.length,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
for (const line of highConfidenceLines(readOpinions())) emit.ok(line);
|
|
473
109
|
}
|
|
474
110
|
|
|
475
111
|
if (import.meta.main) run();
|