portable-agent-layer 0.63.3 → 0.65.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 +8 -4
- package/assets/schema/pal-settings.schema.json +4 -0
- package/assets/skills/analyze-pdf/SKILL.md +11 -0
- package/assets/skills/analyze-youtube/SKILL.md +12 -0
- package/assets/skills/consulting-report/SKILL.md +9 -0
- package/assets/skills/consulting-report/tools/generate-pdf.mjs +2 -2
- package/assets/skills/consulting-report/tools/generate-pdf.ts +5 -2
- package/assets/skills/council/SKILL.md +32 -0
- package/assets/skills/create-pdf/SKILL.md +13 -0
- package/assets/skills/create-skill/SKILL.md +14 -2
- package/assets/skills/create-skill/authoring-guide.md +10 -1
- package/assets/skills/create-subagent/SKILL.md +22 -4
- package/assets/skills/{research → deep-research}/SKILL.md +32 -1
- package/assets/skills/entities/SKILL.md +10 -0
- package/assets/skills/extract-wisdom/SKILL.md +12 -0
- package/assets/skills/first-principles/SKILL.md +8 -0
- package/assets/skills/frontend-design/SKILL.md +14 -0
- package/assets/skills/fyzz-chat-api/SKILL.md +10 -0
- package/assets/skills/humanize/SKILL.md +13 -1
- package/assets/skills/opinion/SKILL.md +11 -0
- package/assets/skills/pal-analyze/SKILL.md +11 -0
- package/assets/skills/pal-reflect/SKILL.md +10 -0
- package/assets/skills/playwright/SKILL.md +15 -2
- package/assets/skills/playwright/tools/shot.ts +6 -7
- package/assets/skills/presentation/SKILL.md +12 -0
- package/assets/skills/projects/SKILL.md +20 -1
- package/assets/skills/reflect/SKILL.md +13 -0
- package/assets/skills/telos/SKILL.md +12 -0
- package/assets/skills/think/SKILL.md +9 -0
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +3 -0
- package/assets/templates/pal-settings.json +1 -0
- package/assets/templates/settings.claude.json +2 -1
- package/package.json +15 -4
- package/src/cli/index.ts +95 -9
- package/src/cli/migrate.ts +69 -3
- package/src/cli/skill.ts +47 -3
- package/src/hooks/handlers/inject-retrieval.ts +20 -10
- package/src/hooks/lib/anchor.ts +90 -0
- package/src/hooks/lib/bindings.ts +117 -0
- package/src/hooks/lib/export.ts +38 -1
- package/src/hooks/lib/import-merge.ts +220 -0
- package/src/hooks/lib/inference.ts +113 -72
- package/src/hooks/lib/machine.ts +176 -0
- package/src/hooks/lib/projects.ts +223 -15
- package/src/hooks/lib/readme-sync.ts +30 -10
- package/src/hooks/lib/relationship.ts +3 -1
- package/src/hooks/lib/remote.ts +58 -0
- package/src/hooks/lib/retrieval.ts +8 -2
- package/src/hooks/lib/signals.ts +2 -1
- package/src/hooks/lib/skill-match.ts +129 -0
- package/src/hooks/lib/skill-triggers.ts +82 -0
- package/src/hooks/lib/stop.ts +5 -2
- package/src/targets/lib.ts +137 -35
- package/src/targets/opencode/plugin.ts +2 -6
- package/src/tools/agent/algorithm-reflect.ts +45 -11
- package/src/tools/agent/project.ts +148 -23
- package/src/tools/agent/thread.ts +7 -2
- package/src/tools/skill-doctor.ts +130 -5
- package/assets/skills/playwright/tools/shot-lib.mjs +0 -44
- package/assets/skills/playwright/tools/shot.mjs +0 -89
- package/assets/skills/review/SKILL.md +0 -20
- package/assets/skills/summarize/SKILL.md +0 -16
- /package/assets/skills/{research → deep-research}/tools/gemini-search.ts +0 -0
- /package/assets/skills/{research → deep-research}/tools/grok-search.ts +0 -0
- /package/assets/skills/{research → deep-research}/tools/perplexity-search.ts +0 -0
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: presentation
|
|
3
|
+
license: MIT
|
|
3
4
|
description: "Build branded HTML presentations from markdown using Reveal.js. Multi-template registry per user (each template = brand color, logo, fonts, footer, aspect). Per-deck workflow: scaffold → edit one markdown file per slide in slides/ → build → present. Output: a per-deck subdir with a self-contained HTML and a concatenated markdown sibling. 14 layouts including data-display patterns (big-stat, metric-grid). Use when creating slide decks, talks, workshop slides, lectures, or pitch decks."
|
|
4
5
|
argument-hint: <deck-dir> to build, OR `setup-template` to add a brand template, OR `new <deck-dir> --template <name>` to scaffold a deck, OR `list-templates`
|
|
6
|
+
metadata:
|
|
7
|
+
source: portable-agent-layer
|
|
8
|
+
triggers:
|
|
9
|
+
- "presentation"
|
|
10
|
+
- "slide deck"
|
|
11
|
+
- "slides"
|
|
12
|
+
- "pitch deck"
|
|
13
|
+
- "reveal.js"
|
|
14
|
+
- "workshop deck"
|
|
15
|
+
- "talk slides"
|
|
16
|
+
- "ppt"
|
|
5
17
|
---
|
|
6
18
|
|
|
7
19
|
## Overview
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: projects
|
|
3
|
+
license: MIT
|
|
3
4
|
description: "Project context management. PROACTIVE — use when the user references a project (by name or as \"this repo\", \"current work\"), asks to add/update/complete a project, says \"store under a named project\", \"track this\", \"what am I working on\", \"my projects\", \"my priorities\". Also triggered by ISC/ISA terminology — ISC stands for Ideal State Criteria, a project's verifiable done-conditions stored as `ISC-N:` lines in the Criteria section of its ISA.md spec. Phrases like \"ISC\", \"open/opening the ISC\", \"open ISCs\" (unfinished criteria), \"create/creating a ticket for a project\", \"new ticket\", or \"project ticket\" all map here: use `list-isc` to read them, `add-isc` to create one, `complete-isc`/`reopen-isc` to change status."
|
|
4
5
|
argument-hint: [list | create | resume | add-next | add-blocker | add-decision | add-handoff | update-section | criteria | isa-init | complete | archive | pause]
|
|
6
|
+
metadata:
|
|
7
|
+
source: portable-agent-layer
|
|
8
|
+
triggers:
|
|
9
|
+
- "projects"
|
|
10
|
+
- "isc"
|
|
11
|
+
- "isa"
|
|
12
|
+
- "my projects"
|
|
13
|
+
- "what am i working on"
|
|
14
|
+
- "my priorities"
|
|
15
|
+
- "track this"
|
|
16
|
+
- "resume the project"
|
|
17
|
+
- "resume project"
|
|
18
|
+
- "where are we"
|
|
19
|
+
- "open ticket"
|
|
20
|
+
- "open iscs"
|
|
21
|
+
- "file ticket"
|
|
22
|
+
- "file it as a ticket"
|
|
23
|
+
- "file a ticket"
|
|
5
24
|
---
|
|
6
25
|
|
|
7
26
|
Manage the user's project registry. Each project lives at `~/.pal/memory/projects/{slug}/ISA.md`. Frontmatter holds operational state (next steps, blockers, handoff); the body holds ISA spec sections (Problem, Goal, Criteria, Context, Decisions, etc.). The Stop hook auto-touches `updated` whenever the cwd resolves into a registered project — just *being* in the project keeps it warm.
|
|
@@ -48,7 +67,7 @@ The body of each ISA.md holds spec sections. Use `update-section` to set them:
|
|
|
48
67
|
| Decisions | `decisions` | Auto-managed by `add-decision`; dated bullet list |
|
|
49
68
|
| Changelog | `changelog` | Archive of completed ISCs (`complete-isc` moves them here under a dated `### Archived` heading) plus any milestone notes |
|
|
50
69
|
|
|
51
|
-
**ISC archive model.** `complete-isc` does not just check a box — it moves the ISC line out of Criteria and into the Changelog archive, so Criteria always reflects exactly the open work and never bloats the context loaded at session start. `list-isc <name>` shows open ISCs by default; pass `--closed` (archived only) or `--all` (open + archived) to read finished ones. `reopen-isc` pulls an archived ISC back into the open set. `prune-isc <name>` backfills legacy projects by sweeping any done ISCs still sitting in Criteria into the archive in one pass.
|
|
70
|
+
**ISC archive model.** `complete-isc` does not just check a box — it moves the ISC line out of Criteria and into the Changelog archive, so Criteria always reflects exactly the open work and never bloats the context loaded at session start. `list-isc <name>` shows open ISCs by default; pass `--closed` (archived only) or `--all` (open + archived) to read finished ones. `reopen-isc` pulls an archived or retired ISC back into the open set. `edit-isc <name> <id> "new text"` rewrites an ISC's wording in place, keeping its id and state — use it to sharpen a vague criterion instead of closing and refiling. `retire-isc <name> <id> [--by <id>]` closes an ISC that stopped being valid: it files under a `### Retired` heading as `[~]` rather than claiming the work was done, and `--by` records the superseding ISC. A retired ISC counts as neither open nor done, is listed with `--retired`, and keeps its id reserved so no future ISC can reuse it. `prune-isc <name>` backfills legacy projects by sweeping any done ISCs still sitting in Criteria into the archive in one pass.
|
|
52
71
|
|
|
53
72
|
## Routing
|
|
54
73
|
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reflect
|
|
3
|
+
license: MIT
|
|
3
4
|
description: "Diagnose why a PAL behavior did not trigger as expected — trace hooks, instructions, and logic to find the gap. Use when a hook, skill, or automation didn't fire or behaved unexpectedly."
|
|
4
5
|
argument-hint: <what went wrong>
|
|
6
|
+
metadata:
|
|
7
|
+
source: portable-agent-layer
|
|
8
|
+
triggers:
|
|
9
|
+
- "reflect"
|
|
10
|
+
- "didn't fire"
|
|
11
|
+
- "didn't trigger"
|
|
12
|
+
- "didn't run"
|
|
13
|
+
- "why didn't"
|
|
14
|
+
- "not triggering"
|
|
15
|
+
- "never fired"
|
|
16
|
+
- "should have fired"
|
|
17
|
+
- "behaved unexpectedly"
|
|
5
18
|
---
|
|
6
19
|
|
|
7
20
|
When the user invokes `/reflect [optional: description of what didn't happen]`:
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
name: telos
|
|
3
3
|
description: "Personal context management. Use when discussing goals, beliefs, challenges, identity, updating telos, life context, changing a goal, what do I believe, current obstacles, mission, or strategies."
|
|
4
4
|
argument-hint: [area to view or update]
|
|
5
|
+
metadata:
|
|
6
|
+
source: portable-agent-layer
|
|
7
|
+
derived-from: https://github.com/danielmiessler/LifeOS
|
|
8
|
+
triggers:
|
|
9
|
+
- "telos"
|
|
10
|
+
- "my goals"
|
|
11
|
+
- "my beliefs"
|
|
12
|
+
- "my mission"
|
|
13
|
+
- "life context"
|
|
14
|
+
- "current challenges"
|
|
15
|
+
- "what do i believe"
|
|
16
|
+
- "my strategies"
|
|
5
17
|
---
|
|
6
18
|
|
|
7
19
|
Manage the user's TELOS files — from Greek τέλος (télos), meaning end/purpose/goal. The persistent personal context that orients PAL around who the user is and what they're working toward.
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
name: think
|
|
3
3
|
description: "Thinking mode router — selects the right analytical approach for a question. Use when thinking through a problem, analyzing deeply, brainstorming ideas, debating options, decomposing to root cause, challenging assumptions, or exploring from multiple perspectives."
|
|
4
4
|
argument-hint: <question or topic>
|
|
5
|
+
metadata:
|
|
6
|
+
source: portable-agent-layer
|
|
7
|
+
derived-from: https://github.com/danielmiessler/LifeOS
|
|
8
|
+
triggers:
|
|
9
|
+
- "think"
|
|
10
|
+
- "brainstorm"
|
|
11
|
+
- "analyze deeply"
|
|
12
|
+
- "think about this"
|
|
13
|
+
- "mental model"
|
|
5
14
|
---
|
|
6
15
|
|
|
7
16
|
Route $ARGUMENTS to the right thinking mode based on intent. Detect the mode from context — do NOT ask.
|
|
@@ -213,6 +213,9 @@ Brief description.
|
|
|
213
213
|
|
|
214
214
|
┌─────────────────────┐
|
|
215
215
|
│ User Prompt Submit │──► UserPromptOrchestrator.ts
|
|
216
|
+
│ │ - Prompt context (one merged system-reminder):
|
|
217
|
+
│ │ contextual steering, skill trigger matches,
|
|
218
|
+
│ │ prior-lesson retrieval
|
|
216
219
|
│ │ - Rating capture (explicit/implicit)
|
|
217
220
|
│ │ - Session naming (first prompt)
|
|
218
221
|
└─────────────────────┘
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portable-agent-layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"LICENSE"
|
|
15
15
|
],
|
|
16
16
|
"engines": {
|
|
17
|
-
"bun": ">=1.
|
|
17
|
+
"bun": ">=1.4.0"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"ai",
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"type-check": "tsc --noEmit",
|
|
37
|
-
"test": "bun test",
|
|
37
|
+
"test": "bun test --randomize",
|
|
38
|
+
"test:mutate": "stryker run",
|
|
39
|
+
"test:mutate:diff": "bun .agents/scripts/mutate-diff.ts",
|
|
38
40
|
"format": "biome format",
|
|
39
41
|
"format-write": "biome format --write",
|
|
40
42
|
"lint": "biome lint",
|
|
@@ -44,6 +46,10 @@
|
|
|
44
46
|
"knip": "knip-bun",
|
|
45
47
|
"klint": "klint",
|
|
46
48
|
"jscpd": "jscpd --noTips",
|
|
49
|
+
"madge": "madge --circular --extensions ts --ts-config tsconfig.json src",
|
|
50
|
+
"lf": "bun .agents/scripts/check-lf.ts",
|
|
51
|
+
"lf:fix": "bun .agents/scripts/check-lf.ts --fix",
|
|
52
|
+
"secretlint": "secretlint \"**/*\"",
|
|
47
53
|
"lint-staged": "lint-staged",
|
|
48
54
|
"prepare": "bun .husky/install.mjs",
|
|
49
55
|
"build:skill-tools": "bun run scripts/build-skill-tools.ts",
|
|
@@ -66,11 +72,14 @@
|
|
|
66
72
|
"@biomejs/biome": "2.4.15",
|
|
67
73
|
"@commitlint/cli": "21.0.1",
|
|
68
74
|
"@commitlint/config-conventional": "21.0.1",
|
|
69
|
-
"@
|
|
75
|
+
"@hughescr/stryker-bun-runner": "1.3.8",
|
|
76
|
+
"@konvert7/klint": "0.34.0",
|
|
70
77
|
"@opencode-ai/plugin": "latest",
|
|
78
|
+
"@secretlint/secretlint-rule-preset-recommend": "13.0.2",
|
|
71
79
|
"@semantic-release/changelog": "^6.0.3",
|
|
72
80
|
"@semantic-release/git": "^10.0.1",
|
|
73
81
|
"@semantic-release/github": "^12.0.8",
|
|
82
|
+
"@stryker-mutator/core": "9.6.1",
|
|
74
83
|
"@types/adm-zip": "^0.5.8",
|
|
75
84
|
"@types/bun": "latest",
|
|
76
85
|
"@types/node": "latest",
|
|
@@ -78,7 +87,9 @@
|
|
|
78
87
|
"jscpd": "^4.2.3",
|
|
79
88
|
"knip": "^6.14.1",
|
|
80
89
|
"lint-staged": "17.0.5",
|
|
90
|
+
"madge": "8.0.0",
|
|
81
91
|
"promptfoo": "0.121.14",
|
|
92
|
+
"secretlint": "13.0.2",
|
|
82
93
|
"semantic-release": "^25.0.3",
|
|
83
94
|
"typescript": "^5.9.3"
|
|
84
95
|
},
|
package/src/cli/index.ts
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* uninstall [--claude] [--opencode] [--cursor] [--codex] Remove hooks/skills for targets
|
|
13
13
|
* update Update PAL (git pull or npm update)
|
|
14
14
|
* export [path] [--dry-run] Export user state to zip
|
|
15
|
-
* import [path] [--dry-run]
|
|
15
|
+
* import [path] [--dry-run] [--overwrite] Merge user state from zip
|
|
16
16
|
* status Show current PAL configuration
|
|
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>
|
|
20
|
+
* skill doctor <name|--all> Evaluate one skill, or every installed skill, against the authoring best practices
|
|
21
21
|
* subagent link <name> Install a personal ~/.pal/agents/<name>.md into installed agents
|
|
22
22
|
* subagent doctor <name> Evaluate a subagent against the authoring best practices
|
|
23
23
|
* debug [on|off] Enable / disable verbose hook debug logging
|
|
@@ -35,9 +35,17 @@ import {
|
|
|
35
35
|
} from "node:fs";
|
|
36
36
|
import { homedir } from "node:os";
|
|
37
37
|
import { resolve } from "node:path";
|
|
38
|
+
import {
|
|
39
|
+
appendImportLog,
|
|
40
|
+
mergeArchive,
|
|
41
|
+
readManifest,
|
|
42
|
+
summarize,
|
|
43
|
+
} from "../hooks/lib/import-merge";
|
|
38
44
|
import { inference, previewInferenceRoute } from "../hooks/lib/inference";
|
|
39
45
|
import { DEBUG_LOG_MAX_ROTATED, logDebug } from "../hooks/lib/log";
|
|
46
|
+
import { ensureRegistered, writeRegistryEntry } from "../hooks/lib/machine";
|
|
40
47
|
import { palHome, palPkg, paths, platform } from "../hooks/lib/paths";
|
|
48
|
+
import { auditBindings, describeBindingIssue } from "../hooks/lib/projects";
|
|
41
49
|
import { hasRealContent, SETUP_STEPS, STEP_ORDER } from "../hooks/lib/setup";
|
|
42
50
|
import { log } from "../targets/lib";
|
|
43
51
|
import { checkPendingMigrations } from "./migrate";
|
|
@@ -274,7 +282,7 @@ function showHelp() {
|
|
|
274
282
|
pal cli uninstall [--claude] [--opencode] [--cursor] [--codex] Remove hooks for targets
|
|
275
283
|
pal cli update Update PAL (git pull or npm update)
|
|
276
284
|
pal cli export [path] [--dry-run] Export state to zip
|
|
277
|
-
pal cli import [path] [--dry-run]
|
|
285
|
+
pal cli import [path] [--dry-run] Merge state from zip (--overwrite to replace)
|
|
278
286
|
pal cli status Show PAL configuration
|
|
279
287
|
pal cli doctor [--probe-inference] Check prerequisites and health (--probe fires real inference per route)
|
|
280
288
|
pal cli migrate [--list] [--dry-run] Run pending data migrations
|
|
@@ -283,7 +291,7 @@ function showHelp() {
|
|
|
283
291
|
pal cli knowledge <sub> [args] Query & manage the knowledge store
|
|
284
292
|
(search · graph · stats · hubs · find · show · add · ls)
|
|
285
293
|
pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
286
|
-
pal cli skill doctor <name>
|
|
294
|
+
pal cli skill doctor <name|--all> Evaluate one skill, or every installed skill
|
|
287
295
|
pal cli skill author-model Print the flagship model that authors skills for the active agent
|
|
288
296
|
pal cli subagent link <name> Install a personal ~/.pal/agents/<name>.md into installed agents
|
|
289
297
|
pal cli subagent doctor <name> Evaluate a subagent against the authoring best practices
|
|
@@ -845,6 +853,16 @@ function doctor(silent = false): DoctorResult {
|
|
|
845
853
|
);
|
|
846
854
|
}
|
|
847
855
|
|
|
856
|
+
// Project bindings — where each project lives on THIS machine
|
|
857
|
+
{
|
|
858
|
+
const issues = auditBindings();
|
|
859
|
+
if (issues.length === 0) {
|
|
860
|
+
ok("Project bindings healthy");
|
|
861
|
+
} else {
|
|
862
|
+
for (const issue of issues) warn(`Binding: ${describeBindingIssue(issue)}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
848
866
|
// Dependencies (PAL's own npm packages)
|
|
849
867
|
const nodeModulesPath = resolve(palPkg(), "node_modules");
|
|
850
868
|
existsSync(nodeModulesPath)
|
|
@@ -1293,6 +1311,7 @@ async function importState(args: string[]) {
|
|
|
1293
1311
|
|
|
1294
1312
|
const home = palHome();
|
|
1295
1313
|
const dryRun = args.includes("--dry-run");
|
|
1314
|
+
const overwrite = args.includes("--overwrite");
|
|
1296
1315
|
const pathArg = args.find((a) => !a.startsWith("-"));
|
|
1297
1316
|
logDebug("import", `start dryRun=${dryRun} pathArg=${pathArg ?? "(auto)"}`);
|
|
1298
1317
|
|
|
@@ -1369,16 +1388,83 @@ async function importState(args: string[]) {
|
|
|
1369
1388
|
process.exit(0);
|
|
1370
1389
|
}
|
|
1371
1390
|
|
|
1372
|
-
logDebug(
|
|
1391
|
+
logDebug(
|
|
1392
|
+
"import",
|
|
1393
|
+
`zip=${zipPath} entries=${entries.length} dryRun=${dryRun} overwrite=${overwrite}`
|
|
1394
|
+
);
|
|
1373
1395
|
if (dryRun) {
|
|
1374
|
-
console.log(
|
|
1396
|
+
console.log(
|
|
1397
|
+
`Would ${overwrite ? "overwrite with" : "merge"} ${entries.length} files → ${home}\n`
|
|
1398
|
+
);
|
|
1375
1399
|
for (const e of entries) console.log(` ${e.entryName}`);
|
|
1376
|
-
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (overwrite) {
|
|
1377
1404
|
zip.extractAllTo(home, true);
|
|
1378
|
-
logDebug("import", `done
|
|
1379
|
-
console.log(`Imported ${entries.length} files → ${home}`);
|
|
1405
|
+
logDebug("import", `done overwrote=${entries.length} to=${home}`);
|
|
1406
|
+
console.log(`Imported ${entries.length} files → ${home} (overwrite)`);
|
|
1407
|
+
appendImportLog(home, {
|
|
1408
|
+
ts: new Date().toISOString(),
|
|
1409
|
+
archive: zipPath,
|
|
1410
|
+
mode: "overwrite",
|
|
1411
|
+
created: 0,
|
|
1412
|
+
merged: 0,
|
|
1413
|
+
identical: 0,
|
|
1414
|
+
conflicts: 0,
|
|
1415
|
+
skipped: 0,
|
|
1416
|
+
linesAdded: 0,
|
|
1417
|
+
quarantineDir: null,
|
|
1418
|
+
});
|
|
1380
1419
|
log.info("Run 'pal cli install' to re-register hooks.");
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
ensureRegistered(home);
|
|
1424
|
+
const quarantineDir = resolve(
|
|
1425
|
+
home,
|
|
1426
|
+
"backups",
|
|
1427
|
+
`import-conflicts-${new Date()
|
|
1428
|
+
.toISOString()
|
|
1429
|
+
.replace(/[-:T.]/g, "")
|
|
1430
|
+
.slice(0, 14)}`
|
|
1431
|
+
);
|
|
1432
|
+
const result = mergeArchive(
|
|
1433
|
+
entries.map((e) => ({ path: e.entryName, data: () => e.getData() })),
|
|
1434
|
+
home,
|
|
1435
|
+
quarantineDir
|
|
1436
|
+
);
|
|
1437
|
+
const source = readManifest(
|
|
1438
|
+
entries.map((e) => ({ path: e.entryName, data: () => e.getData() }))
|
|
1439
|
+
);
|
|
1440
|
+
if (source) {
|
|
1441
|
+
writeRegistryEntry({ id: source.machineId, label: source.label, os: source.os });
|
|
1442
|
+
console.log(`Source machine: ${source.label} (${source.machineId})`);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
appendImportLog(home, {
|
|
1446
|
+
ts: new Date().toISOString(),
|
|
1447
|
+
archive: zipPath,
|
|
1448
|
+
mode: "merge",
|
|
1449
|
+
created: result.created.length,
|
|
1450
|
+
merged: result.merged.length,
|
|
1451
|
+
identical: result.identical.length,
|
|
1452
|
+
conflicts: result.conflicts.length,
|
|
1453
|
+
skipped: result.skipped.length,
|
|
1454
|
+
linesAdded: result.linesAdded,
|
|
1455
|
+
quarantineDir: result.quarantineDir,
|
|
1456
|
+
sourceMachineId: source?.machineId ?? null,
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
logDebug("import", `done merge ${summarize(result)} to=${home}`);
|
|
1460
|
+
console.log(`Imported → ${home}: ${summarize(result)}`);
|
|
1461
|
+
if (result.conflicts.length > 0) {
|
|
1462
|
+
log.warn(
|
|
1463
|
+
`${result.conflicts.length} file(s) diverged — local kept, incoming saved to ${result.quarantineDir}`
|
|
1464
|
+
);
|
|
1465
|
+
for (const c of result.conflicts) console.log(` ${c}`);
|
|
1381
1466
|
}
|
|
1467
|
+
log.info("Run 'pal cli install' to re-register hooks.");
|
|
1382
1468
|
}
|
|
1383
1469
|
|
|
1384
1470
|
async function update() {
|
package/src/cli/migrate.ts
CHANGED
|
@@ -105,7 +105,7 @@ const v1Projects: Migration = {
|
|
|
105
105
|
function nextIscId(criteria: string): number {
|
|
106
106
|
const ids: number[] = [];
|
|
107
107
|
for (const line of criteria.split("\n")) {
|
|
108
|
-
const m = new RegExp(/^-\s+\[[ x]\]\s+ISC-(\d+):/i).exec(line);
|
|
108
|
+
const m = new RegExp(/^-\s+\[[ x~]\]\s+ISC-(\d+):/i).exec(line);
|
|
109
109
|
if (m) ids.push(Number(m[1]));
|
|
110
110
|
}
|
|
111
111
|
return ids.length > 0 ? Math.max(...ids) + 1 : 1;
|
|
@@ -117,7 +117,9 @@ function pendingThreadsForProjects(): { thread: Thread; project: ProjectProgress
|
|
|
117
117
|
const projects = readAllProjects();
|
|
118
118
|
const results: { thread: Thread; project: ProjectProgress }[] = [];
|
|
119
119
|
for (const thread of threads) {
|
|
120
|
-
const project = projects.find(
|
|
120
|
+
const project = projects.find(
|
|
121
|
+
(p) => p.path !== undefined && resolve(p.path) === resolve(thread.cwd)
|
|
122
|
+
);
|
|
121
123
|
if (project) results.push({ thread, project });
|
|
122
124
|
}
|
|
123
125
|
return results;
|
|
@@ -362,7 +364,71 @@ const v3EntitiesToKnowledge: Migration = {
|
|
|
362
364
|
|
|
363
365
|
// ── Registry ──────────────────────────────────────────────────────
|
|
364
366
|
|
|
365
|
-
|
|
367
|
+
// ── v4: project paths out of records, into machine-local bindings ──
|
|
368
|
+
|
|
369
|
+
/** Records written before bindings existed carry their own `path:` in frontmatter. */
|
|
370
|
+
function recordsCarryingAPath(): string[] {
|
|
371
|
+
const base = paths.projectHistory();
|
|
372
|
+
if (!existsSync(base)) return [];
|
|
373
|
+
const out: string[] = [];
|
|
374
|
+
for (const slug of readdirSync(base)) {
|
|
375
|
+
const file = resolve(base, slug, "ISA.md");
|
|
376
|
+
if (!existsSync(file)) continue;
|
|
377
|
+
const frontmatter = readFileSync(file, "utf-8").split("---")[1] ?? "";
|
|
378
|
+
if (/^path:/m.test(frontmatter)) out.push(slug);
|
|
379
|
+
}
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const v4PathsToBindings: Migration = {
|
|
384
|
+
id: "v4-paths-to-bindings",
|
|
385
|
+
description: "Move project paths out of records into machine-local bindings",
|
|
386
|
+
|
|
387
|
+
check() {
|
|
388
|
+
const stale = recordsCarryingAPath();
|
|
389
|
+
return {
|
|
390
|
+
pending: stale.length > 0,
|
|
391
|
+
detail:
|
|
392
|
+
stale.length > 0 ? `${stale.length} record(s) still store a path` : undefined,
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
// Reading a record and writing it straight back is the whole migration: the
|
|
397
|
+
// writer already routes the path to a binding, drops the field, and picks up
|
|
398
|
+
// the git remote. Nothing here reimplements that, so the two cannot drift.
|
|
399
|
+
run(dryRun = false): MigrationResult {
|
|
400
|
+
const stale = recordsCarryingAPath();
|
|
401
|
+
const results: string[] = [];
|
|
402
|
+
let migrated = 0;
|
|
403
|
+
let skipped = 0;
|
|
404
|
+
|
|
405
|
+
for (const slug of stale) {
|
|
406
|
+
const project = readProject(slug);
|
|
407
|
+
if (!project) {
|
|
408
|
+
skipped++;
|
|
409
|
+
results.push(`${slug}: unreadable, left untouched`);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (dryRun) {
|
|
413
|
+
migrated++;
|
|
414
|
+
results.push(`${slug}: would move ${project.path ?? "(no path)"} to a binding`);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
writeProject(project);
|
|
418
|
+
migrated++;
|
|
419
|
+
results.push(`${slug}: path moved to a binding`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return { migrated, skipped, results };
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const MIGRATIONS: Migration[] = [
|
|
427
|
+
v1Projects,
|
|
428
|
+
v2ThreadsToIsc,
|
|
429
|
+
v3EntitiesToKnowledge,
|
|
430
|
+
v4PathsToBindings,
|
|
431
|
+
];
|
|
366
432
|
|
|
367
433
|
// ── Public API ────────────────────────────────────────────────────
|
|
368
434
|
|
package/src/cli/skill.ts
CHANGED
|
@@ -5,16 +5,59 @@
|
|
|
5
5
|
* every installed agent so it is discoverable.
|
|
6
6
|
* pal cli skill doctor <name> Evaluate ~/.pal/skills/<name>/ against the
|
|
7
7
|
* skill-authoring best practices.
|
|
8
|
+
* pal cli skill doctor --all Evaluate every installed skill, one line each.
|
|
8
9
|
* pal cli skill author-model Print the flagship model configured to author
|
|
9
10
|
* skills for the active agent (empty if none).
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
12
14
|
import { resolve } from "node:path";
|
|
13
15
|
import { getActiveAgent } from "../hooks/lib/agent";
|
|
14
16
|
import { flagshipAuthorModel } from "../hooks/lib/models";
|
|
15
17
|
import { palHome } from "../hooks/lib/paths";
|
|
16
18
|
import { linkPersonalSkill, log } from "../targets/lib";
|
|
17
|
-
import { formatReport, lintSkill } from "../tools/skill-doctor";
|
|
19
|
+
import { formatReport, formatSummary, lintSkill } from "../tools/skill-doctor";
|
|
20
|
+
|
|
21
|
+
/** Entry names under ~/.pal/skills/, sorted; dangling links included. */
|
|
22
|
+
function skillEntries(dir: string): string[] {
|
|
23
|
+
if (!existsSync(dir)) return [];
|
|
24
|
+
return readdirSync(dir).sort();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A listed entry that does not exist can only be a symlink whose target is gone. */
|
|
28
|
+
function isDanglingLink(path: string): boolean {
|
|
29
|
+
return !existsSync(path);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Lint every installed skill, one summary line each. Exits 1 if any has errors. */
|
|
33
|
+
function doctorAll(): number {
|
|
34
|
+
const dir = resolve(palHome(), "skills");
|
|
35
|
+
const entries = skillEntries(dir);
|
|
36
|
+
|
|
37
|
+
for (const name of entries.filter((n) => isDanglingLink(resolve(dir, n)))) {
|
|
38
|
+
log.warn(`Skipped ${name}: link target is gone — run 'pal cli install' to prune it`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const names = entries.filter((n) => {
|
|
42
|
+
const path = resolve(dir, n);
|
|
43
|
+
return !isDanglingLink(path) && statSync(path).isDirectory();
|
|
44
|
+
});
|
|
45
|
+
if (names.length === 0) {
|
|
46
|
+
log.warn(`No skills found in ${dir}`);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const reports = names.map((n) => lintSkill(resolve(dir, n)));
|
|
51
|
+
for (const report of reports) console.log(formatSummary(report));
|
|
52
|
+
|
|
53
|
+
const failing = reports.filter((r) => r.errors > 0).length;
|
|
54
|
+
const warning = reports.filter((r) => r.errors === 0 && r.warnings > 0).length;
|
|
55
|
+
const clean = reports.length - failing - warning;
|
|
56
|
+
console.log(
|
|
57
|
+
`\n${reports.length} skills — ${failing} failing, ${warning} with warnings, ${clean} clean`
|
|
58
|
+
);
|
|
59
|
+
return failing > 0 ? 1 : 0;
|
|
60
|
+
}
|
|
18
61
|
|
|
19
62
|
export async function runSkill(args: string[]): Promise<number> {
|
|
20
63
|
const [sub, name] = args;
|
|
@@ -26,8 +69,9 @@ export async function runSkill(args: string[]): Promise<number> {
|
|
|
26
69
|
}
|
|
27
70
|
|
|
28
71
|
if (sub === "doctor") {
|
|
72
|
+
if (name === "--all") return doctorAll();
|
|
29
73
|
if (!name) {
|
|
30
|
-
log.error("Usage: pal cli skill doctor <name>");
|
|
74
|
+
log.error("Usage: pal cli skill doctor <name|--all>");
|
|
31
75
|
return 1;
|
|
32
76
|
}
|
|
33
77
|
const report = lintSkill(resolve(palHome(), "skills", name));
|
|
@@ -60,6 +104,6 @@ export async function runSkill(args: string[]): Promise<number> {
|
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
|
|
63
|
-
log.error("Usage: pal cli skill <link|doctor|author-model> [name]");
|
|
107
|
+
log.error("Usage: pal cli skill <link|doctor|author-model> [name|--all]");
|
|
64
108
|
return 1;
|
|
65
109
|
}
|
|
@@ -12,6 +12,7 @@ import { logDebug, logError } from "../lib/log";
|
|
|
12
12
|
import { runRetrieval } from "../lib/retrieval";
|
|
13
13
|
import { ensureIndex } from "../lib/retrieval-index";
|
|
14
14
|
import { isEnabled } from "../lib/settings";
|
|
15
|
+
import { getSkillReminder } from "../lib/skill-match";
|
|
15
16
|
import { getSteeringReminder } from "../lib/steering";
|
|
16
17
|
|
|
17
18
|
const BUDGET_MS = 250;
|
|
@@ -35,7 +36,8 @@ export function withinBudget<T>(work: () => T, ms: number): T | null {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
/** Returns the retrieval reminder string, or null if nothing to inject.
|
|
39
|
+
/** Returns the retrieval reminder string, or null if nothing to inject.
|
|
40
|
+
* @lintignore exercised directly by test/inject-retrieval.test.ts */
|
|
39
41
|
export async function getRetrievalReminder(prompt: string): Promise<string | null> {
|
|
40
42
|
if (!prompt?.trim()) return null;
|
|
41
43
|
if (!isEnabled("learningInjection")) return null;
|
|
@@ -77,15 +79,23 @@ function writeForAgent(reminder: string): void {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
82
|
+
/** Merge every prompt-time source — contextual steering, skill matches, prior-lesson
|
|
83
|
+
* retrieval — into one payload, or null when none of them produced anything.
|
|
84
|
+
* @lintignore dynamically imported by opencode plugin */
|
|
85
|
+
export async function getPromptContext(prompt: string): Promise<string | null> {
|
|
86
|
+
const parts = [
|
|
87
|
+
getSteeringReminder(prompt),
|
|
88
|
+
getSkillReminder(prompt),
|
|
89
|
+
await getRetrievalReminder(prompt),
|
|
90
|
+
].filter((p): p is string => Boolean(p));
|
|
91
|
+
|
|
92
|
+
return parts.length > 0 ? parts.join("\n\n") : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Gather all prompt-time context and do the one per-agent write. Returns the
|
|
96
|
+
* combined reminder that was injected, or null if there was nothing to inject. */
|
|
83
97
|
export async function injectPromptContext(prompt: string): Promise<string | null> {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const parts = [steering, retrieval].filter((p): p is string => Boolean(p));
|
|
87
|
-
if (parts.length === 0) return null;
|
|
88
|
-
const combined = parts.join("\n\n");
|
|
89
|
-
writeForAgent(combined);
|
|
98
|
+
const combined = await getPromptContext(prompt);
|
|
99
|
+
if (combined) writeForAgent(combined);
|
|
90
100
|
return combined;
|
|
91
101
|
}
|