portable-agent-layer 0.51.2 → 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/statusline.ps1 +2 -2
- package/assets/statusline.sh +2 -2
- package/assets/templates/PAL/ALGORITHM.md +35 -6
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +2 -3
- package/assets/templates/pal-settings.json +0 -3
- package/package.json +7 -4
- package/src/cli/index.ts +6 -0
- package/src/hooks/LoadContext.ts +2 -1
- package/src/hooks/UserPromptOrchestrator.ts +5 -3
- package/src/hooks/handlers/failure-principle.ts +1 -7
- package/src/hooks/handlers/inject-retrieval.ts +5 -3
- package/src/hooks/handlers/rating.ts +14 -44
- package/src/hooks/handlers/reflect-trigger.ts +15 -0
- package/src/hooks/lib/analyze-nudge.ts +47 -0
- package/src/hooks/lib/context.ts +7 -18
- package/src/hooks/lib/log.ts +34 -2
- package/src/hooks/lib/paths.ts +1 -0
- package/src/hooks/lib/projects.ts +0 -11
- package/src/hooks/lib/security.ts +1 -7
- package/src/hooks/lib/semi-static.ts +1 -62
- package/src/targets/lib.ts +41 -16
- package/src/targets/opencode/plugin.ts +7 -5
- package/src/tools/agent/analyze.ts +6 -4
- package/src/tools/agent/relationship-note.ts +56 -12
- package/src/tools/agent/synthesize.ts +40 -0
- package/src/tools/self-model.ts +18 -37
- package/src/hooks/lib/signal-trends.ts +0 -117
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
|
+
```
|
package/assets/statusline.ps1
CHANGED
|
@@ -29,7 +29,7 @@ $COST_STR = '$' + ([math]::Round($COST, 2)).ToString("0.00")
|
|
|
29
29
|
|
|
30
30
|
# PAL: Hook health - count ERROR lines in debug.log from last 24h
|
|
31
31
|
$HOOK_ERRORS = 0
|
|
32
|
-
$debugLog = Join-Path $env:USERPROFILE ".pal\
|
|
32
|
+
$debugLog = Join-Path $env:USERPROFILE ".pal\debug\debug.log"
|
|
33
33
|
if (Test-Path $debugLog) {
|
|
34
34
|
$cutoff = (Get-Date).AddHours(-24)
|
|
35
35
|
try {
|
|
@@ -173,7 +173,7 @@ $QUOTES = @(
|
|
|
173
173
|
"A person who never made a mistake never tried anything new.|Albert Einstein"
|
|
174
174
|
"The journey of a thousand miles begins with one step.|Lao Tzu"
|
|
175
175
|
"The obstacle is the way.|Marcus Aurelius"
|
|
176
|
-
"Whether you think you can or you think you can't
|
|
176
|
+
"Whether you think you can or you think you can't - you are right.|Henry Ford"
|
|
177
177
|
"Everything should be made as simple as possible, but not simpler.|Albert Einstein"
|
|
178
178
|
"You miss 100% of the shots you don't take.|Wayne Gretzky"
|
|
179
179
|
)
|
package/assets/statusline.sh
CHANGED
|
@@ -56,7 +56,7 @@ fi
|
|
|
56
56
|
|
|
57
57
|
# PAL: Hook health — count ERROR lines in debug.log from last 24h
|
|
58
58
|
HOOK_ERRORS=0
|
|
59
|
-
DEBUG_LOG="$HOME/.pal/
|
|
59
|
+
DEBUG_LOG="$HOME/.pal/debug/debug.log"
|
|
60
60
|
if [ -f "$DEBUG_LOG" ]; then
|
|
61
61
|
# Cross-platform 24h cutoff: macOS uses date -v, GNU Linux uses date -d
|
|
62
62
|
CUTOFF=$(date -v-24H "+%Y-%m-%d %H:%M:%S" 2>/dev/null || date -d '24 hours ago' "+%Y-%m-%d %H:%M:%S" 2>/dev/null)
|
|
@@ -209,7 +209,7 @@ QUOTES=(
|
|
|
209
209
|
"A person who never made a mistake never tried anything new.|Albert Einstein"
|
|
210
210
|
"The journey of a thousand miles begins with one step.|Lao Tzu"
|
|
211
211
|
"The obstacle is the way.|Marcus Aurelius"
|
|
212
|
-
"Whether you think you can or you think you can't
|
|
212
|
+
"Whether you think you can or you think you can't - you are right.|Henry Ford"
|
|
213
213
|
"Everything should be made as simple as possible, but not simpler.|Albert Einstein"
|
|
214
214
|
"You miss 100% of the shots you don't take.|Wayne Gretzky"
|
|
215
215
|
)
|
|
@@ -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)
|
package/src/hooks/LoadContext.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { resolve } from "node:path";
|
|
|
14
14
|
import { getActiveAgent, isCodex, isCopilot, isCursor } from "./lib/agent";
|
|
15
15
|
import { buildClaudeMd, regenerateIfNeeded } from "./lib/claude-md";
|
|
16
16
|
import { type AgentTarget, buildSystemReminder } from "./lib/context";
|
|
17
|
-
import { logDebug, logError } from "./lib/log";
|
|
17
|
+
import { logContextSnapshot, logDebug, logError } from "./lib/log";
|
|
18
18
|
import { platform } from "./lib/paths";
|
|
19
19
|
import { isPalSpawnedInference } from "./lib/spawn-guard";
|
|
20
20
|
|
|
@@ -48,6 +48,7 @@ try {
|
|
|
48
48
|
active === "copilot" || active === "cursor" ? active : "claude";
|
|
49
49
|
const reminder = buildSystemReminder({ agent });
|
|
50
50
|
if (!reminder) process.exit(0);
|
|
51
|
+
logContextSnapshot(reminder);
|
|
51
52
|
|
|
52
53
|
if (isCopilot()) {
|
|
53
54
|
// Copilot: semi-static in ~/.copilot/instructions/pal-*.instructions.md (written at stop).
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { injectRetrieval } from "./handlers/inject-retrieval";
|
|
11
11
|
import { captureRating } from "./handlers/rating";
|
|
12
12
|
import { captureSessionName } from "./handlers/session-name";
|
|
13
|
-
import { logDebug, logError } from "./lib/log";
|
|
13
|
+
import { logDebug, logError, logPromptSnapshot } from "./lib/log";
|
|
14
14
|
import { isPalSpawnedInference } from "./lib/spawn-guard";
|
|
15
15
|
import { readStdinJSON } from "./lib/stdin";
|
|
16
16
|
|
|
@@ -30,13 +30,15 @@ logDebug("UserPromptOrchestrator", `Input: ${JSON.stringify(input).slice(0, 200)
|
|
|
30
30
|
if (!input?.prompt) process.exit(0);
|
|
31
31
|
|
|
32
32
|
const sessionId = input.session_id ?? input.sessionId ?? input.conversation_id;
|
|
33
|
+
const retrieval = await injectRetrieval(input.prompt);
|
|
34
|
+
logPromptSnapshot(input.prompt, retrieval);
|
|
35
|
+
|
|
33
36
|
const results = await Promise.allSettled([
|
|
34
37
|
captureRating(input.prompt, sessionId),
|
|
35
38
|
captureSessionName(input.prompt, sessionId ?? ""),
|
|
36
|
-
injectRetrieval(input.prompt),
|
|
37
39
|
]);
|
|
38
40
|
|
|
39
|
-
const handlerNames = ["rating", "session-name"
|
|
41
|
+
const handlerNames = ["rating", "session-name"];
|
|
40
42
|
for (let i = 0; i < results.length; i++) {
|
|
41
43
|
const r = results[i];
|
|
42
44
|
if (r.status === "rejected") {
|
|
@@ -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,
|
|
@@ -52,10 +52,11 @@ export async function getRetrievalReminder(prompt: string): Promise<string | nul
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Write retrieval reminder to stdout in the correct format for the current agent.
|
|
55
|
-
* Claude Code: plain text. Cursor: { additional_context }. Codex: hookSpecificOutput JSON.
|
|
56
|
-
|
|
55
|
+
* Claude Code: plain text. Cursor: { additional_context }. Codex: hookSpecificOutput JSON.
|
|
56
|
+
* Returns the reminder string that was injected, or null if nothing was injected. */
|
|
57
|
+
export async function injectRetrieval(prompt: string): Promise<string | null> {
|
|
57
58
|
const reminder = await getRetrievalReminder(prompt);
|
|
58
|
-
if (!reminder) return;
|
|
59
|
+
if (!reminder) return null;
|
|
59
60
|
if (isCursor()) {
|
|
60
61
|
process.stdout.write(JSON.stringify({ additional_context: reminder }));
|
|
61
62
|
} else if (isCodex()) {
|
|
@@ -70,4 +71,5 @@ export async function injectRetrieval(prompt: string): Promise<void> {
|
|
|
70
71
|
} else {
|
|
71
72
|
process.stdout.write(`${reminder}\n`);
|
|
72
73
|
}
|
|
74
|
+
return reminder;
|
|
73
75
|
}
|
|
@@ -64,7 +64,7 @@ export function parseExplicitRating(
|
|
|
64
64
|
// Reject if rest starts with words indicating a sentence, not a rating
|
|
65
65
|
if (rest) {
|
|
66
66
|
const sentenceStarters =
|
|
67
|
-
/^(items?|things?|steps?|files?|lines?|bugs?|issues?|errors?|times?|minutes?|hours?|days?|seconds?|percent|%|th\b|st\b|nd\b|rd\b|of\b|in\b|at\b|to\b|the\b|a\b|an\b)/i;
|
|
67
|
+
/^(items?|things?|steps?|files?|lines?|bugs?|issues?|errors?|times?|minutes?|hours?|days?|seconds?|percent|%|th\b|st\b|nd\b|rd\b|of\b|in\b|at\b|to\b|the\b|a\b|an\b|then\b|also\b|next\b)/i;
|
|
68
68
|
if (sentenceStarters.test(rest)) return null;
|
|
69
69
|
|
|
70
70
|
// Reject item selections: "1 and 2", "2 3 5", "1, 3, 5", "1-3"
|
|
@@ -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
|
+
}
|