portable-agent-layer 0.8.1 → 0.10.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/assets/templates/AGENTS.md.template +6 -0
- package/assets/templates/STEERING-RULES.md +23 -0
- package/package.json +2 -2
- package/src/hooks/LoadContext.ts +1 -4
- package/src/hooks/handlers/rating.ts +9 -49
- package/src/hooks/handlers/reflect-trigger.ts +83 -0
- package/src/hooks/handlers/relationship.ts +8 -5
- package/src/hooks/handlers/session-name.ts +8 -6
- package/src/hooks/handlers/work-learning.ts +1 -0
- package/src/hooks/handlers/work-session.ts +16 -3
- package/src/hooks/lib/claude-md.ts +12 -2
- package/src/hooks/lib/context.ts +31 -21
- package/src/hooks/lib/graduation.ts +6 -4
- package/src/hooks/lib/learning-store.ts +7 -117
- package/src/hooks/lib/log.ts +1 -3
- package/src/hooks/lib/opinions.ts +191 -0
- package/src/hooks/lib/relationship.ts +5 -4
- package/src/hooks/lib/security.ts +3 -0
- package/src/hooks/lib/stop.ts +3 -0
- package/src/hooks/lib/text-similarity.ts +125 -0
- package/src/hooks/lib/work-tracking.ts +1 -1
- package/src/targets/opencode/install.ts +6 -2
- package/src/targets/opencode/plugin.ts +20 -171
- package/src/tools/analyze.ts +49 -15
- package/src/tools/opinion.ts +250 -0
- package/src/tools/relationship-reflect.ts +215 -105
- package/src/hooks/lib/prompts.ts +0 -11
- package/src/tools/eval-principles.ts +0 -234
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Principle Evaluation — generate, regenerate, or compare candidate principles.
|
|
4
|
-
*
|
|
5
|
-
* Reads failures (capture.md) and learnings (frontmatter .md) and uses Haiku
|
|
6
|
-
* to generate candidate principles. Useful for tuning prompt quality.
|
|
7
|
-
*
|
|
8
|
-
* Modes:
|
|
9
|
-
* --dry-run Preview which files would be updated
|
|
10
|
-
* --evaluate Show current vs new principle for comparison (does not write)
|
|
11
|
-
* --force Regenerate principles even if one already exists
|
|
12
|
-
* (default) Generate missing principles only
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* bun run tool:eval # generate missing
|
|
16
|
-
* bun run tool:eval -- --dry-run # preview
|
|
17
|
-
* bun run tool:eval -- --evaluate # compare current vs new
|
|
18
|
-
* bun run tool:eval -- --force # regenerate all
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
-
import { resolve } from "node:path";
|
|
23
|
-
import { hasFrontmatter, parse, stringify } from "../hooks/lib/frontmatter";
|
|
24
|
-
import { inference } from "../hooks/lib/inference";
|
|
25
|
-
import { palHome } from "../hooks/lib/paths";
|
|
26
|
-
import {
|
|
27
|
-
FAILURE_PRINCIPLE_PROMPT,
|
|
28
|
-
LEARNING_PRINCIPLE_PROMPT,
|
|
29
|
-
} from "../hooks/lib/prompts";
|
|
30
|
-
|
|
31
|
-
const args = process.argv.slice(2);
|
|
32
|
-
const dryRun = args.includes("--dry-run");
|
|
33
|
-
const evaluate = args.includes("--evaluate");
|
|
34
|
-
const force = args.includes("--force");
|
|
35
|
-
|
|
36
|
-
const home = palHome();
|
|
37
|
-
let processed = 0;
|
|
38
|
-
let skipped = 0;
|
|
39
|
-
let failed = 0;
|
|
40
|
-
|
|
41
|
-
async function generatePrinciple(systemPrompt: string, context: string): Promise<string> {
|
|
42
|
-
const result = await inference({
|
|
43
|
-
system: systemPrompt,
|
|
44
|
-
user: context,
|
|
45
|
-
maxTokens: 100,
|
|
46
|
-
timeout: 10000,
|
|
47
|
-
jsonSchema: {
|
|
48
|
-
type: "object" as const,
|
|
49
|
-
additionalProperties: false,
|
|
50
|
-
properties: {
|
|
51
|
-
principle: { type: "string" as const },
|
|
52
|
-
},
|
|
53
|
-
required: ["principle"],
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
if (result.success && result.output) {
|
|
58
|
-
const parsed = JSON.parse(result.output) as { principle?: string };
|
|
59
|
-
const principle = parsed.principle?.trim() || "";
|
|
60
|
-
if (principle.length > 10) return principle;
|
|
61
|
-
}
|
|
62
|
-
return "";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ── Failures ──
|
|
66
|
-
|
|
67
|
-
async function processFailures() {
|
|
68
|
-
const failuresDir = resolve(home, "memory", "learning", "failures");
|
|
69
|
-
if (!existsSync(failuresDir)) return;
|
|
70
|
-
|
|
71
|
-
for (const year of readdirSync(failuresDir)) {
|
|
72
|
-
const yearDir = resolve(failuresDir, year);
|
|
73
|
-
for (const month of readdirSync(yearDir)) {
|
|
74
|
-
const monthDir = resolve(yearDir, month);
|
|
75
|
-
for (const slug of readdirSync(monthDir)) {
|
|
76
|
-
const capturePath = resolve(monthDir, slug, "capture.md");
|
|
77
|
-
if (!existsSync(capturePath)) continue;
|
|
78
|
-
|
|
79
|
-
const content = readFileSync(capturePath, "utf-8");
|
|
80
|
-
if (!hasFrontmatter(content)) continue;
|
|
81
|
-
|
|
82
|
-
const { meta, body } = parse<{
|
|
83
|
-
principle?: string;
|
|
84
|
-
context?: string;
|
|
85
|
-
rating?: number;
|
|
86
|
-
}>(content);
|
|
87
|
-
|
|
88
|
-
const hasPrinciple = !!meta.principle;
|
|
89
|
-
if (hasPrinciple && !force && !evaluate) {
|
|
90
|
-
skipped++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const context = meta.context || "";
|
|
95
|
-
if (!context) {
|
|
96
|
-
skipped++;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const inputContext = `Rating: ${meta.rating}/10\nContext: ${context}\n\n${body.slice(0, 400)}`;
|
|
101
|
-
|
|
102
|
-
if (dryRun) {
|
|
103
|
-
console.log(` [failure] ${slug.slice(0, 60)}`);
|
|
104
|
-
processed++;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const newPrinciple = await generatePrinciple(
|
|
110
|
-
FAILURE_PRINCIPLE_PROMPT,
|
|
111
|
-
inputContext
|
|
112
|
-
);
|
|
113
|
-
if (!newPrinciple) {
|
|
114
|
-
skipped++;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (evaluate) {
|
|
119
|
-
console.log(` [failure] ${slug.slice(0, 50)}`);
|
|
120
|
-
if (hasPrinciple) {
|
|
121
|
-
console.log(` OLD: ${meta.principle}`);
|
|
122
|
-
}
|
|
123
|
-
console.log(` NEW: ${newPrinciple}`);
|
|
124
|
-
console.log("");
|
|
125
|
-
processed++;
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const newMeta = { ...meta, principle: newPrinciple } as Record<string, unknown>;
|
|
130
|
-
writeFileSync(capturePath, stringify(newMeta, body), "utf-8");
|
|
131
|
-
console.log(` [failure] ${slug.slice(0, 60)}`);
|
|
132
|
-
processed++;
|
|
133
|
-
} catch {
|
|
134
|
-
failed++;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ── Learnings ──
|
|
142
|
-
|
|
143
|
-
async function processLearnings() {
|
|
144
|
-
const learningDir = resolve(home, "memory", "learning", "session");
|
|
145
|
-
if (!existsSync(learningDir)) return;
|
|
146
|
-
|
|
147
|
-
for (const year of readdirSync(learningDir)) {
|
|
148
|
-
const yearDir = resolve(learningDir, year);
|
|
149
|
-
for (const month of readdirSync(yearDir)) {
|
|
150
|
-
const monthDir = resolve(yearDir, month);
|
|
151
|
-
for (const file of readdirSync(monthDir).filter((f) => f.endsWith(".md"))) {
|
|
152
|
-
const filepath = resolve(monthDir, file);
|
|
153
|
-
const content = readFileSync(filepath, "utf-8");
|
|
154
|
-
|
|
155
|
-
if (!hasFrontmatter(content)) {
|
|
156
|
-
skipped++;
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const { meta, body } = parse<{
|
|
161
|
-
principle?: string;
|
|
162
|
-
title?: string;
|
|
163
|
-
}>(content);
|
|
164
|
-
|
|
165
|
-
const hasPrinciple = !!meta.principle;
|
|
166
|
-
if (hasPrinciple && !force && !evaluate) {
|
|
167
|
-
skipped++;
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const title = meta.title || "";
|
|
172
|
-
if (!title) {
|
|
173
|
-
skipped++;
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const inputContext = `Title: ${title}\n\n${body.slice(0, 400)}`;
|
|
178
|
-
|
|
179
|
-
if (dryRun) {
|
|
180
|
-
console.log(` [learning] ${file.slice(0, 60)}`);
|
|
181
|
-
processed++;
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const newPrinciple = await generatePrinciple(
|
|
187
|
-
LEARNING_PRINCIPLE_PROMPT,
|
|
188
|
-
inputContext
|
|
189
|
-
);
|
|
190
|
-
if (!newPrinciple) {
|
|
191
|
-
skipped++;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (evaluate) {
|
|
196
|
-
console.log(` [learning] ${file.slice(0, 50)}`);
|
|
197
|
-
if (hasPrinciple) {
|
|
198
|
-
console.log(` OLD: ${meta.principle}`);
|
|
199
|
-
}
|
|
200
|
-
console.log(` NEW: ${newPrinciple}`);
|
|
201
|
-
console.log("");
|
|
202
|
-
processed++;
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const newMeta = { ...meta, principle: newPrinciple } as Record<string, unknown>;
|
|
207
|
-
writeFileSync(filepath, stringify(newMeta, body), "utf-8");
|
|
208
|
-
console.log(` [learning] ${file.slice(0, 60)}`);
|
|
209
|
-
processed++;
|
|
210
|
-
} catch {
|
|
211
|
-
failed++;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── Main ──
|
|
219
|
-
|
|
220
|
-
const mode = evaluate
|
|
221
|
-
? "evaluate"
|
|
222
|
-
: force
|
|
223
|
-
? "force regenerate"
|
|
224
|
-
: dryRun
|
|
225
|
-
? "dry run"
|
|
226
|
-
: "backfill";
|
|
227
|
-
console.log(`\n Principle ${mode}...\n`);
|
|
228
|
-
|
|
229
|
-
await processFailures();
|
|
230
|
-
await processLearnings();
|
|
231
|
-
|
|
232
|
-
console.log(
|
|
233
|
-
`\n Done: ${processed} ${evaluate ? "compared" : "processed"}, ${skipped} skipped, ${failed} failed\n`
|
|
234
|
-
);
|