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.
Files changed (89) hide show
  1. package/README.md +1 -0
  2. package/assets/skills/analyze-pdf/tools/pdf-download.ts +1 -1
  3. package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +1 -1
  4. package/assets/skills/consulting-report/tools/dev.ts +2 -2
  5. package/assets/skills/consulting-report/tools/generate-pdf.ts +9 -9
  6. package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
  7. package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +2 -2
  8. package/assets/skills/opinion/tools/opinion.ts +3 -2
  9. package/assets/skills/presentation/SKILL.md +1 -1
  10. package/assets/skills/presentation/tools/doctor.ts +2 -5
  11. package/assets/skills/presentation/tools/lib/inline.ts +6 -11
  12. package/assets/skills/presentation/tools/lib/lint-helpers.ts +2 -2
  13. package/assets/skills/presentation/tools/lib/lint-rules.ts +5 -2
  14. package/assets/skills/presentation/tools/setup-template.ts +10 -7
  15. package/assets/skills/projects/SKILL.md +44 -20
  16. package/assets/skills/research/tools/gemini-search.ts +2 -2
  17. package/assets/skills/research/tools/grok-search.ts +2 -2
  18. package/assets/skills/research/tools/perplexity-search.ts +2 -2
  19. package/assets/skills/telos/tools/update-telos.ts +0 -1
  20. package/assets/templates/PAL/ALGORITHM.md +27 -3
  21. package/assets/templates/hooks.codex.json +44 -0
  22. package/assets/templates/hooks.cursor.json +11 -5
  23. package/package.json +2 -1
  24. package/src/cli/index.ts +112 -14
  25. package/src/cli/migrate.ts +299 -0
  26. package/src/cli/setup-identity.ts +3 -3
  27. package/src/cli/setup-telos.ts +0 -1
  28. package/src/hooks/CompactRecover.ts +11 -5
  29. package/src/hooks/LoadContext.ts +14 -2
  30. package/src/hooks/PreCompactPersist.ts +26 -34
  31. package/src/hooks/SecurityValidator.ts +43 -21
  32. package/src/hooks/StopOrchestrator.ts +4 -1
  33. package/src/hooks/UserPromptOrchestrator.ts +4 -2
  34. package/src/hooks/handlers/auto-graduate.ts +2 -2
  35. package/src/hooks/handlers/backup.ts +3 -3
  36. package/src/hooks/handlers/failure.ts +5 -3
  37. package/src/hooks/handlers/inject-retrieval.ts +29 -6
  38. package/src/hooks/handlers/persist-last-exchange.ts +76 -0
  39. package/src/hooks/handlers/rating.ts +2 -1
  40. package/src/hooks/handlers/readme-sync.ts +3 -2
  41. package/src/hooks/handlers/session-intelligence.ts +9 -8
  42. package/src/hooks/handlers/session-name.ts +2 -2
  43. package/src/hooks/handlers/synthesis.ts +5 -2
  44. package/src/hooks/handlers/update-counts.ts +3 -2
  45. package/src/hooks/lib/agent.ts +20 -18
  46. package/src/hooks/lib/context.ts +45 -117
  47. package/src/hooks/lib/entities.ts +7 -7
  48. package/src/hooks/lib/frontmatter.ts +4 -4
  49. package/src/hooks/lib/graduation.ts +7 -6
  50. package/src/hooks/lib/inference.ts +6 -2
  51. package/src/hooks/lib/learning-category.ts +1 -1
  52. package/src/hooks/lib/learning-store.ts +6 -1
  53. package/src/hooks/lib/notify.ts +2 -2
  54. package/src/hooks/lib/opinions.ts +3 -3
  55. package/src/hooks/lib/paths.ts +2 -0
  56. package/src/hooks/lib/projects.ts +142 -74
  57. package/src/hooks/lib/readme-sync.ts +1 -1
  58. package/src/hooks/lib/relationship.ts +3 -15
  59. package/src/hooks/lib/retrieval-index.ts +5 -3
  60. package/src/hooks/lib/retrieval.ts +11 -12
  61. package/src/hooks/lib/security.ts +22 -18
  62. package/src/hooks/lib/semi-static.ts +4 -2
  63. package/src/hooks/lib/session-names.ts +1 -1
  64. package/src/hooks/lib/settings.ts +1 -1
  65. package/src/hooks/lib/setup.ts +2 -60
  66. package/src/hooks/lib/signals.ts +2 -2
  67. package/src/hooks/lib/stdin.ts +1 -1
  68. package/src/hooks/lib/stop.ts +13 -6
  69. package/src/hooks/lib/token-usage.ts +1 -2
  70. package/src/hooks/lib/transcript.ts +1 -1
  71. package/src/hooks/lib/wisdom.ts +5 -5
  72. package/src/hooks/lib/work-tracking.ts +8 -14
  73. package/src/targets/codex/install.ts +95 -0
  74. package/src/targets/codex/uninstall.ts +70 -0
  75. package/src/targets/lib.ts +140 -14
  76. package/src/targets/opencode/plugin.ts +22 -11
  77. package/src/tools/agent/algorithm-reflect.ts +1 -1
  78. package/src/tools/agent/analyze.ts +18 -18
  79. package/src/tools/agent/handoff-note.ts +1 -1
  80. package/src/tools/agent/project.ts +375 -75
  81. package/src/tools/agent/synthesize.ts +6 -42
  82. package/src/tools/agent/thread.ts +15 -14
  83. package/src/tools/agent/wisdom-frame.ts +9 -3
  84. package/src/tools/import.ts +1 -1
  85. package/src/tools/relationship-reflect.ts +13 -11
  86. package/src/tools/self-model.ts +20 -16
  87. package/src/tools/session-summary.ts +3 -3
  88. package/src/tools/token-cost.ts +15 -16
  89. 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
@@ -99,4 +99,4 @@ async function main() {
99
99
  console.log(JSON.stringify(result, null, 2));
100
100
  }
101
101
 
102
- main();
102
+ void main();
@@ -102,4 +102,4 @@ async function main() {
102
102
  console.log(text);
103
103
  }
104
104
 
105
- main();
105
+ void main();
@@ -19,7 +19,7 @@ async function exists(p: string): Promise<boolean> {
19
19
  }
20
20
  }
21
21
 
22
- export async function dev(reportDir: string): Promise<number> {
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
- export async function run(argv: string[] = process.argv.slice(2)): Promise<void> {
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
- .replace(/&/g, "&amp;")
47
- .replace(/</g, "&lt;")
48
- .replace(/>/g, "&gt;")
49
- .replace(/"/g, "&quot;");
46
+ .replaceAll("&", "&amp;")
47
+ .replaceAll("<", "&lt;")
48
+ .replaceAll(">", "&gt;")
49
+ .replaceAll('"', "&quot;");
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
- export async function loadMeta(reportDir: string): Promise<ReportMeta> {
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
- export function buildNext(reportDir: string): void {
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
- export async function renderPdf(
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
- export async function generate(opts: GenerateOptions): Promise<{
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
- export async function run(argv: string[] = process.argv.slice(2)): Promise<void> {
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
- export async function scaffold(opts: ScaffoldOptions): Promise<void> {
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
- export async function run(argv: string[] = process.argv.slice(2)): Promise<void> {
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
- if (!htmlOut) htmlOut = resolve(dir, `${stem}.html`);
34
- if (!pdfOut) pdfOut = resolve(dir, `${stem}.pdf`);
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
- const color =
74
- op.confidence >= 0.85 ? c.green : op.confidence <= 0.3 ? c.red : c.yellow;
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 { DeckContext, Finding, SlideContext, SlideReport } from "./lib/lint-types";
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
- export async function lintDeck(deckDir: string): Promise<{
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
- const mime =
11
- ext === ".svg"
12
- ? "image/svg+xml"
13
- : ext === ".png"
14
- ? "image/png"
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).replace(/'/g, "%27").replace(/"/g, "%22");
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}}\\s+(.+?)\\s*$`, "gm");
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 = line.match(/^(\s*)(?:[-*]\s+|\d+\.\s+)(.*)$/);
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 = line.match(/^(\s*)(?:[-*]\s+|\d+\.\s+)(.*)$/);
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(`^[-*]\\s+${beat.replace(/\s+/g, "\\s+")}\\b`, "im");
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
- const tplPath = args.path
171
- ? resolve(args.path)
172
- : rl
173
- ? resolve(await ask(rl, "Storage path:", defaultPath))
174
- : defaultPath;
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
- if (!logoPlacement) logoPlacement = "footer";
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
- if (!aspect) aspect = "16:9";
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-fact | add-objective | add-next | add-blocker | add-decision | add-handoff | complete | archive | pause]
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 has its own state file at `~/.pal/memory/state/progress/{slug}.json`. The Stop hook auto-touches `updated` whenever the cwd resolves into a registered project — so just *being* in the project keeps it warm.
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 JSONfacts, objectives, next steps, blockers, decisions, handoff |
24
- | `add-fact <name> "text"` | Append a stable fact / reference (e.g., "reference impl lives in this repo") |
25
- | `add-objective <name> "text"` | Append an objective |
26
- | `add-next <name> "text"` | Append a next step |
27
- | `add-blocker <name> "text"` | Append a blocker |
28
- | `add-decision <name> "decision" "rationale"` | Log a timestamped decision |
29
- | `add-handoff <name> "text"` | Overwrite the handoff field (single-value) |
30
- | `rm-fact \| rm-objective \| rm-next \| rm-blocker <name> <index>` | Remove an entry by zero-based index |
23
+ | `resume <name>` | Print the full project ISAall 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 state file entirely |
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 → `add-fact`, work item → `add-next`, obstacle → `add-blocker`. If unclear, ask. |
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 objectives in conversation.** If the user accepts but doesn't volunteer objectives, ask one short question, or infer from the last few messages and confirm.
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 plans, 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.
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 → add-fact
76
- → bun ~/.pal/tools/project.ts add-fact <slug> "Reference implementation lives in this repo"
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 JSON.** Summarize. The user can ask for the raw payload.
102
- - **Don't write without confirming the field choice on ambiguous "store" requests.** A "fact" sticks forever; a "next step" implies follow-up — these are different commitments.
103
- - **Don't edit the JSON files directly.** Always use the CLI — it timestamps `updated` and keeps the schema valid.
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
- export async function geminiSearch(query: string, maxTokens: number): Promise<void> {
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
- export async function grokSearch(
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
- export async function perplexitySearch(query: string, maxTokens: number): Promise<void> {
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();
@@ -10,7 +10,6 @@
10
10
  * - Appends content (preserves existing)
11
11
  * - Logs the change to updates.md
12
12
  *
13
- * For PROJECTS.md upserts (add/update by ID), use update-projects.ts instead.
14
13
  */
15
14
 
16
15
  import {
@@ -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 Threads** — for each unresolved question, decision, or follow-up that came up during this session:
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
- bun ~/.pal/tools/thread.ts --add --title "brief title" --context "why it matters, what needs to happen"
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
- Only add threads that genuinely need follow-up. Resolve existing threads if this session closed them:
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.36.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",