portable-agent-layer 0.36.0 → 0.37.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/tools/pdf-download.ts +1 -1
- package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +1 -1
- package/assets/skills/consulting-report/tools/dev.ts +2 -2
- package/assets/skills/consulting-report/tools/generate-pdf.ts +9 -9
- package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
- package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +2 -2
- package/assets/skills/opinion/tools/opinion.ts +3 -2
- package/assets/skills/presentation/SKILL.md +1 -1
- package/assets/skills/presentation/tools/doctor.ts +2 -5
- package/assets/skills/presentation/tools/lib/inline.ts +6 -11
- package/assets/skills/presentation/tools/lib/lint-helpers.ts +2 -2
- package/assets/skills/presentation/tools/lib/lint-rules.ts +5 -2
- package/assets/skills/presentation/tools/setup-template.ts +10 -7
- package/assets/skills/projects/SKILL.md +44 -20
- package/assets/skills/research/tools/gemini-search.ts +2 -2
- package/assets/skills/research/tools/grok-search.ts +2 -2
- package/assets/skills/research/tools/perplexity-search.ts +2 -2
- package/assets/skills/telos/tools/update-telos.ts +0 -1
- package/assets/templates/PAL/ALGORITHM.md +27 -3
- package/assets/templates/hooks.codex.json +44 -0
- package/assets/templates/hooks.cursor.json +11 -5
- package/package.json +2 -1
- package/src/cli/index.ts +112 -14
- package/src/cli/migrate.ts +299 -0
- package/src/cli/setup-identity.ts +3 -3
- package/src/cli/setup-telos.ts +0 -1
- package/src/hooks/CompactRecover.ts +11 -5
- package/src/hooks/LoadContext.ts +14 -2
- package/src/hooks/PreCompactPersist.ts +26 -34
- package/src/hooks/SecurityValidator.ts +43 -21
- package/src/hooks/StopOrchestrator.ts +4 -1
- package/src/hooks/UserPromptOrchestrator.ts +4 -2
- package/src/hooks/handlers/auto-graduate.ts +2 -2
- package/src/hooks/handlers/backup.ts +3 -3
- package/src/hooks/handlers/failure.ts +5 -3
- package/src/hooks/handlers/inject-retrieval.ts +29 -6
- package/src/hooks/handlers/persist-last-exchange.ts +76 -0
- package/src/hooks/handlers/rating.ts +2 -1
- package/src/hooks/handlers/readme-sync.ts +3 -2
- package/src/hooks/handlers/session-intelligence.ts +9 -8
- package/src/hooks/handlers/session-name.ts +2 -2
- package/src/hooks/handlers/synthesis.ts +5 -2
- package/src/hooks/handlers/update-counts.ts +3 -2
- package/src/hooks/lib/agent.ts +20 -18
- package/src/hooks/lib/context.ts +45 -117
- package/src/hooks/lib/entities.ts +7 -7
- package/src/hooks/lib/frontmatter.ts +4 -4
- package/src/hooks/lib/graduation.ts +7 -6
- package/src/hooks/lib/inference.ts +6 -2
- package/src/hooks/lib/learning-category.ts +1 -1
- package/src/hooks/lib/learning-store.ts +6 -1
- package/src/hooks/lib/notify.ts +2 -2
- package/src/hooks/lib/opinions.ts +3 -3
- package/src/hooks/lib/paths.ts +2 -0
- package/src/hooks/lib/projects.ts +142 -74
- package/src/hooks/lib/readme-sync.ts +1 -1
- package/src/hooks/lib/relationship.ts +3 -15
- package/src/hooks/lib/retrieval-index.ts +5 -3
- package/src/hooks/lib/retrieval.ts +11 -12
- package/src/hooks/lib/security.ts +22 -18
- package/src/hooks/lib/semi-static.ts +4 -2
- package/src/hooks/lib/session-names.ts +1 -1
- package/src/hooks/lib/settings.ts +1 -1
- package/src/hooks/lib/setup.ts +2 -60
- package/src/hooks/lib/signals.ts +2 -2
- package/src/hooks/lib/stdin.ts +1 -1
- package/src/hooks/lib/stop.ts +13 -6
- package/src/hooks/lib/token-usage.ts +1 -2
- package/src/hooks/lib/transcript.ts +1 -1
- package/src/hooks/lib/wisdom.ts +5 -5
- package/src/hooks/lib/work-tracking.ts +8 -14
- package/src/targets/codex/install.ts +95 -0
- package/src/targets/codex/uninstall.ts +70 -0
- package/src/targets/lib.ts +140 -14
- package/src/targets/opencode/plugin.ts +22 -11
- package/src/tools/agent/algorithm-reflect.ts +1 -1
- package/src/tools/agent/analyze.ts +18 -18
- package/src/tools/agent/handoff-note.ts +1 -1
- package/src/tools/agent/project.ts +375 -75
- package/src/tools/agent/synthesize.ts +6 -42
- package/src/tools/agent/thread.ts +15 -14
- package/src/tools/agent/wisdom-frame.ts +9 -3
- package/src/tools/import.ts +1 -1
- package/src/tools/relationship-reflect.ts +13 -11
- package/src/tools/self-model.ts +20 -16
- package/src/tools/session-summary.ts +3 -3
- package/src/tools/token-cost.ts +15 -16
- package/assets/skills/telos/tools/update-projects.ts +0 -106
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ pal cli status # check your setup
|
|
|
82
82
|
| `pal cli import` | Import user state from a zip |
|
|
83
83
|
| `pal cli status` | Show current PAL configuration |
|
|
84
84
|
| `pal cli doctor` | Check prerequisites and system health |
|
|
85
|
+
| `pal cli migrate` | Run pending data migrations (non-destructive) |
|
|
85
86
|
| `pal cli usage` | Summarize token usage and estimated cost |
|
|
86
87
|
|
|
87
88
|
### Target flags
|
|
@@ -19,7 +19,7 @@ async function exists(p: string): Promise<boolean> {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
async function dev(reportDir: string): Promise<number> {
|
|
23
23
|
const dir = resolve(reportDir);
|
|
24
24
|
const pkg = join(dir, "package.json");
|
|
25
25
|
if (!(await exists(pkg))) {
|
|
@@ -33,7 +33,7 @@ export async function dev(reportDir: string): Promise<number> {
|
|
|
33
33
|
return result.status ?? 1;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
async function run(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
37
37
|
if (argv.length === 0) {
|
|
38
38
|
console.error("usage: dev.ts <report-dir>");
|
|
39
39
|
process.exit(1);
|
|
@@ -43,10 +43,10 @@ async function exists(p: string): Promise<boolean> {
|
|
|
43
43
|
|
|
44
44
|
function escapeHtml(s: string): string {
|
|
45
45
|
return s
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
46
|
+
.replaceAll("&", "&")
|
|
47
|
+
.replaceAll("<", "<")
|
|
48
|
+
.replaceAll(">", ">")
|
|
49
|
+
.replaceAll('"', """);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function slugify(s: string): string {
|
|
@@ -56,7 +56,7 @@ function slugify(s: string): string {
|
|
|
56
56
|
.replace(/^-|-$/g, "");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
async function loadMeta(reportDir: string): Promise<ReportMeta> {
|
|
60
60
|
const dataPath = join(reportDir, "lib", "report-data.ts");
|
|
61
61
|
if (!(await exists(dataPath))) {
|
|
62
62
|
throw new Error(`lib/report-data.ts not found at ${dataPath}`);
|
|
@@ -70,7 +70,7 @@ export async function loadMeta(reportDir: string): Promise<ReportMeta> {
|
|
|
70
70
|
return mod.reportData;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
function buildNext(reportDir: string): void {
|
|
74
74
|
const result = spawnSync("bun", ["run", "build"], {
|
|
75
75
|
cwd: reportDir,
|
|
76
76
|
stdio: "inherit",
|
|
@@ -121,7 +121,7 @@ function serveStatic(rootDir: string): Promise<{ server: Server; url: string }>
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
async function renderPdf(
|
|
125
125
|
htmlPath: string,
|
|
126
126
|
pdfPath: string,
|
|
127
127
|
meta: ReportMeta
|
|
@@ -186,7 +186,7 @@ interface GenerateOptions {
|
|
|
186
186
|
skipBuild?: boolean;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
async function generate(opts: GenerateOptions): Promise<{
|
|
190
190
|
htmlPath: string;
|
|
191
191
|
pdfPath: string;
|
|
192
192
|
}> {
|
|
@@ -227,7 +227,7 @@ function parseArgs(argv: string[]): GenerateOptions {
|
|
|
227
227
|
return opts;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
async function run(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
231
231
|
const opts = parseArgs(argv);
|
|
232
232
|
const { htmlPath, pdfPath } = await generate(opts);
|
|
233
233
|
const [htmlStat, pdfStat] = await Promise.all([stat(htmlPath), stat(pdfPath)]);
|
|
@@ -34,7 +34,7 @@ function templateDir(): string {
|
|
|
34
34
|
return resolve(here, "..", "template");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
async function scaffold(opts: ScaffoldOptions): Promise<void> {
|
|
38
38
|
const tpl = templateDir();
|
|
39
39
|
if (!(await exists(tpl))) {
|
|
40
40
|
throw new Error(`template not found at ${tpl}`);
|
|
@@ -91,7 +91,7 @@ function parseArgs(argv: string[]): ScaffoldOptions {
|
|
|
91
91
|
return opts;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
async function run(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
95
95
|
const opts = parseArgs(argv);
|
|
96
96
|
await scaffold(opts);
|
|
97
97
|
console.log(`Scaffolded: ${opts.targetDir}`);
|
|
@@ -30,8 +30,8 @@ for (let i = 1; i < args.length; i++) {
|
|
|
30
30
|
}
|
|
31
31
|
const stem = basename(input, extname(input));
|
|
32
32
|
const dir = dirname(input);
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
htmlOut ??= resolve(dir, `${stem}.html`);
|
|
34
|
+
pdfOut ??= resolve(dir, `${stem}.pdf`);
|
|
35
35
|
|
|
36
36
|
const md = await readFile(input, "utf8");
|
|
37
37
|
marked.setOptions({ gfm: true, breaks: false });
|
|
@@ -70,8 +70,9 @@ switch (command) {
|
|
|
70
70
|
console.log(` ${c.cyan(category)}`);
|
|
71
71
|
for (const op of ops.sort((a, b) => b.confidence - a.confidence)) {
|
|
72
72
|
const pct = `${Math.round(op.confidence * 100)}%`;
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
let color = c.yellow;
|
|
74
|
+
if (op.confidence >= 0.85) color = c.green;
|
|
75
|
+
else if (op.confidence <= 0.3) color = c.red;
|
|
75
76
|
console.log(` [${bar(op.confidence)}] ${color(pct)} ${op.statement}`);
|
|
76
77
|
}
|
|
77
78
|
console.log("");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: presentation
|
|
3
|
-
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 `<deck-name>/` 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.
|
|
3
|
+
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 `<deck-name>/` 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
4
|
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`
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -23,10 +23,8 @@ import {
|
|
|
23
23
|
import { RULES } from "./lib/lint-rules";
|
|
24
24
|
import type { DeckContext, Finding, SlideContext, SlideReport } from "./lib/lint-types";
|
|
25
25
|
|
|
26
|
-
// Re-export for backward compatibility with external callers (tests, etc.)
|
|
27
|
-
// that imported these directly from doctor.ts.
|
|
28
26
|
export { extractLayout } from "./lib/lint-helpers";
|
|
29
|
-
export type {
|
|
27
|
+
export type { Finding } from "./lib/lint-types";
|
|
30
28
|
|
|
31
29
|
async function loadSlides(deckDir: string): Promise<{ name: string; body: string }[]> {
|
|
32
30
|
const slidesDir = join(deckDir, "slides");
|
|
@@ -64,7 +62,7 @@ function buildSlideContext(
|
|
|
64
62
|
};
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
async function lintDeck(deckDir: string): Promise<{
|
|
68
66
|
slides: SlideContext[];
|
|
69
67
|
reports: SlideReport[];
|
|
70
68
|
deckFindings: Finding[];
|
|
@@ -95,7 +93,6 @@ export async function lintDeck(deckDir: string): Promise<{
|
|
|
95
93
|
return { slides, reports, deckFindings };
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
// Public: kept for any external caller that imported `lintSlide` directly.
|
|
99
96
|
export async function lintSlide(
|
|
100
97
|
slide: { name: string; body: string },
|
|
101
98
|
deckDir: string
|
|
@@ -7,21 +7,16 @@ export async function readText(path: string): Promise<string> {
|
|
|
7
7
|
|
|
8
8
|
export async function dataUri(path: string): Promise<string> {
|
|
9
9
|
const ext = extname(path).toLowerCase();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
: ext === ".jpg" || ext === ".jpeg"
|
|
16
|
-
? "image/jpeg"
|
|
17
|
-
: ext === ".webp"
|
|
18
|
-
? "image/webp"
|
|
19
|
-
: "application/octet-stream";
|
|
10
|
+
let mime = "application/octet-stream";
|
|
11
|
+
if (ext === ".svg") mime = "image/svg+xml";
|
|
12
|
+
else if (ext === ".png") mime = "image/png";
|
|
13
|
+
else if (ext === ".jpg" || ext === ".jpeg") mime = "image/jpeg";
|
|
14
|
+
else if (ext === ".webp") mime = "image/webp";
|
|
20
15
|
|
|
21
16
|
if (ext === ".svg") {
|
|
22
17
|
// Inline SVGs as URL-encoded text — smaller than base64 and renders crisply at any size.
|
|
23
18
|
const svg = await readFile(path, "utf8");
|
|
24
|
-
const enc = encodeURIComponent(svg).
|
|
19
|
+
const enc = encodeURIComponent(svg).replaceAll("'", "%27").replaceAll('"', "%22");
|
|
25
20
|
return `url("data:${mime};utf8,${enc}")`;
|
|
26
21
|
}
|
|
27
22
|
|
|
@@ -39,7 +39,7 @@ export function extractNotes(body: string): string {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function countAtxHeading(body: string, level: 1 | 2): string[] {
|
|
42
|
-
const re = new RegExp(`^#{${level}}
|
|
42
|
+
const re = new RegExp(String.raw`^#{${level}}\s+(.+?)\s*$`, "gm");
|
|
43
43
|
return Array.from(body.matchAll(re), (m) => m[1]);
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -71,7 +71,7 @@ export type ListItem = {
|
|
|
71
71
|
export function listItems(body: string): ListItem[] {
|
|
72
72
|
const out: ListItem[] = [];
|
|
73
73
|
for (const line of body.split("\n")) {
|
|
74
|
-
const m =
|
|
74
|
+
const m = new RegExp(/^(\s*)(?:[-*]\s+|\d+\.\s+)(.*)$/).exec(line);
|
|
75
75
|
if (!m) continue;
|
|
76
76
|
out.push({ indent: m[1].length, content: m[2], raw: line });
|
|
77
77
|
}
|
|
@@ -125,7 +125,7 @@ export const RULES: Rule[] = [
|
|
|
125
125
|
// pretending to be a bullet. Convert to a sub-bullet instead.
|
|
126
126
|
const findings: Finding[] = [];
|
|
127
127
|
for (const line of ctx.bodyNoNotes.split("\n")) {
|
|
128
|
-
const m =
|
|
128
|
+
const m = new RegExp(/^(\s*)(?:[-*]\s+|\d+\.\s+)(.*)$/).exec(line);
|
|
129
129
|
if (!m) continue;
|
|
130
130
|
const stripped = stripCodeAndLinks(m[2]);
|
|
131
131
|
if (/\s—\s/.test(stripped)) {
|
|
@@ -338,7 +338,10 @@ export const RULES: Rule[] = [
|
|
|
338
338
|
"Anticipated questions",
|
|
339
339
|
];
|
|
340
340
|
for (const beat of required) {
|
|
341
|
-
const re = new RegExp(
|
|
341
|
+
const re = new RegExp(
|
|
342
|
+
String.raw`^[-*]\s+${beat.replace(/\s+/g, "\\s+")}\b`,
|
|
343
|
+
"im"
|
|
344
|
+
);
|
|
342
345
|
if (!re.test(notes)) {
|
|
343
346
|
findings.push({
|
|
344
347
|
rule: "exercise-note-beats",
|
|
@@ -167,11 +167,14 @@ async function main() {
|
|
|
167
167
|
|
|
168
168
|
// 2. Path
|
|
169
169
|
const defaultPath = join(TEMPLATES_ROOT, name);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
let tplPath: string;
|
|
171
|
+
if (args.path) {
|
|
172
|
+
tplPath = resolve(args.path);
|
|
173
|
+
} else if (rl) {
|
|
174
|
+
tplPath = resolve(await ask(rl, "Storage path:", defaultPath));
|
|
175
|
+
} else {
|
|
176
|
+
tplPath = defaultPath;
|
|
177
|
+
}
|
|
175
178
|
|
|
176
179
|
// 3. Logo
|
|
177
180
|
let logo = args.logo;
|
|
@@ -231,7 +234,7 @@ async function main() {
|
|
|
231
234
|
["cover-only", "footer", "both", "none"].includes(a) ? a : "footer"
|
|
232
235
|
) as LogoPlacement;
|
|
233
236
|
}
|
|
234
|
-
|
|
237
|
+
logoPlacement ??= "footer";
|
|
235
238
|
|
|
236
239
|
// 8. Fonts
|
|
237
240
|
const fonts =
|
|
@@ -244,7 +247,7 @@ async function main() {
|
|
|
244
247
|
const a = await ask(rl, "Aspect ratio [16:9 / 4:3 / 16:10]:", "16:9");
|
|
245
248
|
aspect = (["16:9", "4:3", "16:10"].includes(a) ? a : "16:9") as Aspect;
|
|
246
249
|
}
|
|
247
|
-
|
|
250
|
+
aspect ??= "16:9";
|
|
248
251
|
|
|
249
252
|
// 10. Showcase deck?
|
|
250
253
|
let showcase = args.showcase;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: projects
|
|
3
3
|
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 <project>", "track this", "what am I working on", "my projects", "my priorities".
|
|
4
|
-
argument-hint: [list | create | resume | add-
|
|
4
|
+
argument-hint: [list | create | resume | add-next | add-blocker | add-decision | add-handoff | update-section | criteria | isa-init | complete | archive | pause]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Manage the user's project registry. Each project
|
|
7
|
+
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.
|
|
8
8
|
|
|
9
9
|
## CLI
|
|
10
10
|
|
|
@@ -20,16 +20,33 @@ Output is JSON.
|
|
|
20
20
|
|---------|---------|
|
|
21
21
|
| `list` | All registered projects with status, path, updated, stale flag, and counts |
|
|
22
22
|
| `create [name] [--path PATH] [--objectives "a;b;c"]` | Register a project. Defaults: name=basename(cwd), path=cwd. Slug must be `[a-z0-9_-]+` |
|
|
23
|
-
| `resume <name>` | Print the full project
|
|
24
|
-
| `add-
|
|
25
|
-
| `add-
|
|
26
|
-
| `add-
|
|
27
|
-
| `add-
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
23
|
+
| `resume <name>` | Print the full project ISA — all frontmatter and body sections |
|
|
24
|
+
| `add-next <name> "text"` | Append a next step (array, instantly appendable) |
|
|
25
|
+
| `add-blocker <name> "text"` | Append a blocker (array, instantly appendable) |
|
|
26
|
+
| `add-decision <name> "decision" "rationale"` | Log a timestamped decision entry to the Decisions section |
|
|
27
|
+
| `add-handoff <name> "text"` | Overwrite the handoff field (single-value, replaces) |
|
|
28
|
+
| `rm-next \| rm-blocker <name> <index>` | Remove a next/blocker entry by zero-based index |
|
|
29
|
+
| `update-section <name> <section> "content"` | Set an ISA body section (problem, goal, criteria, vision, constraints, out_of_scope, context, decisions, changelog) |
|
|
30
|
+
| `criteria <name>` | Print the Criteria section (verifiable success conditions) |
|
|
31
|
+
| `isa-init <name>` | Mark a project as ISA-initialized |
|
|
31
32
|
| `complete <name>` / `archive <name>` / `pause <name>` / `unpause <name>` | Status transitions |
|
|
32
|
-
| `rm <name>` | Delete the project
|
|
33
|
+
| `rm <name>` | Delete the project directory entirely |
|
|
34
|
+
|
|
35
|
+
## ISA Sections
|
|
36
|
+
|
|
37
|
+
The body of each ISA.md holds spec sections. Use `update-section` to set them:
|
|
38
|
+
|
|
39
|
+
| Section | Key | What goes here |
|
|
40
|
+
|---------|-----|---------------|
|
|
41
|
+
| Problem | `problem` | Why this project exists; the pain or gap being addressed |
|
|
42
|
+
| Goal | `goal` | What success looks like (may be bullet list) |
|
|
43
|
+
| Criteria | `criteria` | Verifiable done conditions — testable ISCs (Ideal State Criteria) |
|
|
44
|
+
| Vision | `vision` | Long-horizon aspiration beyond the immediate goal |
|
|
45
|
+
| Constraints | `constraints` | Non-negotiable limits (budget, time, tech, compatibility) |
|
|
46
|
+
| Out of Scope | `out_of_scope` | What this project explicitly does NOT cover |
|
|
47
|
+
| Context | `context` | Stable facts / references (e.g. "reference impl lives at ~/pai") |
|
|
48
|
+
| Decisions | `decisions` | Auto-managed by `add-decision`; dated bullet list |
|
|
49
|
+
| Changelog | `changelog` | Summary of completed milestones |
|
|
33
50
|
|
|
34
51
|
## Routing
|
|
35
52
|
|
|
@@ -38,9 +55,11 @@ Output is JSON.
|
|
|
38
55
|
| "what am I working on", "my projects", "priorities" | `list` — summarize active and recently-touched projects |
|
|
39
56
|
| "tell me about <project>" | `resume <name>` — present current state, highlight blockers and next steps |
|
|
40
57
|
| "register this" / "track this" / cwd is unregistered work | `create` (default the name from cwd basename, confirm before writing) |
|
|
41
|
-
| "store under <project>: X" / "note on <project>: X" | Pick the field — durable reference → `
|
|
58
|
+
| "store under <project>: X" / "note on <project>: X" | Pick the field — durable reference → `update-section <slug> context "..."`, work item → `add-next`, obstacle → `add-blocker`. If unclear, ask. |
|
|
42
59
|
| "we decided X because Y" | `add-decision <name> "X" "Y"` |
|
|
43
60
|
| "handoff for <project>" / "next session pick up at X" | `add-handoff <name> "<text>"` |
|
|
61
|
+
| "set the goal for <project>" / "describe the problem" | `update-section <name> goal "..."` or `update-section <name> problem "..."` |
|
|
62
|
+
| "what are the criteria for <project>" / "what counts as done" | `criteria <name>` |
|
|
44
63
|
| "mark X complete" / "X is done" | `complete <name>` |
|
|
45
64
|
| "park <project>" / "pause <project>" | `pause <name>` |
|
|
46
65
|
| "archive <project>" | `archive <name>` |
|
|
@@ -53,7 +72,7 @@ When SessionStart context flags the current cwd as unregistered (e.g. `💡 cwd
|
|
|
53
72
|
|
|
54
73
|
- **Default name** = the FULL last path segment of cwd, lowercased. For `/repos/portable-agent-layer` → `portable-agent-layer`. Never split on `-`.
|
|
55
74
|
- **Confirm before creating.** Never auto-create without explicit user approval ("yes", "do it", "register").
|
|
56
|
-
- **Capture
|
|
75
|
+
- **Capture context in conversation.** If the user accepts but doesn't volunteer a goal, ask one short question, or infer from the last few messages and confirm.
|
|
57
76
|
|
|
58
77
|
### When NOT to suggest registration
|
|
59
78
|
|
|
@@ -64,7 +83,7 @@ When SessionStart context flags the current cwd as unregistered (e.g. `💡 cwd
|
|
|
64
83
|
|
|
65
84
|
## Append-as-you-go
|
|
66
85
|
|
|
67
|
-
When the user describes
|
|
86
|
+
When the user describes next steps, blockers, or decisions during normal work, invoke the relevant subcommand to keep state current — that's the dynamism this system is built for. Don't invoke for fleeting comments, hypotheticals, or things the user is just thinking through. Wait for a clear declarative ("let's add X", "Z is blocking us"), not a question or musing.
|
|
68
87
|
|
|
69
88
|
## Examples
|
|
70
89
|
|
|
@@ -72,8 +91,8 @@ When the user describes plans, blockers, or decisions during normal work, invoke
|
|
|
72
91
|
```
|
|
73
92
|
User: "store under <project> that a reference implementation exists in this repo"
|
|
74
93
|
→ Identify the project from `list` (or by name)
|
|
75
|
-
→ Durable reference, not a task →
|
|
76
|
-
→ bun ~/.pal/tools/project.ts
|
|
94
|
+
→ Durable reference, not a task → update-section
|
|
95
|
+
→ bun ~/.pal/tools/project.ts update-section <slug> context "Reference implementation lives in this repo"
|
|
77
96
|
```
|
|
78
97
|
|
|
79
98
|
**Registering the current repo**
|
|
@@ -89,6 +108,12 @@ User: "we decided <decision> because <reason>"
|
|
|
89
108
|
→ bun ~/.pal/tools/project.ts add-decision <slug> "<decision>" "<reason>"
|
|
90
109
|
```
|
|
91
110
|
|
|
111
|
+
**Setting the goal and criteria**
|
|
112
|
+
```
|
|
113
|
+
User: "set the goal for pal to 'ship ISA support with full test coverage'"
|
|
114
|
+
→ bun ~/.pal/tools/project.ts update-section pal goal "ship ISA support with full test coverage"
|
|
115
|
+
```
|
|
116
|
+
|
|
92
117
|
**Completing a project**
|
|
93
118
|
```
|
|
94
119
|
User: "mark <project> as complete"
|
|
@@ -98,10 +123,9 @@ User: "mark <project> as complete"
|
|
|
98
123
|
|
|
99
124
|
## Anti-patterns
|
|
100
125
|
|
|
101
|
-
- **Don't dump the full
|
|
102
|
-
- **Don't write without confirming the field choice on ambiguous "store" requests.** A
|
|
103
|
-
- **Don't edit
|
|
104
|
-
- **Don't confuse `add-fact` with the `telos` skill's `LEARNED.md` or `IDEAS.md`.** Project facts are scoped to one project; TELOS lessons are cross-cutting.
|
|
126
|
+
- **Don't dump the full ISA.md.** Summarize. The user can ask for the raw payload.
|
|
127
|
+
- **Don't write without confirming the field choice on ambiguous "store" requests.** A context fact sticks forever; a next step implies follow-up — these are different commitments.
|
|
128
|
+
- **Don't edit ISA.md files directly.** Always use the CLI — it timestamps `updated` and keeps the schema valid.
|
|
105
129
|
|
|
106
130
|
## Rules
|
|
107
131
|
|
|
@@ -70,7 +70,7 @@ Distinguish between peer-reviewed findings and preprints/working papers.
|
|
|
70
70
|
Note methodology limitations and sample sizes when relevant.
|
|
71
71
|
Be thorough but concise.`;
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
async function geminiSearch(query: string, maxTokens: number): Promise<void> {
|
|
74
74
|
const apiKey = loadApiKey();
|
|
75
75
|
|
|
76
76
|
const body = {
|
|
@@ -183,4 +183,4 @@ Examples:
|
|
|
183
183
|
await geminiSearch(query, maxTokens);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
if (import.meta.main) run();
|
|
186
|
+
if (import.meta.main) void run();
|
|
@@ -70,7 +70,7 @@ function sourcesToTools(sources: SourceType[]): ToolType[] {
|
|
|
70
70
|
return sources.map((s) => map[s]);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
async function grokSearch(
|
|
74
74
|
query: string,
|
|
75
75
|
sources: SourceType[],
|
|
76
76
|
maxTokens: number
|
|
@@ -189,4 +189,4 @@ Examples:
|
|
|
189
189
|
await grokSearch(query, sources, maxTokens);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
if (import.meta.main) run();
|
|
192
|
+
if (import.meta.main) void run();
|
|
@@ -58,7 +58,7 @@ Distinguish between confirmed facts, single-source claims, and unverified allega
|
|
|
58
58
|
Flag contradictions between sources.
|
|
59
59
|
Be thorough but concise.`;
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
async function perplexitySearch(query: string, maxTokens: number): Promise<void> {
|
|
62
62
|
const apiKey = loadApiKey();
|
|
63
63
|
|
|
64
64
|
const body = {
|
|
@@ -147,4 +147,4 @@ Examples:
|
|
|
147
147
|
await perplexitySearch(query, maxTokens);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
if (import.meta.main) run();
|
|
150
|
+
if (import.meta.main) void run();
|
|
@@ -35,6 +35,20 @@ Thinking-only. No tool calls except context recovery (Grep/Glob/Read).
|
|
|
35
35
|
⏱️ EFFORT: [Standard | Extended | Advanced | Deep] — [one-line reason]
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
**0.5. ISA context** — before reverse engineering, orient against the ISA:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# If cwd matches a registered project — read its open ISCs:
|
|
42
|
+
bun ~/.pal/tools/project.ts list-isc <project-name>
|
|
43
|
+
|
|
44
|
+
# If this is ad-hoc work with no registered project — scaffold a task ISA:
|
|
45
|
+
bun ~/.pal/tools/project.ts scaffold-task-isa "<task title>"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Surface any open ISCs as live context: they are unfinished criteria from prior sessions. New criteria defined in this session extend them (use `add-isc`), not replace them.
|
|
49
|
+
|
|
50
|
+
**Off-topic detection:** if the task description references a different registered project than the cwd project (e.g., working on project-a while inside project-b's directory), use `AskUserQuestion` to ask which project the ISC belongs to before writing anything. `add-isc` takes a project name as its first argument, so routing to any project is a one-word change.
|
|
51
|
+
|
|
38
52
|
**1. Reverse engineer the request:**
|
|
39
53
|
|
|
40
54
|
🔎 REVERSE ENGINEERING:
|
|
@@ -231,15 +245,25 @@ bun ~/.pal/tools/handoff-note.ts --done --title "what we completed"
|
|
|
231
245
|
- Skip if the session fully resolved everything it set out to do
|
|
232
246
|
- `--text` should answer: what's next, what was decided, what to watch out for
|
|
233
247
|
|
|
234
|
-
**5. Open
|
|
248
|
+
**5. Open work** — close what this session finished; open what it didn't:
|
|
235
249
|
|
|
250
|
+
**Project work** — use ISCs, not threads:
|
|
236
251
|
```bash
|
|
237
|
-
|
|
252
|
+
# Close completed ISCs:
|
|
253
|
+
bun ~/.pal/tools/project.ts check-isc <project-name> <id>
|
|
254
|
+
|
|
255
|
+
# Open new ISCs for unfinished work:
|
|
256
|
+
bun ~/.pal/tools/project.ts add-isc <project-name> "what remains"
|
|
238
257
|
```
|
|
239
258
|
|
|
240
|
-
|
|
259
|
+
**Task ISA (one-shot work)** — mark complete when done:
|
|
260
|
+
```bash
|
|
261
|
+
bun ~/.pal/tools/project.ts complete-task-isa <slug>
|
|
262
|
+
```
|
|
241
263
|
|
|
264
|
+
**Cross-project or non-project follow-ups** — use threads:
|
|
242
265
|
```bash
|
|
266
|
+
bun ~/.pal/tools/thread.ts --add --title "brief title" --context "why it matters, what needs to happen"
|
|
243
267
|
bun ~/.pal/tools/thread.ts --resolve --id <id>
|
|
244
268
|
```
|
|
245
269
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "PAL_AGENT=codex bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"UserPromptSubmit": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "PAL_AGENT=codex bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PreToolUse": [
|
|
24
|
+
{
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "PAL_AGENT=codex bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"Stop": [
|
|
34
|
+
{
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "PAL_AGENT=codex bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -4,29 +4,35 @@
|
|
|
4
4
|
"sessionStart": [
|
|
5
5
|
{
|
|
6
6
|
"type": "command",
|
|
7
|
-
"command": "bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts"
|
|
7
|
+
"command": "PAL_AGENT=cursor bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts"
|
|
8
8
|
}
|
|
9
9
|
],
|
|
10
10
|
"beforeSubmitPrompt": [
|
|
11
11
|
{
|
|
12
12
|
"type": "command",
|
|
13
|
-
"command": "bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts"
|
|
13
|
+
"command": "PAL_AGENT=cursor bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"preToolUse": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
|
-
"command": "bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
|
|
19
|
+
"command": "PAL_AGENT=cursor bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"type": "command",
|
|
23
|
-
"command": "bun run {{PKG_ROOT}}/src/hooks/SkillGuard.ts"
|
|
23
|
+
"command": "PAL_AGENT=cursor bun run {{PKG_ROOT}}/src/hooks/SkillGuard.ts"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"beforeShellExecution": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "PAL_AGENT=cursor bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
|
|
24
30
|
}
|
|
25
31
|
],
|
|
26
32
|
"stop": [
|
|
27
33
|
{
|
|
28
34
|
"type": "command",
|
|
29
|
-
"command": "bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts"
|
|
35
|
+
"command": "PAL_AGENT=cursor bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts"
|
|
30
36
|
}
|
|
31
37
|
]
|
|
32
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portable-agent-layer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"check": "biome check",
|
|
42
42
|
"check-write": "biome check --write",
|
|
43
43
|
"knip": "knip-bun",
|
|
44
|
+
"arch-check": "bun flint/cli.ts",
|
|
44
45
|
"lint-staged": "lint-staged",
|
|
45
46
|
"prepare": "bun .husky/install.mjs",
|
|
46
47
|
"install:all": "bun run src/cli/index.ts cli install",
|