portable-agent-layer 0.9.0 → 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 -1
- package/src/hooks/handlers/rating.ts +4 -47
- 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/opinions.ts +191 -0
- package/src/hooks/lib/relationship.ts +5 -4
- package/src/hooks/lib/security.ts +1 -0
- package/src/hooks/lib/stop.ts +3 -0
- package/src/hooks/lib/text-similarity.ts +125 -0
- package/src/tools/analyze.ts +49 -15
- package/src/tools/opinion.ts +250 -0
- package/src/tools/relationship-reflect.ts +215 -105
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* OpinionTracker — manage confidence-tracked opinions about the user.
|
|
4
|
+
*
|
|
5
|
+
* Called by the AI during conversation when it detects confirmations,
|
|
6
|
+
* contradictions, or new behavioral patterns.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun run tool:opinion -- list List all opinions
|
|
10
|
+
* bun run tool:opinion -- show "statement" Show opinion details
|
|
11
|
+
* bun run tool:opinion -- add "statement" [--category workflow] Add new opinion
|
|
12
|
+
* bun run tool:opinion -- evidence "statement" --supporting "why" Add supporting evidence
|
|
13
|
+
* bun run tool:opinion -- evidence "statement" --counter "why" Add counter evidence
|
|
14
|
+
* bun run tool:opinion -- evidence "statement" --confirmation "why" Explicit user confirmation
|
|
15
|
+
* bun run tool:opinion -- evidence "statement" --contradiction "why" Explicit user contradiction
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
addEvidence,
|
|
20
|
+
createOpinion,
|
|
21
|
+
type EvidenceType,
|
|
22
|
+
findSimilarOpinion,
|
|
23
|
+
type OpinionCategory,
|
|
24
|
+
readOpinions,
|
|
25
|
+
saveOpinion,
|
|
26
|
+
} from "../hooks/lib/opinions";
|
|
27
|
+
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const command = args[0];
|
|
30
|
+
|
|
31
|
+
const NOTIFICATION_THRESHOLD = 0.15;
|
|
32
|
+
|
|
33
|
+
function flag(name: string): string | undefined {
|
|
34
|
+
const idx = args.indexOf(`--${name}`);
|
|
35
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const c = {
|
|
39
|
+
bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
|
|
40
|
+
dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
|
|
41
|
+
cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
|
|
42
|
+
yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
|
|
43
|
+
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
44
|
+
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function bar(confidence: number): string {
|
|
48
|
+
const filled = Math.round(confidence * 10);
|
|
49
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
switch (command) {
|
|
53
|
+
case "list": {
|
|
54
|
+
const opinions = readOpinions();
|
|
55
|
+
if (opinions.length === 0) {
|
|
56
|
+
console.log("\n No opinions tracked yet.\n");
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const categories = new Map<string, typeof opinions>();
|
|
61
|
+
for (const op of opinions) {
|
|
62
|
+
const list = categories.get(op.category) ?? [];
|
|
63
|
+
list.push(op);
|
|
64
|
+
categories.set(op.category, list);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\n ${c.bold("Tracked Opinions")} (${opinions.length} total)\n`);
|
|
68
|
+
|
|
69
|
+
for (const [category, ops] of categories) {
|
|
70
|
+
console.log(` ${c.cyan(category)}`);
|
|
71
|
+
for (const op of ops.sort((a, b) => b.confidence - a.confidence)) {
|
|
72
|
+
const pct = `${Math.round(op.confidence * 100)}%`;
|
|
73
|
+
const color =
|
|
74
|
+
op.confidence >= 0.85 ? c.green : op.confidence <= 0.3 ? c.red : c.yellow;
|
|
75
|
+
console.log(` [${bar(op.confidence)}] ${color(pct)} ${op.statement}`);
|
|
76
|
+
}
|
|
77
|
+
console.log("");
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case "show": {
|
|
83
|
+
const statement = args[1];
|
|
84
|
+
if (!statement) {
|
|
85
|
+
console.error('Usage: bun run tool:opinion -- show "statement"');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const opinions = readOpinions();
|
|
90
|
+
const match = findSimilarOpinion(statement, opinions);
|
|
91
|
+
if (!match) {
|
|
92
|
+
console.error(` No matching opinion found for: "${statement}"`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pct = Math.round(match.confidence * 100);
|
|
97
|
+
console.log(`\n ${c.bold("Opinion Details")}\n`);
|
|
98
|
+
console.log(` ${c.bold("Statement:")} ${match.statement}`);
|
|
99
|
+
console.log(` ${c.bold("Confidence:")} [${bar(match.confidence)}] ${pct}%`);
|
|
100
|
+
console.log(` ${c.bold("Category:")} ${match.category}`);
|
|
101
|
+
console.log(` ${c.bold("Created:")} ${match.created}`);
|
|
102
|
+
console.log(` ${c.bold("Updated:")} ${match.updated}`);
|
|
103
|
+
console.log(`\n ${c.bold("Evidence")} (${match.evidence.length} items)\n`);
|
|
104
|
+
|
|
105
|
+
const supporting = match.evidence.filter(
|
|
106
|
+
(e) => e.type === "supporting" || e.type === "confirmation"
|
|
107
|
+
);
|
|
108
|
+
const counter = match.evidence.filter(
|
|
109
|
+
(e) => e.type === "counter" || e.type === "contradiction"
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (supporting.length > 0) {
|
|
113
|
+
console.log(` ${c.green("Supporting:")}`);
|
|
114
|
+
for (const e of supporting) {
|
|
115
|
+
console.log(` ${c.dim(e.date)} [${e.type}] ${e.source}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (counter.length > 0) {
|
|
119
|
+
console.log(` ${c.red("Counter:")}`);
|
|
120
|
+
for (const e of counter) {
|
|
121
|
+
console.log(` ${c.dim(e.date)} [${e.type}] ${e.source}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log("");
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "add": {
|
|
129
|
+
const statement = args[1];
|
|
130
|
+
if (!statement) {
|
|
131
|
+
console.error(
|
|
132
|
+
'Usage: bun run tool:opinion -- add "statement" [--category workflow]'
|
|
133
|
+
);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const category = (flag("category") || "general") as OpinionCategory;
|
|
138
|
+
const opinions = readOpinions();
|
|
139
|
+
const existing = findSimilarOpinion(statement, opinions);
|
|
140
|
+
|
|
141
|
+
if (existing) {
|
|
142
|
+
console.log(
|
|
143
|
+
` Similar opinion already exists: "${existing.statement}" (${Math.round(existing.confidence * 100)}%)`
|
|
144
|
+
);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const opinion = createOpinion(statement, "manual add");
|
|
149
|
+
opinion.category = category;
|
|
150
|
+
saveOpinion(opinion);
|
|
151
|
+
console.log(` Added: "${statement}" [${category}] at 50%`);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "evidence": {
|
|
156
|
+
const statement = args[1];
|
|
157
|
+
if (!statement) {
|
|
158
|
+
console.error(
|
|
159
|
+
'Usage: bun run tool:opinion -- evidence "statement" --supporting "description"'
|
|
160
|
+
);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const opinions = readOpinions();
|
|
165
|
+
const match = findSimilarOpinion(statement, opinions);
|
|
166
|
+
if (!match) {
|
|
167
|
+
console.error(` No matching opinion found for: "${statement}"`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let evidenceType: EvidenceType | undefined;
|
|
172
|
+
let description: string | undefined;
|
|
173
|
+
|
|
174
|
+
for (const t of ["supporting", "counter", "confirmation", "contradiction"] as const) {
|
|
175
|
+
const val = flag(t);
|
|
176
|
+
if (val) {
|
|
177
|
+
evidenceType = t;
|
|
178
|
+
description = val;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!evidenceType || !description) {
|
|
184
|
+
console.error(
|
|
185
|
+
" Provide one of: --supporting, --counter, --confirmation, --contradiction"
|
|
186
|
+
);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const oldConfidence = match.confidence;
|
|
191
|
+
const updated = addEvidence(match, evidenceType, description);
|
|
192
|
+
saveOpinion(updated);
|
|
193
|
+
|
|
194
|
+
const shift = updated.confidence - oldConfidence;
|
|
195
|
+
const arrow = shift > 0 ? c.green("\u2191") : c.red("\u2193");
|
|
196
|
+
console.log(
|
|
197
|
+
` ${arrow} ${Math.round(oldConfidence * 100)}% \u2192 ${Math.round(updated.confidence * 100)}% "${match.statement}"`
|
|
198
|
+
);
|
|
199
|
+
console.log(` [${evidenceType}] ${description}`);
|
|
200
|
+
|
|
201
|
+
if (Math.abs(shift) >= NOTIFICATION_THRESHOLD) {
|
|
202
|
+
console.log(
|
|
203
|
+
`\n ${c.bold(c.yellow("Major shift detected!"))} (${shift > 0 ? "+" : ""}${Math.round(shift * 100)}%)`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case "--help":
|
|
210
|
+
case "-h":
|
|
211
|
+
case "help":
|
|
212
|
+
case undefined: {
|
|
213
|
+
console.log(`
|
|
214
|
+
OpinionTracker — manage confidence-tracked opinions about the user
|
|
215
|
+
|
|
216
|
+
The "statement" argument is fuzzy-matched (Dice similarity) against all
|
|
217
|
+
stored opinions. Use a few keywords from the opinion, not the exact text.
|
|
218
|
+
|
|
219
|
+
Commands:
|
|
220
|
+
list List all opinions with confidence bars
|
|
221
|
+
show "keywords" Show opinion details + full evidence history
|
|
222
|
+
add "statement" [--category X] Create new opinion (starts at 50%)
|
|
223
|
+
evidence "keywords" --supporting "why" Supporting evidence (+2%)
|
|
224
|
+
evidence "keywords" --counter "why" Counter evidence (-5%)
|
|
225
|
+
evidence "keywords" --confirmation "why" User explicitly confirmed (+10%)
|
|
226
|
+
evidence "keywords" --contradiction "why" User explicitly contradicted (-20%)
|
|
227
|
+
|
|
228
|
+
Categories: communication, technical, workflow, general
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
bun run tool:opinion -- list
|
|
232
|
+
bun run tool:opinion -- evidence "concise direct responses" --confirmation "User said: keep it short"
|
|
233
|
+
bun run tool:opinion -- evidence "concise direct responses" --contradiction "User asked for detailed explanation"
|
|
234
|
+
bun run tool:opinion -- add "User prefers iterative development" --category workflow
|
|
235
|
+
bun run tool:opinion -- show "iterative development"
|
|
236
|
+
|
|
237
|
+
Confidence lifecycle:
|
|
238
|
+
New opinions start at 50%. Supporting notes from reflect add +2% each.
|
|
239
|
+
Explicit confirmations jump +10%, contradictions drop -20%.
|
|
240
|
+
At >=85%, opinions are auto-injected into every session context.
|
|
241
|
+
|
|
242
|
+
Usage: bun run tool:opinion -- <command> [args]
|
|
243
|
+
`);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
default:
|
|
248
|
+
console.error(` Unknown command: ${command}. Run with --help for usage.`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|