portable-agent-layer 0.52.0 → 0.53.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 +1 -0
- package/assets/skills/analyze-pdf/SKILL.md +12 -0
- package/assets/skills/analyze-pdf/tools/pdf-read.ts +35 -0
- package/assets/skills/pal-analyze/SKILL.md +54 -0
- package/assets/skills/pal-reflect/SKILL.md +52 -0
- package/assets/templates/PAL/ALGORITHM.md +35 -6
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +2 -3
- package/package.json +7 -4
- package/src/cli/index.ts +6 -0
- package/src/hooks/handlers/failure-principle.ts +1 -7
- package/src/hooks/handlers/rating.ts +13 -43
- package/src/hooks/handlers/reflect-trigger.ts +15 -0
- package/src/hooks/lib/analyze-nudge.ts +47 -0
- package/src/hooks/lib/context.ts +6 -0
- package/src/tools/agent/analyze.ts +6 -4
- package/src/tools/agent/relationship-note.ts +56 -12
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ pal cli status # check your setup
|
|
|
83
83
|
| `pal cli status` | Show current PAL configuration |
|
|
84
84
|
| `pal cli doctor` | Check prerequisites and system health |
|
|
85
85
|
| `pal cli migrate` | Run pending data migrations (non-destructive) |
|
|
86
|
+
| `pal cli analyze [--actionable]` | Learning analysis: rating trends, failure patterns, graduation candidates |
|
|
86
87
|
| `pal cli usage` | Summarize token usage and estimated cost |
|
|
87
88
|
| `pal cli knowledge` | Query & manage the knowledge store (search, graph, stats, hubs, find, show, add, ls, ingest) |
|
|
88
89
|
| `pal cli skill link <name>` | Link a personal `~/.pal/skills/<name>/` into every installed agent so it is discoverable |
|
|
@@ -22,6 +22,18 @@ Use your native PDF reading capability (e.g. a Read tool or equivalent). Most mo
|
|
|
22
22
|
|
|
23
23
|
Do NOT install PDF processing tools (poppler, pdftotext, etc.) unless the user explicitly asks. Native reading is sufficient.
|
|
24
24
|
|
|
25
|
+
### Fallback: when native Read fails
|
|
26
|
+
|
|
27
|
+
If the Read tool fails to open the PDF (e.g. error mentioning `pdftoppm`, missing renderer, or unsupported format), fall back to the text-extraction CLI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun ~/.pal/skills/analyze-pdf/tools/pdf-read.ts -- <path>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use the stdout output as the document content and proceed with the user's request as normal.
|
|
34
|
+
|
|
35
|
+
If `pdf-read.ts` also fails, stop completely — report the exact errors from both attempts and ask the user how to proceed. Never infer PDF content from filename, metadata, or assumptions.
|
|
36
|
+
|
|
25
37
|
## What to do with it
|
|
26
38
|
|
|
27
39
|
Follow the user's request. Common tasks:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PDF Read — Extracts text from a local PDF file, page by page.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* bun pdf-read.ts -- <path/to/file.pdf>
|
|
8
|
+
*
|
|
9
|
+
* Prints each page's text to stdout separated by a page marker.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import { parseArgs } from "node:util";
|
|
14
|
+
import { extractText, getDocumentProxy } from "unpdf";
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const { positionals } = parseArgs({ allowPositionals: true, options: {} });
|
|
18
|
+
|
|
19
|
+
const filePath = positionals[0];
|
|
20
|
+
if (!filePath) {
|
|
21
|
+
console.error("Usage: bun pdf-read.ts -- <path/to/file.pdf>");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const buffer = await readFile(filePath);
|
|
26
|
+
const pdf = await getDocumentProxy(new Uint8Array(buffer));
|
|
27
|
+
const { totalPages, text } = await extractText(pdf, { mergePages: false });
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < totalPages; i++) {
|
|
30
|
+
console.log(`--- Page ${i + 1} ---`);
|
|
31
|
+
console.log(text[i]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void main();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pal-analyze
|
|
3
|
+
description: Run learning analysis — surface rating trends, recurring failure patterns, and graduation candidates. Use when learning analysis is due, or when the user asks about performance patterns, low ratings, or what to improve.
|
|
4
|
+
argument-hint: [optional: --actionable for AI-generated recommendations]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
When `/pal-analyze` is invoked (by you in response to a nudge, or by the user directly):
|
|
8
|
+
|
|
9
|
+
## 1. Run the analyze tool
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pal cli analyze
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If the user passed `--actionable` or wants recommendations, append it: `pal cli analyze --actionable`
|
|
16
|
+
|
|
17
|
+
## 2. Parse and present the output
|
|
18
|
+
|
|
19
|
+
Read the console output and summarize the key signals. Focus on what's actionable, not exhaustive lists:
|
|
20
|
+
|
|
21
|
+
**Ratings section:**
|
|
22
|
+
- Lead with the average and trend direction
|
|
23
|
+
- Call out any striking low-rating clusters (e.g. "47 low ratings, mostly around scope drift corrections")
|
|
24
|
+
|
|
25
|
+
**Graduation candidates:**
|
|
26
|
+
- Name the top 1-3 patterns ready to crystallize
|
|
27
|
+
- Suggest the wisdom domain they'd live in
|
|
28
|
+
|
|
29
|
+
**Emerging patterns:**
|
|
30
|
+
- Name any 2-occurrence patterns worth watching
|
|
31
|
+
|
|
32
|
+
Format example:
|
|
33
|
+
```
|
|
34
|
+
Analysis complete:
|
|
35
|
+
|
|
36
|
+
Ratings: X.X/10 avg | N low (≤4) | N high (≥7)
|
|
37
|
+
Top low-rating context: "[cluster description]"
|
|
38
|
+
|
|
39
|
+
Ready to graduate (3+ occurrences):
|
|
40
|
+
- "[pattern]" → suggest adding to wisdom/[domain].md
|
|
41
|
+
|
|
42
|
+
Emerging (2 occurrences — one more to graduate):
|
|
43
|
+
- "[pattern]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 3. Offer to act
|
|
47
|
+
|
|
48
|
+
After showing results, ask:
|
|
49
|
+
> "Want to crystallize any of these into a wisdom frame, or dig into the low-rating clusters?"
|
|
50
|
+
|
|
51
|
+
If yes to crystallizing:
|
|
52
|
+
```bash
|
|
53
|
+
bun ~/.pal/tools/wisdom-frame.ts --domain <domain> --observation "<principle>"
|
|
54
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pal-reflect
|
|
3
|
+
description: Run relationship reflect — promote recurring behavioral observations into tracked opinions. Use when relationship reflect is due, or when the user asks to review what patterns have been observed.
|
|
4
|
+
argument-hint: [optional: --dry-run to preview without writing]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
When `/pal-reflect` is invoked (by you in response to a nudge, or by the user directly):
|
|
8
|
+
|
|
9
|
+
## 1. Run the reflect tool
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run tool:reflect
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If the user passed `--dry-run`, append it: `bun run tool:reflect -- --dry-run`
|
|
16
|
+
|
|
17
|
+
## 2. Parse and present the output
|
|
18
|
+
|
|
19
|
+
Read the console output and present a concise summary:
|
|
20
|
+
|
|
21
|
+
**If opinions were promoted or strengthened:**
|
|
22
|
+
```
|
|
23
|
+
Reflect complete — here's what changed:
|
|
24
|
+
|
|
25
|
+
NEW opinions (confidence seeded from recurring observations):
|
|
26
|
+
- [statement] (XX%)
|
|
27
|
+
|
|
28
|
+
STRENGTHENED opinions (evidence accumulated):
|
|
29
|
+
- [statement]: XX% → YY%
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**If nothing changed:**
|
|
33
|
+
```
|
|
34
|
+
Reflect complete — no patterns strong enough yet.
|
|
35
|
+
[N] observations loaded. An opinion promotes when the same pattern appears 2+ times.
|
|
36
|
+
Most recent observations:
|
|
37
|
+
- [list up to 3 recent O notes]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 3. Offer a follow-up
|
|
41
|
+
|
|
42
|
+
After showing results, say:
|
|
43
|
+
> "Want to review the full opinions list, or is there a specific pattern you'd like to reinforce or correct?"
|
|
44
|
+
|
|
45
|
+
If the user confirms or corrects something, use the opinion tool:
|
|
46
|
+
```bash
|
|
47
|
+
# Confirmed
|
|
48
|
+
bun ~/.pal/skills/opinion/tools/opinion.ts evidence "keywords" --confirmation "what they confirmed"
|
|
49
|
+
|
|
50
|
+
# Corrected
|
|
51
|
+
bun ~/.pal/skills/opinion/tools/opinion.ts evidence "keywords" --contradiction "what they corrected"
|
|
52
|
+
```
|
|
@@ -175,6 +175,8 @@ Don't just *list* what must be true — actively confirm the load-bearing facts
|
|
|
175
175
|
|
|
176
176
|
No criterion may rest on an unverified premise. If a premise can't be verified now, mark it explicitly as an assumption and add a criterion to check it during EXECUTE. After 2 failed attempts at the same sub-problem, stop and re-ground — re-run OBSERVE rather than iterating on a bad premise.
|
|
177
177
|
|
|
178
|
+
**Retrieval failure is a hard block.** If a grounding tool call (Read, fetch, API, query) returns an error or no content, stop here — do not enter EXECUTE. Report the exact error, do not infer content from filename, metadata, or prior context, and ask the user for direction before proceeding.
|
|
179
|
+
|
|
178
180
|
Output: `🧭 GROUNDING: [premises verified, or assumptions flagged]`
|
|
179
181
|
|
|
180
182
|
### ━━━ ⚡ EXECUTE ━━━ 3/5
|
|
@@ -234,16 +236,43 @@ bun ~/.pal/tools/algorithm-reflect.ts --task "description" --criteria N --passed
|
|
|
234
236
|
|
|
235
237
|
Set `--scope task-specific` when the Q2 idea is bound to this one task (e.g. a criterion that only matters for the specific file, API, or dataset you were handling) and would not generalize to the algorithm; use `general` (the default) for reusable structural improvements. This keeps the algorithm-update synthesis focused on changes worth folding into ALGORITHM.md.
|
|
236
238
|
|
|
237
|
-
**3. Relationship note** — write
|
|
239
|
+
**3. Relationship note** — write behavioral observations (O, W) and a session diary entry (--b).
|
|
240
|
+
|
|
241
|
+
The goal is behavioral intelligence about the user, not a session log. Session logs belong in handoff notes and project history. This step captures *who the user is* — observable patterns, preferences, world facts.
|
|
238
242
|
|
|
239
243
|
```bash
|
|
240
|
-
|
|
244
|
+
# Opinion — behavioral observation about the user (what you noticed about how they work):
|
|
245
|
+
bun ~/.pal/tools/relationship-note.ts --o "User prefers reviewing existing code before adding anything new" --confidence 0.80
|
|
246
|
+
|
|
247
|
+
# World fact — objective fact about the user's situation (tech stack, project state, context):
|
|
248
|
+
bun ~/.pal/tools/relationship-note.ts --w "User is building a backend service in TypeScript with Bun"
|
|
249
|
+
|
|
250
|
+
# Session diary — what the agent did this session (first-person, specific):
|
|
251
|
+
bun ~/.pal/tools/relationship-note.ts --b "Refactored the auth middleware to support refresh token rotation"
|
|
252
|
+
|
|
253
|
+
# Multiple notes in one call:
|
|
254
|
+
bun ~/.pal/tools/relationship-note.ts --o "User prefers one verified change at a time, not batches" --confidence 0.80 --b "Fixed the path-normalization bug in the hook merge logic"
|
|
241
255
|
```
|
|
242
256
|
|
|
243
|
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
257
|
+
**O (Opinion) — what to write:**
|
|
258
|
+
- Preference patterns: "User prefers short answers with a recommendation over exhaustive options"
|
|
259
|
+
- Correction patterns: "User redirects scope drift immediately without elaborating on why"
|
|
260
|
+
- Working style: "User verifies each change before asking for the next one"
|
|
261
|
+
- Confidence guide: 0.70 = single observation, 0.80 = confirmed twice, 0.85+ = established pattern
|
|
262
|
+
|
|
263
|
+
**W (World) — what to write:**
|
|
264
|
+
- Tech stack facts: "User's current project uses PostgreSQL, tRPC, and Next.js"
|
|
265
|
+
- Situation facts: "User is in the middle of a database migration with live traffic"
|
|
266
|
+
|
|
267
|
+
**--b (Session diary) — keep it sharp:**
|
|
268
|
+
- ✓ "Debugged the race condition in the message queue consumer and fixed the ack logic"
|
|
269
|
+
- ✗ "Helped with backend improvements" — too vague, no system named
|
|
270
|
+
|
|
271
|
+
**When to write O vs use the opinion tool (step 6):**
|
|
272
|
+
- O notes → subtle patterns you observed, not yet confirmed → goes into daily file → synthesis promotes to opinions.json over time
|
|
273
|
+
- Opinion tool → explicit user confirmation/correction → goes directly to opinions.json immediately
|
|
274
|
+
|
|
275
|
+
Skip only if the session was a trivial lookup or typo fix (same rule as step 2).
|
|
247
276
|
|
|
248
277
|
**4. Handoff note** — if work is unfinished, write what remains so the next session can pick up immediately:
|
|
249
278
|
|
|
@@ -111,14 +111,13 @@ Before any code, before any architecture — there must be clear thinking:
|
|
|
111
111
|
- Each tool is a standalone CLI program
|
|
112
112
|
- Each skill is a self-contained capability
|
|
113
113
|
- Hooks compose via `Promise.allSettled` — independent, parallel, isolated failures
|
|
114
|
-
- Tools compose via `
|
|
114
|
+
- Tools compose via `pal cli <name>` — standard I/O, exit codes
|
|
115
115
|
|
|
116
116
|
### 9. CLI as Interface
|
|
117
117
|
|
|
118
118
|
**Every operation should be accessible via command line.**
|
|
119
119
|
|
|
120
|
-
- `pal cli <command>` for system management
|
|
121
|
-
- `bun run tool:<name>` for utilities
|
|
120
|
+
- `pal cli <command>` for system management and user-facing utilities
|
|
122
121
|
- Skills triggered via agent slash commands
|
|
123
122
|
- No hidden operations — everything is scriptable and testable
|
|
124
123
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portable-agent-layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,9 +50,10 @@
|
|
|
50
50
|
"uninstall": "bun run src/cli/index.ts cli uninstall",
|
|
51
51
|
"tool:synthesize": "bun run src/tools/agent/synthesize.ts",
|
|
52
52
|
"tool:thread": "bun run src/tools/agent/thread.ts",
|
|
53
|
-
"tool:analyze": "bun run src/tools/agent/analyze.ts",
|
|
54
53
|
"tool:wisdom-frame": "bun run src/tools/agent/wisdom-frame.ts",
|
|
55
|
-
"tool:reflect": "bun run src/tools/relationship-reflect.ts"
|
|
54
|
+
"tool:reflect": "bun run src/tools/relationship-reflect.ts",
|
|
55
|
+
"eval:sentiment": "bun eval/run.ts sentiment",
|
|
56
|
+
"eval:failure-principle": "bun eval/run.ts failure-principle"
|
|
56
57
|
},
|
|
57
58
|
"lint-staged": {
|
|
58
59
|
"*.ts": [
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
"jscpd": "^4.2.3",
|
|
76
77
|
"knip": "^6.14.1",
|
|
77
78
|
"lint-staged": "17.0.5",
|
|
79
|
+
"promptfoo": "0.121.14",
|
|
78
80
|
"semantic-release": "^25.0.3",
|
|
79
81
|
"typescript": "^5.9.3"
|
|
80
82
|
},
|
|
@@ -83,6 +85,7 @@
|
|
|
83
85
|
"adm-zip": "^0.5.17",
|
|
84
86
|
"marked": "18.0.4",
|
|
85
87
|
"pdf-lib": "1.17.1",
|
|
86
|
-
"playwright": "^1.60.0"
|
|
88
|
+
"playwright": "^1.60.0",
|
|
89
|
+
"unpdf": "^0.12.0"
|
|
87
90
|
}
|
|
88
91
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -192,6 +192,11 @@ async function runCli(command: string | undefined, args: string[]) {
|
|
|
192
192
|
runMigrate(args);
|
|
193
193
|
break;
|
|
194
194
|
}
|
|
195
|
+
case "analyze": {
|
|
196
|
+
const { run: runAnalyze } = await import("../tools/agent/analyze");
|
|
197
|
+
await runAnalyze(args);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
195
200
|
case "usage": {
|
|
196
201
|
const { usage } = await import("../tools/token-cost");
|
|
197
202
|
usage();
|
|
@@ -251,6 +256,7 @@ function showHelp() {
|
|
|
251
256
|
pal cli status Show PAL configuration
|
|
252
257
|
pal cli doctor [--probe-inference] Check prerequisites and health (--probe fires real inference per route)
|
|
253
258
|
pal cli migrate [--list] [--dry-run] Run pending data migrations
|
|
259
|
+
pal cli analyze [--actionable] Learning analysis: ratings, failure patterns, graduation candidates
|
|
254
260
|
pal cli usage Summarize token usage and cost
|
|
255
261
|
pal cli knowledge <sub> [args] Query & manage the knowledge store
|
|
256
262
|
(search · graph · stats · hubs · find · show · add · ls)
|
|
@@ -57,13 +57,7 @@ async function processFailurePrinciple(
|
|
|
57
57
|
.join("\n\n");
|
|
58
58
|
|
|
59
59
|
const result = await inference({
|
|
60
|
-
system: `Analyze this failed AI interaction
|
|
61
|
-
|
|
62
|
-
Return JSON:
|
|
63
|
-
{
|
|
64
|
-
"principle": "<one actionable rule the AI should follow, 10-20 words. Start with a verb: 'Verify...', 'Always...', 'Never...', 'Ask before...'>",
|
|
65
|
-
"detailed_context": "<what went wrong and why, 50-150 words>"
|
|
66
|
-
}`,
|
|
60
|
+
system: `Analyze this failed AI interaction (rated ${pending.rating}/10). Return JSON: {"principle": "<verb-first actionable rule, 10-20 words — write a full sentence, not a fragment>", "detailed_context": "<root cause and what to do differently, 50-150 words>"}.`,
|
|
67
61
|
user: `User feedback: ${pending.context}\n\nConversation:\n${recent}`,
|
|
68
62
|
maxTokens: 400,
|
|
69
63
|
timeout: 60000,
|
|
@@ -196,51 +196,21 @@ OUTPUT FORMAT (JSON only):
|
|
|
196
196
|
"rating": <1-10 or null>,
|
|
197
197
|
"sentiment": "positive" | "negative" | "neutral",
|
|
198
198
|
"confidence": <0.0-1.0>,
|
|
199
|
-
"summary": "<
|
|
200
|
-
"detailed_context": "<
|
|
201
|
-
"principle": "<one actionable rule
|
|
199
|
+
"summary": "<10 words max>",
|
|
200
|
+
"detailed_context": "<what the user wanted, what the AI did or failed to do, root cause, and what to do differently — 50-150 words>",
|
|
201
|
+
"principle": "<one actionable rule, 10-20 words, start with a verb: Verify / Always / Never / Ask>"
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- 5: Neutral
|
|
215
|
-
- 6-7: Satisfaction, approval
|
|
216
|
-
- 8-9: Strong approval, impressed
|
|
217
|
-
- 10: Extraordinary enthusiasm
|
|
218
|
-
|
|
219
|
-
CRITICAL DISTINCTIONS:
|
|
220
|
-
- Profanity can indicate EITHER frustration OR excitement — use context
|
|
221
|
-
- Sarcasm: "Oh great, another error" = negative despite "great"
|
|
222
|
-
- Short praise ("great job", "nice") = STRONG APPROVAL (8-9), not mild
|
|
223
|
-
|
|
224
|
-
IMPLIED SENTIMENT (most feedback is implied, not explicit):
|
|
225
|
-
|
|
226
|
-
Implied NEGATIVE (rate 2-4):
|
|
227
|
-
- CORRECTIONS: "No, I meant..." / "That's not what I said" -> 3-4
|
|
228
|
-
- REPEATED REQUESTS: Having to ask the same thing twice -> 2-3
|
|
229
|
-
- BEHAVIORAL CORRECTIONS: "Don't do that" / "Stop doing X" -> 3
|
|
230
|
-
- EXASPERATED QUESTIONS: "Why is this still broken?" -> 2-3
|
|
231
|
-
- SHORT DISMISSALS: "whatever" / "fine" / "just do it" -> 3-4
|
|
232
|
-
- POINTING OUT OMISSIONS: "What about X?" (obviously required) -> 4
|
|
233
|
-
|
|
234
|
-
Implied POSITIVE (rate 6-8):
|
|
235
|
-
- TRUST SIGNALS: "Alright, fix all of it" / "Go ahead" -> 7
|
|
236
|
-
- BUILDING ON WORK: "Now also add..." / "Next, do..." -> 6-7
|
|
237
|
-
- ENGAGED FOLLOW-UPS: "What about X?" (exploring, not correcting) -> 6
|
|
238
|
-
- MOVING FORWARD: Accepting output and giving next task -> 6
|
|
239
|
-
|
|
240
|
-
WHEN TO RETURN null FOR RATING:
|
|
241
|
-
- Neutral technical questions ("Can you check the logs?")
|
|
242
|
-
- Simple commands ("Do it", "Yes", "Continue")
|
|
243
|
-
- No emotional indicators present`;
|
|
204
|
+
RATING SCALE: 1-2 strong frustration · 3-4 mild frustration · 5 neutral · 6-7 approval · 8-9 strong approval · 10 exceptional · null = no emotional signal
|
|
205
|
+
Short praise ("great job", "nice") = STRONG APPROVAL (8-9), not mild.
|
|
206
|
+
|
|
207
|
+
TASK DIRECTIVES — short imperatives with "pls" are approval or sequencing signals (null or 6-7, NOT negative):
|
|
208
|
+
"write it pls" / "do it pls" / "step 3 pls" / "option 2 pls" = user directing or approving the AI's own proposal.
|
|
209
|
+
EXCEPTION: "fix X pls" / "fix that pls" is always negative — "fix" implies a problem regardless of "pls".
|
|
210
|
+
|
|
211
|
+
IMPLIED NEGATIVE (rate 2-4): corrections ("that's wrong", "I meant..."), repeats ("again pls", "still broken", "doesn't work again"), behavioral corrections ("don't do that"), exasperated questions ("why is this still broken?"), failure confirmations ("nothing is fixed").
|
|
212
|
+
IMPLIED POSITIVE (rate 6-8): trust signals ("go ahead", "fix all of it"), continuation ("now also add X", "next do..."), moving forward with the next task.
|
|
213
|
+
NULL rating: neutral questions, simple continuations ("yes", "continue"), task directives without failure context.`;
|
|
244
214
|
|
|
245
215
|
const MIN_CONFIDENCE = 0.5;
|
|
246
216
|
|
|
@@ -43,6 +43,21 @@ function countNotesSince(since: string): number {
|
|
|
43
43
|
return count;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Session-start nudge: surfaces when ≥5 new O/W/B notes are pending review.
|
|
48
|
+
* Returns a formatted reminder string, or "" when nothing is due.
|
|
49
|
+
*/
|
|
50
|
+
export function loadReflectNudge(): string {
|
|
51
|
+
const lastReflect = getLastReflectDate();
|
|
52
|
+
const newNotes = countNotesSince(lastReflect || "2000-01-01");
|
|
53
|
+
if (newNotes < MIN_NEW_NOTES) return "";
|
|
54
|
+
const since = lastReflect || "the start";
|
|
55
|
+
return [
|
|
56
|
+
"## Relationship Reflect Due",
|
|
57
|
+
`🔄 ${newNotes} new relationship observations since ${since} — offer to run \`/pal-reflect\` to promote recurring patterns into tracked opinions.`,
|
|
58
|
+
].join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
46
61
|
export async function checkReflectTrigger(): Promise<void> {
|
|
47
62
|
const lastReflect = getLastReflectDate();
|
|
48
63
|
const now = new Date();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze nudge — surfaces a session-start reminder when the learning analysis
|
|
3
|
+
* is overdue (≥7 days since last run, or never run).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { paths } from "./paths";
|
|
9
|
+
|
|
10
|
+
const MAX_DAYS = 7;
|
|
11
|
+
const DAY_MS = 86_400_000;
|
|
12
|
+
|
|
13
|
+
function markFile(): string {
|
|
14
|
+
return resolve(paths.state(), "last-analyze.json");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readLastAnalyzeDate(): string | null {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(readFileSync(markFile(), "utf-8"));
|
|
20
|
+
return typeof parsed.lastAnalyzedTs === "string" ? parsed.lastAnalyzedTs : null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function writeLastAnalyzeDate(ts: string): void {
|
|
27
|
+
writeFileSync(markFile(), `${JSON.stringify({ lastAnalyzedTs: ts }, null, 2)}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadAnalyzeNudge(now: Date = new Date()): string {
|
|
31
|
+
const last = readLastAnalyzeDate();
|
|
32
|
+
if (!last) {
|
|
33
|
+
return [
|
|
34
|
+
"## Learning Analysis Due",
|
|
35
|
+
"📊 Learning analysis has never been run — offer to run `/pal-analyze` (`pal cli analyze`) for a health check on ratings, failure patterns, and graduation candidates.",
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const daysSince = (now.getTime() - new Date(last).getTime()) / DAY_MS;
|
|
40
|
+
if (daysSince < MAX_DAYS) return "";
|
|
41
|
+
|
|
42
|
+
const since = new Date(last).toISOString().slice(0, 10);
|
|
43
|
+
return [
|
|
44
|
+
"## Learning Analysis Due",
|
|
45
|
+
`📊 Learning analysis last run ${Math.floor(daysSince)}d ago (${since}) — offer to run \`/pal-analyze\` for a health check.`,
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
package/src/hooks/lib/context.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
|
+
import { loadReflectNudge } from "../handlers/reflect-trigger";
|
|
9
10
|
import { loadAlgorithmReviewNudge } from "./algorithm-review";
|
|
11
|
+
import { loadAnalyzeNudge } from "./analyze-nudge";
|
|
10
12
|
import { readLearnings } from "./learning-store";
|
|
11
13
|
import { loadOpinionContext } from "./opinions";
|
|
12
14
|
import { paths } from "./paths";
|
|
@@ -300,10 +302,14 @@ export function buildSystemReminder(opts: { agent?: AgentTarget } = {}): string
|
|
|
300
302
|
const handoff = settings.isEnabled("handoff") ? loadHandoff() : "";
|
|
301
303
|
// Maintainer-only: self-gates to a repo checkout, "" for everyone else.
|
|
302
304
|
const algoReview = loadAlgorithmReviewNudge();
|
|
305
|
+
const reflectNudge = loadReflectNudge();
|
|
306
|
+
const analyzeNudge = loadAnalyzeNudge();
|
|
303
307
|
const parts: string[] = [];
|
|
304
308
|
if (startup) parts.push(startup);
|
|
305
309
|
if (handoff) parts.push(handoff);
|
|
306
310
|
if (algoReview) parts.push(algoReview);
|
|
311
|
+
if (reflectNudge) parts.push(reflectNudge);
|
|
312
|
+
if (analyzeNudge) parts.push(analyzeNudge);
|
|
307
313
|
if (selfModel) parts.push(selfModel);
|
|
308
314
|
if (wisdom) parts.push(wisdom);
|
|
309
315
|
if (opinions) parts.push(opinions);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { parseArgs } from "node:util";
|
|
12
|
+
import { writeLastAnalyzeDate } from "../../hooks/lib/analyze-nudge";
|
|
12
13
|
import { type AnalysisResult, analyze } from "../../hooks/lib/graduation";
|
|
13
14
|
|
|
14
15
|
// ── ANSI Colors ──
|
|
@@ -23,7 +24,7 @@ const c = {
|
|
|
23
24
|
magenta: (s: string) => `\x1b[35m${s}\x1b[0m`,
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
function printReport(result: AnalysisResult): void {
|
|
27
28
|
const hasPatterns = result.candidates.length > 0 || result.emerging.length > 0;
|
|
28
29
|
const hasRatings = result.ratings !== null;
|
|
29
30
|
|
|
@@ -117,9 +118,9 @@ export function printReport(result: AnalysisResult): void {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
async function run() {
|
|
121
|
+
export async function run(argv: string[] = Bun.argv.slice(2)) {
|
|
121
122
|
const { values } = parseArgs({
|
|
122
|
-
args:
|
|
123
|
+
args: argv,
|
|
123
124
|
options: {
|
|
124
125
|
help: { type: "boolean", short: "h" },
|
|
125
126
|
actionable: { type: "boolean", short: "a" },
|
|
@@ -145,13 +146,14 @@ async function run() {
|
|
|
145
146
|
To crystallize a graduated pattern, add it to the target wisdom frame:
|
|
146
147
|
- Your principle here [CRYSTAL: 85%]
|
|
147
148
|
|
|
148
|
-
Usage:
|
|
149
|
+
Usage: pal cli analyze [--actionable]
|
|
149
150
|
`);
|
|
150
151
|
process.exit(0);
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
const result = await analyze({ actionable: values.actionable });
|
|
154
155
|
printReport(result);
|
|
156
|
+
writeLastAnalyzeDate(new Date().toISOString());
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
if (import.meta.main) await run();
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* RelationshipNote — Write
|
|
3
|
+
* RelationshipNote — Write W/O/Session entries to today's relationship log.
|
|
4
4
|
*
|
|
5
|
-
* Called in the ALGORITHM LEARN phase.
|
|
6
|
-
*
|
|
5
|
+
* Called in the ALGORITHM LEARN phase. Writes behavioral observations about
|
|
6
|
+
* the user (O, W) and session diary entries (--b).
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
* bun ~/.pal/tools/relationship-note.ts --
|
|
9
|
+
* bun ~/.pal/tools/relationship-note.ts --o "Rico prefers X" --confidence 0.80
|
|
10
|
+
* bun ~/.pal/tools/relationship-note.ts --w "Rico is building X in TypeScript"
|
|
11
|
+
* bun ~/.pal/tools/relationship-note.ts --b "Debugged the cache split logic"
|
|
12
|
+
*
|
|
13
|
+
* Note types:
|
|
14
|
+
* --o Opinion/behavioral observation about the user (requires --confidence)
|
|
15
|
+
* --w World fact about the user's situation (objective, observable)
|
|
16
|
+
* --b Session diary — what Jarvis did this session (first-person, specific)
|
|
10
17
|
*/
|
|
11
18
|
|
|
12
19
|
import { parseArgs } from "node:util";
|
|
@@ -16,35 +23,72 @@ function run() {
|
|
|
16
23
|
const { values } = parseArgs({
|
|
17
24
|
args: Bun.argv.slice(2),
|
|
18
25
|
options: {
|
|
26
|
+
o: { type: "string", multiple: true },
|
|
27
|
+
w: { type: "string", multiple: true },
|
|
19
28
|
b: { type: "string" },
|
|
29
|
+
confidence: { type: "string" },
|
|
20
30
|
help: { type: "boolean", short: "h" },
|
|
21
31
|
},
|
|
22
32
|
});
|
|
23
33
|
|
|
24
34
|
if (values.help) {
|
|
25
35
|
console.log(`
|
|
26
|
-
RelationshipNote — Append
|
|
36
|
+
RelationshipNote — Append W/O/Session entries to today's relationship log
|
|
27
37
|
|
|
28
38
|
Usage:
|
|
29
|
-
bun ~/.pal/tools/relationship-note.ts --
|
|
39
|
+
bun ~/.pal/tools/relationship-note.ts --o "Rico prefers X" --confidence 0.80
|
|
40
|
+
bun ~/.pal/tools/relationship-note.ts --w "Rico is building X in TypeScript"
|
|
41
|
+
bun ~/.pal/tools/relationship-note.ts --b "Debugged the cache split logic"
|
|
42
|
+
|
|
43
|
+
Flags:
|
|
44
|
+
--o TEXT Opinion/behavioral observation about the user
|
|
45
|
+
--confidence N Confidence for --o (0.0–1.0, default 0.75)
|
|
46
|
+
--w TEXT World fact about the user's situation
|
|
47
|
+
--b TEXT Session diary — what Jarvis did (first-person, specific)
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
--b What happened this session (1-2 sentences, first-person, specific)
|
|
49
|
+
Multiple flags may be combined in one call. At least one of --o, --w, --b is required.
|
|
33
50
|
|
|
34
51
|
Output: appends to memory/relationship/YYYY-MM/YYYY-MM-DD.md
|
|
35
52
|
`);
|
|
36
53
|
process.exit(0);
|
|
37
54
|
}
|
|
38
55
|
|
|
39
|
-
if (!values.b) {
|
|
40
|
-
console.error("Required: --b");
|
|
56
|
+
if (!values.o && !values.w && !values.b) {
|
|
57
|
+
console.error("Required: at least one of --o, --w, --b");
|
|
41
58
|
process.exit(1);
|
|
42
59
|
}
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
const notes = [];
|
|
62
|
+
|
|
63
|
+
if (values.o && values.o.length > 0) {
|
|
64
|
+
const confidence = values.confidence ? Number.parseFloat(values.confidence) : 0.75;
|
|
65
|
+
if (Number.isNaN(confidence) || confidence < 0 || confidence > 1) {
|
|
66
|
+
console.error("--confidence must be a number between 0.0 and 1.0");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
for (const text of values.o) {
|
|
70
|
+
notes.push({ type: "O" as const, text, confidence });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (values.w && values.w.length > 0) {
|
|
75
|
+
for (const text of values.w) {
|
|
76
|
+
notes.push({ type: "W" as const, text });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (values.b) {
|
|
81
|
+
notes.push({ type: "Session" as const, text: values.b });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
appendNotes(notes);
|
|
45
85
|
|
|
46
86
|
console.log(
|
|
47
|
-
JSON.stringify(
|
|
87
|
+
JSON.stringify(
|
|
88
|
+
{ success: true, message: "Relationship note written", count: notes.length },
|
|
89
|
+
null,
|
|
90
|
+
2
|
|
91
|
+
)
|
|
48
92
|
);
|
|
49
93
|
}
|
|
50
94
|
|