portable-agent-layer 0.47.0 โ 0.49.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/STATUSLINE.md +102 -0
- package/assets/skills/create-skill/SKILL.md +6 -1
- package/assets/statusline.ps1 +142 -0
- package/assets/statusline.sh +167 -0
- package/assets/templates/PAL/ALGORITHM.md +16 -1
- package/assets/templates/settings.claude.json +21 -0
- package/package.json +1 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/skill.ts +18 -3
- package/src/hooks/lib/algorithm-review.ts +85 -0
- package/src/hooks/lib/context.ts +4 -0
- package/src/hooks/lib/learning-store.ts +45 -0
- package/src/hooks/lib/paths.ts +5 -0
- package/src/hooks/lib/retrieval-index.ts +32 -5
- package/src/hooks/lib/retrieval.ts +2 -1
- package/src/hooks/lib/semi-static.ts +29 -6
- package/src/targets/claude/install.ts +9 -1
- package/src/targets/claude/uninstall.ts +9 -1
- package/src/targets/lib.ts +216 -0
- package/src/tools/agent/algorithm-reflect.ts +10 -5
- package/src/tools/agent/algorithm-synthesize.ts +218 -0
- package/src/tools/skill-doctor.ts +254 -0
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ pal cli status # check your setup
|
|
|
86
86
|
| `pal cli usage` | Summarize token usage and estimated cost |
|
|
87
87
|
| `pal cli knowledge` | Query & manage the knowledge store (search, graph, stats, hubs, find, show, add, ls, ingest) |
|
|
88
88
|
| `pal cli skill link <name>` | Link a personal `~/.pal/skills/<name>/` into every installed agent so it is discoverable |
|
|
89
|
+
| `pal cli skill doctor <name>` | Evaluate a skill against the authoring best practices (folder/file-name match, name, description, body length, point-of-view, reference depth) |
|
|
89
90
|
|
|
90
91
|
### Target flags
|
|
91
92
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# PAL Status Line
|
|
2
|
+
|
|
3
|
+
A customizable status line for Claude Code that displays context usage, active model, session cost, current directory, and git branch.
|
|
4
|
+
|
|
5
|
+
## What It Shows
|
|
6
|
+
|
|
7
|
+
**Line 1:** Model name, current directory, git branch, hook health, open ISCs, signal trend
|
|
8
|
+
**Line 2:** Context usage progress bar, percentage used, session cost, remaining context, rate limits
|
|
9
|
+
**Line 3:** Update notice (only when a new PAL version is available)
|
|
10
|
+
|
|
11
|
+
Example output (macOS/Linux):
|
|
12
|
+
```
|
|
13
|
+
[Opus] ๐ portable-agent-layer ๐ฟ main ๐ 21 open ISCs sig: 3.0/10 โ
|
|
14
|
+
โโโโโโโโโโโโโโโโโโโโ 45% โ $0.12 โ 55% free
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Example output (Windows):
|
|
18
|
+
```
|
|
19
|
+
[Opus] folder: portable-agent-layer (git: main) 21 open ISCs sig: 3.0/10 v
|
|
20
|
+
########------------ 45% - $0.12 - 55% free
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Colors adapt based on context usage:
|
|
24
|
+
- ๐ข Green: 0-60% used
|
|
25
|
+
- ๐ก Yellow: 60-80% used
|
|
26
|
+
- ๐ด Red: >80% used
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
### On macOS / Linux
|
|
31
|
+
|
|
32
|
+
1. Make the script executable:
|
|
33
|
+
```bash
|
|
34
|
+
chmod +x ~/.claude/statusline.sh
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
2. Add to `~/.claude/settings.json`:
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"statusLine": {
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "~/.claude/statusline.sh",
|
|
43
|
+
"padding": 2
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The statusline script is at: `portable-agent-layer/assets/statusline.sh`
|
|
49
|
+
|
|
50
|
+
### On Windows
|
|
51
|
+
|
|
52
|
+
1. Add to `~/.claude/settings.json`:
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"statusLine": {
|
|
56
|
+
"type": "command",
|
|
57
|
+
"command": "powershell -NoProfile -Command \"Get-Content -Raw | & 'C:\\path\\to\\statusline.ps1'\"",
|
|
58
|
+
"padding": 2
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or copy `statusline.ps1` to `~/.claude/statusline.ps1` and reference it from settings.
|
|
64
|
+
|
|
65
|
+
The statusline script is at: `portable-agent-layer/assets/statusline.ps1`
|
|
66
|
+
|
|
67
|
+
## Data Displayed
|
|
68
|
+
|
|
69
|
+
- **Model:** Active Claude model (Opus, Sonnet, Haiku, etc.)
|
|
70
|
+
- **Directory:** Repository name (if in a git repo) or folder name
|
|
71
|
+
- **Git Branch:** Current branch, fetched from git worktree or `git` command
|
|
72
|
+
- **Hook Health:** Count of ERROR lines in `~/.pal/memory/state/debug.log` from last 24h (hidden when 0)
|
|
73
|
+
- **Open ISCs:** Count of unchecked ISCs for the current project (hidden when 0)
|
|
74
|
+
- **Signal Trend:** Session quality score from `~/.pal/memory/state/signal-cache.json` with trend arrow
|
|
75
|
+
- **Context Bar:** Visual progress bar showing token usage
|
|
76
|
+
- **Context %:** Percentage of context window used
|
|
77
|
+
- **Cost:** Estimated session cost in USD (shows "free" if <$0.01 on macOS/Linux, `$0.00` on Windows)
|
|
78
|
+
- **Remaining %:** Percentage of context window remaining
|
|
79
|
+
- **Rate Limits:** 5h and 7d usage percentages (Pro/Max plans only โ hidden on other plans)
|
|
80
|
+
- **Update Notice:** Shown on line 3 when `~/.pal/memory/state/update-available.json` flags a new version
|
|
81
|
+
|
|
82
|
+
## Dependencies
|
|
83
|
+
|
|
84
|
+
- **macOS/Linux:** `bash`, `jq`, `git` (for branch detection)
|
|
85
|
+
- **Windows:** PowerShell 7+, `git` (for branch detection)
|
|
86
|
+
|
|
87
|
+
## Customization
|
|
88
|
+
|
|
89
|
+
Both scripts use `jq` to parse the JSON data Claude Code sends. You can modify them to:
|
|
90
|
+
- Add or remove fields
|
|
91
|
+
- Change colors (ANSI escape codes)
|
|
92
|
+
- Adjust progress bar width
|
|
93
|
+
- Show additional data like effort level, rate limits, or PR status
|
|
94
|
+
|
|
95
|
+
Available fields are documented in the Claude Code statusline reference:
|
|
96
|
+
https://code.claude.com/docs/en/statusline#available-data
|
|
97
|
+
|
|
98
|
+
## Updates
|
|
99
|
+
|
|
100
|
+
The status line runs after each assistant message, after `/compact`, and when vim mode toggles. It does not consume API tokens.
|
|
101
|
+
|
|
102
|
+
Changes to the script appear on your next interaction with Claude Code.
|
|
@@ -57,7 +57,12 @@ The `description` should state **both what the skill does and when to invoke it*
|
|
|
57
57
|
pal cli skill link <name>
|
|
58
58
|
```
|
|
59
59
|
This creates the per-skill discovery symlink in each installed agent's skills directory (Claude Code, Cursor, Copilot, Codex); opencode discovers it automatically via `~/.pal/skills/`.
|
|
60
|
-
6.
|
|
60
|
+
6. Run the doctor and resolve every error it reports:
|
|
61
|
+
```bash
|
|
62
|
+
pal cli skill doctor <name>
|
|
63
|
+
```
|
|
64
|
+
It checks the mechanical rules (folder/file-name match, name length/charset, description length, point-of-view, body length, reference depth). Fix all `โ` errors; weigh each `โ ` warning. A name/folder mismatch or a misnamed file makes the skill silently fail to load, so never skip this.
|
|
65
|
+
7. Validate the rest by hand โ the doctor can't judge these:
|
|
61
66
|
- **Trigger clarity** โ could a model decide *not* to invoke this from the description alone? If so, tighten it.
|
|
62
67
|
- **Step concreteness** โ every step has a verb and an object.
|
|
63
68
|
- **Output specification** โ the caller knows what they get back.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# PAL Status Line - Windows PowerShell version
|
|
2
|
+
# Reads JSON from stdin (Claude Code session data) and prints a formatted status line
|
|
3
|
+
|
|
4
|
+
$data = $input | Out-String | ConvertFrom-Json
|
|
5
|
+
|
|
6
|
+
# Extract data with fallbacks (PowerShell 5.1 compatible)
|
|
7
|
+
$MODEL = if ($data.model.display_name) { $data.model.display_name } else { "Unknown" }
|
|
8
|
+
$USED_RAW = $data.context_window.used_percentage
|
|
9
|
+
$USED = if ($USED_RAW -ne $null) { [int]$USED_RAW } else { 0 }
|
|
10
|
+
$REM_RAW = $data.context_window.remaining_percentage
|
|
11
|
+
$REM = if ($REM_RAW -ne $null) { [int]$REM_RAW } else { 0 }
|
|
12
|
+
$COST_RAW = $data.cost.total_cost_usd
|
|
13
|
+
$COST = if ($COST_RAW -ne $null) { [double]$COST_RAW } else { 0 }
|
|
14
|
+
$CWD = if ($data.workspace.current_dir) { $data.workspace.current_dir } else { "~" }
|
|
15
|
+
$REPO = if ($data.workspace.repo.name) { $data.workspace.repo.name } else { "" }
|
|
16
|
+
|
|
17
|
+
# Extract git branch
|
|
18
|
+
$BRANCH = ""
|
|
19
|
+
try { $BRANCH = & git -C $CWD rev-parse --abbrev-ref HEAD 2>$null } catch { $BRANCH = "" }
|
|
20
|
+
|
|
21
|
+
# Format directory
|
|
22
|
+
$DIR_DISPLAY = if ($REPO) { $REPO } else { Split-Path -Leaf $CWD }
|
|
23
|
+
|
|
24
|
+
# Format git branch indicator
|
|
25
|
+
$GIT_INDICATOR = if ($BRANCH) { " (git: $BRANCH)" } else { "" }
|
|
26
|
+
|
|
27
|
+
# Format cost
|
|
28
|
+
$COST_STR = '$' + ([math]::Round($COST, 2)).ToString("0.00")
|
|
29
|
+
|
|
30
|
+
# PAL: Hook health - count ERROR lines in debug.log from last 24h
|
|
31
|
+
$HOOK_ERRORS = 0
|
|
32
|
+
$debugLog = Join-Path $env:USERPROFILE ".pal\memory\state\debug.log"
|
|
33
|
+
if (Test-Path $debugLog) {
|
|
34
|
+
$cutoff = (Get-Date).AddHours(-24)
|
|
35
|
+
try {
|
|
36
|
+
$HOOK_ERRORS = (Get-Content $debugLog -ErrorAction SilentlyContinue |
|
|
37
|
+
Where-Object { $_ -match '\] ERROR ' } |
|
|
38
|
+
Where-Object {
|
|
39
|
+
$m = [regex]::Match($_, '^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]')
|
|
40
|
+
if ($m.Success) { [datetime]$m.Groups[1].Value -gt $cutoff } else { $false }
|
|
41
|
+
}).Count
|
|
42
|
+
} catch { $HOOK_ERRORS = 0 }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# PAL: Open ISC count - find project matching current directory
|
|
46
|
+
$OPEN_ISCS = 0
|
|
47
|
+
$PROJECT_NAME = ""
|
|
48
|
+
$projectsDir = Join-Path $env:USERPROFILE ".pal\memory\projects"
|
|
49
|
+
if (Test-Path $projectsDir) {
|
|
50
|
+
try {
|
|
51
|
+
$CWD_NORM = $CWD.Replace('/', '\').TrimEnd('\')
|
|
52
|
+
foreach ($slug in Get-ChildItem $projectsDir -Directory | Select-Object -ExpandProperty Name) {
|
|
53
|
+
$isa = Join-Path $projectsDir "$slug\ISA.md"
|
|
54
|
+
if (-not (Test-Path $isa)) { continue }
|
|
55
|
+
$content = Get-Content $isa -Raw -ErrorAction SilentlyContinue
|
|
56
|
+
$pathMatch = [regex]::Match($content, 'path:\s*"?([^"\n\r]+)"?')
|
|
57
|
+
if ($pathMatch.Success) {
|
|
58
|
+
$projPath = $pathMatch.Groups[1].Value.Trim().Replace('/', '\').TrimEnd('\')
|
|
59
|
+
if ($projPath -eq $CWD_NORM -or $CWD_NORM.StartsWith($projPath)) {
|
|
60
|
+
$PROJECT_NAME = $slug
|
|
61
|
+
$OPEN_ISCS = ([regex]::Matches($content, '- \[ \] ISC-')).Count
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch { $OPEN_ISCS = 0 }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Rate limits (Pro/Max only - absent for other plans)
|
|
70
|
+
$FIVE_H_RAW = $data.rate_limits.five_hour.used_percentage
|
|
71
|
+
$SEVEN_D_RAW = $data.rate_limits.seven_day.used_percentage
|
|
72
|
+
$RATE_PARTS = @()
|
|
73
|
+
if ($FIVE_H_RAW -ne $null) { $RATE_PARTS += "5h: $([int]$FIVE_H_RAW)%" }
|
|
74
|
+
if ($SEVEN_D_RAW -ne $null) { $RATE_PARTS += "7d: $([int]$SEVEN_D_RAW)%" }
|
|
75
|
+
$RATE_STR = if ($RATE_PARTS.Count -gt 0) { " - " + ($RATE_PARTS -join " | ") } else { "" }
|
|
76
|
+
|
|
77
|
+
# Create context progress bar - if both are 0, data not yet available (pre-first API call)
|
|
78
|
+
$NO_DATA = ($USED_RAW -eq $null -and $REM_RAW -eq $null)
|
|
79
|
+
if ($NO_DATA) { $USED = 0; $REM = 100 }
|
|
80
|
+
$FILLED = [int]($USED / 5)
|
|
81
|
+
$EMPTY = 20 - $FILLED
|
|
82
|
+
$BAR = ("#" * $FILLED) + ("-" * $EMPTY)
|
|
83
|
+
|
|
84
|
+
# ANSI color codes (PowerShell 5.1 compatible - use concatenation to avoid array-index expansion)
|
|
85
|
+
$ESC = [char]27
|
|
86
|
+
$CYAN = $ESC + "[36m"
|
|
87
|
+
$GREEN = $ESC + "[32m"
|
|
88
|
+
$YELLOW = $ESC + "[33m"
|
|
89
|
+
$RED = $ESC + "[31m"
|
|
90
|
+
$DIM = $ESC + "[90m"
|
|
91
|
+
$RESET = $ESC + "[0m"
|
|
92
|
+
|
|
93
|
+
# Choose bar color based on context usage
|
|
94
|
+
$BAR_COLOR = if ($USED -gt 80) { $RED } elseif ($USED -gt 60) { $YELLOW } else { $GREEN }
|
|
95
|
+
|
|
96
|
+
# PAL: Signal trend (reads 10-min cache written by session intelligence)
|
|
97
|
+
$SIGNAL_STR = ""
|
|
98
|
+
$signalCache = Join-Path $env:USERPROFILE ".pal\memory\state\signal-cache.json"
|
|
99
|
+
if (Test-Path $signalCache) {
|
|
100
|
+
try {
|
|
101
|
+
$sc = Get-Content $signalCache -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json
|
|
102
|
+
if ($sc.today -ne $null) {
|
|
103
|
+
$ARROW = switch ($sc.trend) {
|
|
104
|
+
"up" { "^"; break }
|
|
105
|
+
"down" { "v"; break }
|
|
106
|
+
"stable" { "-"; break }
|
|
107
|
+
default { "?"; break }
|
|
108
|
+
}
|
|
109
|
+
$SIG_TODAY = ([math]::Round([double]$sc.today, 1)).ToString("0.0")
|
|
110
|
+
$SIG_COLOR = if ($sc.trend -eq "up") { $GREEN } elseif ($sc.trend -eq "down") { $RED } else { $DIM }
|
|
111
|
+
$SIGNAL_STR = " " + $SIG_COLOR + "sig: $SIG_TODAY/10 $ARROW" + $RESET
|
|
112
|
+
}
|
|
113
|
+
} catch {}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# PAL: Update available check (reads cached result, no network call)
|
|
117
|
+
# Emoji constructed at runtime to avoid PS5.1 UTF-8-without-BOM encoding issues
|
|
118
|
+
$UPDATE_LINE = ""
|
|
119
|
+
$updateCache = Join-Path $env:USERPROFILE ".pal\memory\state\update-available.json"
|
|
120
|
+
if (Test-Path $updateCache) {
|
|
121
|
+
try {
|
|
122
|
+
$uc = Get-Content $updateCache -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json
|
|
123
|
+
if ($uc.available -eq $true) {
|
|
124
|
+
$versionStr = if ($uc.current -ne $uc.latest) { "$($uc.current) -> $($uc.latest)" } else { $uc.current + " (new commits)" }
|
|
125
|
+
$UPDATE_LINE = "[update] $versionStr run: pal cli update"
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Build PAL indicators
|
|
131
|
+
$HOOK_STR = if ($HOOK_ERRORS -gt 0) { " " + $RED + "! $HOOK_ERRORS hook err" + $RESET } else { "" }
|
|
132
|
+
$ISC_STR = if ($OPEN_ISCS -gt 0) { " " + $DIM + "$OPEN_ISCS open ISCs" + $RESET } else { "" }
|
|
133
|
+
|
|
134
|
+
# Line 1: Model, Directory, Git Branch, Hook Health, Open ISCs
|
|
135
|
+
Write-Host ($CYAN + "[$MODEL]" + $RESET + " folder: $DIR_DISPLAY" + $DIM + $GIT_INDICATOR + $RESET + $HOOK_STR + $ISC_STR + $SIGNAL_STR)
|
|
136
|
+
|
|
137
|
+
# Line 2: Context bar with usage percentage, cost, and rate limits
|
|
138
|
+
$CTX_STR = "$USED% - $COST_STR - ${REM}% free"
|
|
139
|
+
Write-Host ($BAR_COLOR + $BAR + $RESET + " " + $CTX_STR + $DIM + $RATE_STR + $RESET)
|
|
140
|
+
|
|
141
|
+
# Line 3: Update available (only when cache says available AND versions differ)
|
|
142
|
+
if ($UPDATE_LINE) { Write-Host ($YELLOW + $UPDATE_LINE + $RESET) }
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PAL Status Line โ macOS/Linux
|
|
3
|
+
# Reads JSON from stdin (Claude Code session data) and prints a formatted status line
|
|
4
|
+
|
|
5
|
+
input=$(cat)
|
|
6
|
+
|
|
7
|
+
# Extract data with fallbacks
|
|
8
|
+
MODEL=$(echo "$input" | jq -r '.model.display_name // "Unknown"')
|
|
9
|
+
USED_RAW=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
|
10
|
+
REM_RAW=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty')
|
|
11
|
+
USED=$( [ -n "$USED_RAW" ] && echo "${USED_RAW%.*}" || echo 0 )
|
|
12
|
+
REMAINING=$( [ -n "$REM_RAW" ] && echo "${REM_RAW%.*}" || echo 0 )
|
|
13
|
+
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
|
|
14
|
+
CWD=$(echo "$input" | jq -r '.workspace.current_dir // "~"')
|
|
15
|
+
REPO=$(echo "$input" | jq -r '.workspace.repo.name // ""')
|
|
16
|
+
|
|
17
|
+
# No data yet (pre-first API call)
|
|
18
|
+
if [ -z "$USED_RAW" ] && [ -z "$REM_RAW" ]; then
|
|
19
|
+
USED=0; REMAINING=100
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Extract git branch โ try multiple sources for robustness
|
|
23
|
+
BRANCH=$(echo "$input" | jq -r '.worktree.branch // ""')
|
|
24
|
+
if [ -z "$BRANCH" ]; then
|
|
25
|
+
BRANCH=$(git -C "$CWD" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Format directory โ show repo name if available, else just folder name
|
|
29
|
+
if [ -n "$REPO" ]; then
|
|
30
|
+
DIR_DISPLAY="$REPO"
|
|
31
|
+
else
|
|
32
|
+
DIR_DISPLAY="${CWD##*/}"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Format git branch with emoji indicator
|
|
36
|
+
if [ -n "$BRANCH" ]; then
|
|
37
|
+
GIT_INDICATOR=" (๐ฟ ${BRANCH})"
|
|
38
|
+
else
|
|
39
|
+
GIT_INDICATOR=""
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Format cost โ show as $X.XX or 'free' if < $0.01
|
|
43
|
+
if (( $(echo "$COST < 0.01" | bc -l) )); then
|
|
44
|
+
COST_STR="free"
|
|
45
|
+
else
|
|
46
|
+
COST_STR=$(printf '$%.2f' "$COST")
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# PAL: Hook health โ count ERROR lines in debug.log from last 24h
|
|
50
|
+
HOOK_ERRORS=0
|
|
51
|
+
DEBUG_LOG="$HOME/.pal/memory/state/debug.log"
|
|
52
|
+
if [ -f "$DEBUG_LOG" ]; then
|
|
53
|
+
# Cross-platform 24h cutoff: macOS uses date -v, GNU Linux uses date -d
|
|
54
|
+
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)
|
|
55
|
+
if [ -n "$CUTOFF" ]; then
|
|
56
|
+
# Log format: [YYYY-MM-DD HH:MM:SS] LEVEL ... โ split on [ or ] to get $2 = timestamp
|
|
57
|
+
HOOK_ERRORS=$(grep '\] ERROR ' "$DEBUG_LOG" | awk -F'[][]]' '$2 > "'"$CUTOFF"'"' | wc -l | tr -d ' ')
|
|
58
|
+
else
|
|
59
|
+
HOOK_ERRORS=$(grep -c '\] ERROR ' "$DEBUG_LOG" 2>/dev/null || echo 0)
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# PAL: Open ISC count โ find project matching current directory
|
|
64
|
+
OPEN_ISCS=0
|
|
65
|
+
PROJECTS_DIR="$HOME/.pal/memory/projects"
|
|
66
|
+
if [ -d "$PROJECTS_DIR" ]; then
|
|
67
|
+
CWD_NORM="${CWD%/}"
|
|
68
|
+
for ISA in "$PROJECTS_DIR"/*/ISA.md; do
|
|
69
|
+
[ -f "$ISA" ] || continue
|
|
70
|
+
PROJ_PATH=$(grep -m1 'path:' "$ISA" | sed 's/.*path:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
71
|
+
PROJ_PATH="${PROJ_PATH%/}"
|
|
72
|
+
if [ "$PROJ_PATH" = "$CWD_NORM" ] || [[ "$CWD_NORM" == "${PROJ_PATH}/"* ]]; then
|
|
73
|
+
OPEN_ISCS=$(grep -c '- \[ \] ISC-' "$ISA" 2>/dev/null || echo 0)
|
|
74
|
+
break
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Rate limits (Pro/Max only โ absent for other plans)
|
|
80
|
+
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
|
81
|
+
SEVEN_D=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
|
82
|
+
RATE_PARTS=()
|
|
83
|
+
[ -n "$FIVE_H" ] && RATE_PARTS+=("5h: ${FIVE_H%.*}%")
|
|
84
|
+
[ -n "$SEVEN_D" ] && RATE_PARTS+=("7d: ${SEVEN_D%.*}%")
|
|
85
|
+
RATE_STR=""
|
|
86
|
+
if [ ${#RATE_PARTS[@]} -gt 0 ]; then
|
|
87
|
+
RATE_STR=" โ $(IFS=" | "; echo "${RATE_PARTS[*]}")"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Create context progress bar (20 chars wide)
|
|
91
|
+
FILLED=$((USED / 5))
|
|
92
|
+
EMPTY=$((20 - FILLED))
|
|
93
|
+
if [ "$FILLED" -le 0 ]; then
|
|
94
|
+
BAR="โโโโโโโโโโโโโโโโโโโโ"
|
|
95
|
+
elif [ "$FILLED" -ge 20 ]; then
|
|
96
|
+
BAR="โโโโโโโโโโโโโโโโโโโโ"
|
|
97
|
+
else
|
|
98
|
+
BAR=$(printf 'โ%.0s' $(seq 1 $FILLED))$(printf 'โ%.0s' $(seq 1 $EMPTY))
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Color codes (ANSI)
|
|
102
|
+
CYAN='\033[36m'
|
|
103
|
+
GREEN='\033[32m'
|
|
104
|
+
YELLOW='\033[33m'
|
|
105
|
+
RED='\033[31m'
|
|
106
|
+
DIM='\033[90m'
|
|
107
|
+
RESET='\033[0m'
|
|
108
|
+
|
|
109
|
+
# Choose bar color based on context usage
|
|
110
|
+
if [ "$USED" -gt 80 ]; then
|
|
111
|
+
CONTEXT_COLOR=$RED
|
|
112
|
+
elif [ "$USED" -gt 60 ]; then
|
|
113
|
+
CONTEXT_COLOR=$YELLOW
|
|
114
|
+
else
|
|
115
|
+
CONTEXT_COLOR=$GREEN
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# PAL: Signal trend (reads 10-min cache written by session intelligence)
|
|
119
|
+
SIGNAL_STR=""
|
|
120
|
+
SIGNAL_CACHE="$HOME/.pal/memory/state/signal-cache.json"
|
|
121
|
+
if [ -f "$SIGNAL_CACHE" ]; then
|
|
122
|
+
SC_TODAY=$(jq -r '.today // empty' "$SIGNAL_CACHE" 2>/dev/null)
|
|
123
|
+
SC_TREND=$(jq -r '.trend // empty' "$SIGNAL_CACHE" 2>/dev/null)
|
|
124
|
+
if [ -n "$SC_TODAY" ]; then
|
|
125
|
+
case "$SC_TREND" in
|
|
126
|
+
up) ARROW="โ"; SIG_COLOR=$GREEN ;;
|
|
127
|
+
down) ARROW="โ"; SIG_COLOR=$RED ;;
|
|
128
|
+
stable) ARROW="โ"; SIG_COLOR=$DIM ;;
|
|
129
|
+
*) ARROW="?"; SIG_COLOR=$DIM ;;
|
|
130
|
+
esac
|
|
131
|
+
SIG_TODAY=$(printf "%.1f" "$SC_TODAY")
|
|
132
|
+
SIGNAL_STR=" ${SIG_COLOR}sig: ${SIG_TODAY}/10 ${ARROW}${RESET}"
|
|
133
|
+
fi
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# PAL: Update available check (reads cached result, no network call)
|
|
137
|
+
UPDATE_LINE=""
|
|
138
|
+
UPDATE_CACHE="$HOME/.pal/memory/state/update-available.json"
|
|
139
|
+
if [ -f "$UPDATE_CACHE" ]; then
|
|
140
|
+
UC_AVAIL=$(jq -r '.available // false' "$UPDATE_CACHE" 2>/dev/null)
|
|
141
|
+
if [ "$UC_AVAIL" = "true" ]; then
|
|
142
|
+
UC_CURRENT=$(jq -r '.current // ""' "$UPDATE_CACHE" 2>/dev/null)
|
|
143
|
+
UC_LATEST=$(jq -r '.latest // ""' "$UPDATE_CACHE" 2>/dev/null)
|
|
144
|
+
if [ "$UC_CURRENT" != "$UC_LATEST" ]; then
|
|
145
|
+
VERSION_STR="$UC_CURRENT โ $UC_LATEST"
|
|
146
|
+
else
|
|
147
|
+
VERSION_STR="$UC_CURRENT (new commits)"
|
|
148
|
+
fi
|
|
149
|
+
UPDATE_LINE="๐ฆ update: $VERSION_STR run: pal cli update"
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# Build PAL indicators
|
|
154
|
+
HOOK_STR=""
|
|
155
|
+
[ "$HOOK_ERRORS" -gt 0 ] && HOOK_STR=" ${RED}โ ๏ธ ${HOOK_ERRORS} hook err${RESET}"
|
|
156
|
+
ISC_STR=""
|
|
157
|
+
[ "$OPEN_ISCS" -gt 0 ] && ISC_STR=" ${DIM}๐ ${OPEN_ISCS} open ISCs${RESET}"
|
|
158
|
+
|
|
159
|
+
# Line 1: Model, Directory, Git Branch, Hook Health, Open ISCs, Signal
|
|
160
|
+
echo -e "${CYAN}[${MODEL}]${RESET} ๐ ${DIR_DISPLAY}${DIM}${GIT_INDICATOR}${RESET}${HOOK_STR}${ISC_STR}${SIGNAL_STR}"
|
|
161
|
+
|
|
162
|
+
# Line 2: Context bar with usage percentage, cost, and rate limits
|
|
163
|
+
echo -e "${CONTEXT_COLOR}${BAR}${RESET} ${USED}% โ ${COST_STR} โ ${REMAINING}% free${DIM}${RATE_STR}${RESET}"
|
|
164
|
+
|
|
165
|
+
# Line 3: Update available (only when cache says available AND versions differ)
|
|
166
|
+
[ -n "$UPDATE_LINE" ] && echo -e "${YELLOW}${UPDATE_LINE}${RESET}"
|
|
167
|
+
exit 0
|
|
@@ -165,6 +165,18 @@ Refine criteria if the pressure test reveals gaps. Add criteria for uncovered fa
|
|
|
165
165
|
- Decide execution order โ what's serial, what can parallelize
|
|
166
166
|
- **Extended+:** use EnterPlanMode for user alignment before executing
|
|
167
167
|
|
|
168
|
+
**Grounding Gate โ verify the premises before EXECUTE.**
|
|
169
|
+
|
|
170
|
+
Don't just *list* what must be true โ actively confirm the load-bearing facts the criteria rest on. For each premise (a reported behavior, a provider/API contract, an external tool's CLI, a file's contents, a claim about system state):
|
|
171
|
+
- **Reproduce** the reported behavior before designing a fix โ no fix on an unreproduced bug.
|
|
172
|
+
- **Fetch the authoritative source** (live docs, the actual file, `gh api`) before asserting how something works.
|
|
173
|
+
- **Probe the dependency/contract** (tool availability, MCP account, env var, DB connectivity, exact CLI) before building on it.
|
|
174
|
+
- **Confirm one concrete artifact** (a commit, a shebang, a line of output) behind any external claim.
|
|
175
|
+
|
|
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
|
+
|
|
178
|
+
Output: `๐งญ GROUNDING: [premises verified, or assumptions flagged]`
|
|
179
|
+
|
|
168
180
|
### โโโ โก EXECUTE โโโ 3/5
|
|
169
181
|
|
|
170
182
|
Do the work. Invoke selected capabilities via tool calls.
|
|
@@ -217,9 +229,11 @@ Focus: reasoning approach, problem decomposition, anticipation, blind spots.
|
|
|
217
229
|
|
|
218
230
|
```bash
|
|
219
231
|
bun ~/.pal/tools/algorithm-reflect.ts --task "description" --criteria N --passed N --failed N --sentiment 1-10 \
|
|
220
|
-
--q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
|
|
232
|
+
--q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection" --scope general
|
|
221
233
|
```
|
|
222
234
|
|
|
235
|
+
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
|
+
|
|
223
237
|
**3. Relationship note** โ write one Session entry capturing what was done this session:
|
|
224
238
|
|
|
225
239
|
```bash
|
|
@@ -316,6 +330,7 @@ Only write if the insight is **genuine and reusable** โ not every session prod
|
|
|
316
330
|
โโโ ๐ง PLAN โโโ 2/5
|
|
317
331
|
๐ง RISKS: [risks]
|
|
318
332
|
๐ง PREMORTEM: [failure modes]
|
|
333
|
+
๐งญ GROUNDING: [premises verified, or assumptions flagged]
|
|
319
334
|
๐ APPROACH: [execution plan]
|
|
320
335
|
|
|
321
336
|
โโโ โก EXECUTE โโโ 3/5
|
|
@@ -25,6 +25,27 @@
|
|
|
25
25
|
"Bash(node --experimental-strip-types ~/.pal/skills/consulting-report/tools/generate-pdf.ts *)"
|
|
26
26
|
]
|
|
27
27
|
},
|
|
28
|
+
"skillOverrides": {
|
|
29
|
+
"claude-api": "off",
|
|
30
|
+
"update-config": "off",
|
|
31
|
+
"deep-research": "off",
|
|
32
|
+
"code-review": "off"
|
|
33
|
+
},
|
|
34
|
+
"attribution": {
|
|
35
|
+
"commit": "",
|
|
36
|
+
"pr": ""
|
|
37
|
+
},
|
|
38
|
+
"showClearContextOnPlanAccept": true,
|
|
39
|
+
"respectGitignore": true,
|
|
40
|
+
"spinnerTipsEnabled": true,
|
|
41
|
+
"spinnerTipsOverride": {
|
|
42
|
+
"tips": [
|
|
43
|
+
"Need something reusable? Use the /create-skill command.",
|
|
44
|
+
"PAL learns from your feedback and improves over time. Don't be shy to give feedback!",
|
|
45
|
+
"For simple PDFs use the /create-pdf skill. Need a styled one? Use /consulting-report instead.",
|
|
46
|
+
"Use the /research skill to run comprehensive web research with multiple parallel agents."
|
|
47
|
+
]
|
|
48
|
+
},
|
|
28
49
|
"hooks": {
|
|
29
50
|
"SessionStart": [
|
|
30
51
|
{
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* doctor Check prerequisites and system health
|
|
18
18
|
* usage Summarize token usage and cost
|
|
19
19
|
* skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
20
|
+
* skill doctor <name> Evaluate a skill against the authoring best practices
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import { spawnSync } from "node:child_process";
|
|
@@ -242,6 +243,7 @@ function showHelp() {
|
|
|
242
243
|
pal cli knowledge <sub> [args] Query & manage the knowledge store
|
|
243
244
|
(search ยท graph ยท stats ยท hubs ยท find ยท show ยท add ยท ls)
|
|
244
245
|
pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
246
|
+
pal cli skill doctor <name> Evaluate a skill against the authoring best practices
|
|
245
247
|
|
|
246
248
|
Environment:
|
|
247
249
|
PAL_HOME Override user state directory (default: ~/.pal or repo root)
|
package/src/cli/skill.ts
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pal cli skill โ manage personal skills under ~/.pal/skills/.
|
|
3
3
|
*
|
|
4
|
-
* pal cli skill link <name>
|
|
5
|
-
*
|
|
4
|
+
* pal cli skill link <name> Link an existing ~/.pal/skills/<name>/ into
|
|
5
|
+
* every installed agent so it is discoverable.
|
|
6
|
+
* pal cli skill doctor <name> Evaluate ~/.pal/skills/<name>/ against the
|
|
7
|
+
* skill-authoring best practices.
|
|
6
8
|
*/
|
|
7
9
|
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { palHome } from "../hooks/lib/paths";
|
|
8
12
|
import { linkPersonalSkill, log } from "../targets/lib";
|
|
13
|
+
import { formatReport, lintSkill } from "../tools/skill-doctor";
|
|
9
14
|
|
|
10
15
|
export async function runSkill(args: string[]): Promise<number> {
|
|
11
16
|
const [sub, name] = args;
|
|
12
17
|
|
|
18
|
+
if (sub === "doctor") {
|
|
19
|
+
if (!name) {
|
|
20
|
+
log.error("Usage: pal cli skill doctor <name>");
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
const report = lintSkill(resolve(palHome(), "skills", name));
|
|
24
|
+
console.log(formatReport(report));
|
|
25
|
+
return report.errors > 0 ? 1 : 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
if (sub === "link") {
|
|
14
29
|
if (!name) {
|
|
15
30
|
log.error("Usage: pal cli skill link <name>");
|
|
@@ -35,6 +50,6 @@ export async function runSkill(args: string[]): Promise<number> {
|
|
|
35
50
|
}
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
log.error("Usage: pal cli skill link <name>");
|
|
53
|
+
log.error("Usage: pal cli skill <link|doctor> <name>");
|
|
39
54
|
return 1;
|
|
40
55
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Algorithm-review cadence โ decides when to nudge the MAINTAINER to run an
|
|
3
|
+
* `/algorithm-update` session (fold accumulated Q2 reflections into ALGORITHM.md).
|
|
4
|
+
*
|
|
5
|
+
* Maintainer-only by construction: the nudge fires solely in a PAL repo checkout,
|
|
6
|
+
* detected by the presence of the repo-only `.agents/skills/algorithm-update`
|
|
7
|
+
* skill. That path never ships to downstream installs โ package.json `files`
|
|
8
|
+
* lists only `src/` and `assets/`, so `.agents/` is absent everywhere except a
|
|
9
|
+
* source checkout. Downstream users therefore never see this nudge.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { readReflections } from "./learning-store";
|
|
15
|
+
import { palPkg, paths } from "./paths";
|
|
16
|
+
|
|
17
|
+
const NUDGE_MIN_COUNT = 25;
|
|
18
|
+
const NUDGE_MIN_DAYS = 7;
|
|
19
|
+
const DAY_MS = 86_400_000;
|
|
20
|
+
|
|
21
|
+
function markFile(): string {
|
|
22
|
+
return resolve(paths.state(), "algorithm-review.json");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function readReviewMark(): string | null {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(readFileSync(markFile(), "utf-8"));
|
|
28
|
+
return typeof parsed.lastReviewedTs === "string" ? parsed.lastReviewedTs : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function writeReviewMark(ts: string): void {
|
|
35
|
+
writeFileSync(markFile(), `${JSON.stringify({ lastReviewedTs: ts }, null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** True only in a maintainer repo checkout โ the repo-only update skill is present. */
|
|
39
|
+
export function isMaintainerEnv(): boolean {
|
|
40
|
+
return existsSync(
|
|
41
|
+
resolve(palPkg(), ".agents", "skills", "algorithm-update", "SKILL.md")
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Count reflections newer than the last review mark (all of them, if never reviewed). */
|
|
46
|
+
export function countUnreviewed(mark: string | null = readReviewMark()): number {
|
|
47
|
+
const reflections = readReflections(paths.reflectionsFile());
|
|
48
|
+
if (!mark) return reflections.length;
|
|
49
|
+
const cutoff = new Date(mark).getTime();
|
|
50
|
+
return reflections.filter((r) => r.ts && new Date(r.ts).getTime() > cutoff).length;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ReviewNudge {
|
|
54
|
+
count: number;
|
|
55
|
+
sinceDays: number;
|
|
56
|
+
since: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Nudge data when BOTH thresholds are met (โฅ25 new AND โฅ7 days) in a maintainer
|
|
61
|
+
* env, else null. `now` is injected for testability.
|
|
62
|
+
*/
|
|
63
|
+
export function algorithmReviewNudge(now: Date): ReviewNudge | null {
|
|
64
|
+
if (!isMaintainerEnv()) return null;
|
|
65
|
+
const mark = readReviewMark();
|
|
66
|
+
const count = countUnreviewed(mark);
|
|
67
|
+
if (count < NUDGE_MIN_COUNT) return null;
|
|
68
|
+
const sinceDays = mark
|
|
69
|
+
? Math.floor((now.getTime() - new Date(mark).getTime()) / DAY_MS)
|
|
70
|
+
: Number.POSITIVE_INFINITY;
|
|
71
|
+
if (sinceDays < NUDGE_MIN_DAYS) return null;
|
|
72
|
+
return { count, sinceDays, since: mark };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Formatted nudge section for the session reminder, or "" when it shouldn't fire. */
|
|
76
|
+
export function loadAlgorithmReviewNudge(now: Date = new Date()): string {
|
|
77
|
+
const n = algorithmReviewNudge(now);
|
|
78
|
+
if (!n) return "";
|
|
79
|
+
const since = n.since ? new Date(n.since).toISOString().slice(0, 10) : "the start";
|
|
80
|
+
const age = Number.isFinite(n.sinceDays) ? `, ${n.sinceDays}d` : "";
|
|
81
|
+
return [
|
|
82
|
+
"## Algorithm Review Due",
|
|
83
|
+
`๐ง ${n.count} new algorithm reflections since ${since}${age} โ run \`/algorithm-update\` to fold the recurring ones into ALGORITHM.md.`,
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|