portable-agent-layer 0.32.0 → 0.34.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 (38) hide show
  1. package/README.md +1 -0
  2. package/assets/skills/presentation/SKILL.md +124 -5
  3. package/assets/skills/presentation/WORKSHOP.md +128 -0
  4. package/assets/skills/presentation/theme-base/base.css +113 -0
  5. package/assets/skills/presentation/theme-base/layouts.css +11 -2
  6. package/assets/skills/presentation/tools/build.ts +136 -6
  7. package/assets/skills/presentation/tools/doctor.ts +106 -317
  8. package/assets/skills/presentation/tools/lib/lint-helpers.ts +150 -0
  9. package/assets/skills/presentation/tools/lib/lint-rules.ts +744 -0
  10. package/assets/skills/presentation/tools/lib/lint-types.ts +40 -0
  11. package/assets/skills/presentation/tools/new-deck.ts +9 -4
  12. package/assets/skills/presentation/vendor/reveal/plugin/highlight/github-dark.css +118 -0
  13. package/assets/skills/projects/SKILL.md +111 -0
  14. package/assets/skills/telos/SKILL.md +4 -1
  15. package/assets/templates/AGENTS.md.template +28 -7
  16. package/assets/templates/PAL/ALGORITHM.md +2 -0
  17. package/assets/templates/PAL/README.md +1 -2
  18. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +1 -1
  19. package/assets/templates/pal-settings.json +2 -2
  20. package/package.json +2 -3
  21. package/src/cli/index.ts +7 -0
  22. package/src/hooks/UserPromptOrchestrator.ts +3 -1
  23. package/src/hooks/handlers/auto-graduate.ts +169 -0
  24. package/src/hooks/handlers/inject-retrieval.ts +50 -0
  25. package/src/hooks/handlers/project-touch.ts +39 -0
  26. package/src/hooks/lib/context.ts +9 -8
  27. package/src/hooks/lib/paths.ts +2 -0
  28. package/src/hooks/lib/projects.ts +270 -0
  29. package/src/hooks/lib/retrieval-index.ts +223 -0
  30. package/src/hooks/lib/retrieval.ts +170 -0
  31. package/src/hooks/lib/security.ts +2 -0
  32. package/src/hooks/lib/stop.ts +9 -1
  33. package/src/hooks/lib/text-similarity.ts +13 -9
  34. package/src/hooks/lib/wisdom.ts +155 -1
  35. package/src/tools/agent/project.ts +336 -0
  36. package/src/tools/self-model.ts +3 -3
  37. package/src/tools/token-cost.ts +4 -4
  38. package/assets/templates/PAL/CONTEXT_ROUTING.md +0 -30
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Project — register and manage user projects via per-project progress JSONs.
4
+ *
5
+ * Replaces the hand-edited `~/.pal/telos/PROJECTS.md`. The AI is the primary
6
+ * caller (proactive registration on unregistered cwds, append-as-you-go updates);
7
+ * the user is the escape hatch for fine-grained control.
8
+ *
9
+ * State: `~/.pal/memory/state/progress/{slug}.json`. Auto-touched on Stop hook
10
+ * when cwd resolves into a registered project.
11
+ *
12
+ * Usage:
13
+ * bun ~/.pal/tools/project.ts list
14
+ * bun ~/.pal/tools/project.ts create [name] [--path PATH] [--objectives "..."]
15
+ * bun ~/.pal/tools/project.ts resume <name>
16
+ * bun ~/.pal/tools/project.ts complete | archive | pause | unpause <name>
17
+ * bun ~/.pal/tools/project.ts add-fact <name> "text"
18
+ * bun ~/.pal/tools/project.ts add-objective <name> "text"
19
+ * bun ~/.pal/tools/project.ts add-next <name> "text"
20
+ * bun ~/.pal/tools/project.ts add-blocker <name> "text"
21
+ * bun ~/.pal/tools/project.ts add-decision <name> "decision" "rationale"
22
+ * bun ~/.pal/tools/project.ts add-handoff <name> "text"
23
+ * bun ~/.pal/tools/project.ts rm-fact | rm-objective | rm-next | rm-blocker <name> <index>
24
+ */
25
+
26
+ import { resolve } from "node:path";
27
+ import { parseArgs } from "node:util";
28
+ import {
29
+ defaultSlug,
30
+ deleteProject,
31
+ isStale,
32
+ type ProjectProgress,
33
+ type ProjectStatus,
34
+ readAllProjects,
35
+ readProject,
36
+ writeProject,
37
+ } from "../../hooks/lib/projects";
38
+
39
+ function now(): string {
40
+ return new Date().toISOString();
41
+ }
42
+
43
+ function fail(msg: string): never {
44
+ process.stderr.write(`${msg}\n`);
45
+ process.exit(1);
46
+ }
47
+
48
+ function ok(payload: Record<string, unknown>): void {
49
+ console.log(JSON.stringify(payload, null, 2));
50
+ }
51
+
52
+ function requireProject(name: string): ProjectProgress {
53
+ const p = readProject(name);
54
+ if (!p) fail(`No project named "${name}". Run 'list' to see registered projects.`);
55
+ return p as ProjectProgress;
56
+ }
57
+
58
+ // ── list ──────────────────────────────────────────────────────────
59
+
60
+ function cmdList(): void {
61
+ const all = readAllProjects().sort((a, b) => b.updated.localeCompare(a.updated));
62
+ const rows = all.map((p) => ({
63
+ name: p.name,
64
+ status: p.status,
65
+ path: p.path,
66
+ updated: p.updated,
67
+ stale: isStale(p),
68
+ objectives: p.objectives?.length ?? 0,
69
+ next_steps: p.next_steps?.length ?? 0,
70
+ blockers: p.blockers?.length ?? 0,
71
+ }));
72
+ ok({ count: all.length, projects: rows });
73
+ }
74
+
75
+ // ── create ────────────────────────────────────────────────────────
76
+
77
+ function cmdCreate(args: string[]): void {
78
+ const { values, positionals } = parseArgs({
79
+ args,
80
+ options: {
81
+ path: { type: "string" },
82
+ name: { type: "string" },
83
+ objectives: { type: "string" },
84
+ },
85
+ allowPositionals: true,
86
+ });
87
+
88
+ const path = resolve(values.path ?? process.cwd());
89
+ const name = (values.name ?? positionals[0] ?? defaultSlug(path)).trim();
90
+
91
+ if (!/^[a-z0-9_-]+$/.test(name)) {
92
+ fail(
93
+ `Invalid project name "${name}". Use lowercase letters, digits, hyphens, underscores.`
94
+ );
95
+ }
96
+
97
+ if (readProject(name)) {
98
+ fail(
99
+ `Project "${name}" already exists. Pick a different --name or run 'resume ${name}' to inspect.`
100
+ );
101
+ }
102
+
103
+ const objectives = values.objectives
104
+ ? values.objectives
105
+ .split(/[\n;|]/)
106
+ .map((s) => s.trim())
107
+ .filter(Boolean)
108
+ : undefined;
109
+
110
+ const project: ProjectProgress = {
111
+ name,
112
+ path,
113
+ status: "active",
114
+ created: now(),
115
+ updated: now(),
116
+ objectives,
117
+ };
118
+ writeProject(project);
119
+ ok({ created: true, project });
120
+ }
121
+
122
+ // ── resume ────────────────────────────────────────────────────────
123
+
124
+ function cmdResume(args: string[]): void {
125
+ const name = args[0];
126
+ if (!name) fail("Usage: resume <name>");
127
+ ok({ project: requireProject(name) });
128
+ }
129
+
130
+ // ── status transitions ────────────────────────────────────────────
131
+
132
+ function setStatus(name: string, status: ProjectStatus): void {
133
+ const p = requireProject(name);
134
+ p.status = status;
135
+ p.updated = now();
136
+ writeProject(p);
137
+ ok({ updated: true, name, status });
138
+ }
139
+
140
+ // ── append helpers ────────────────────────────────────────────────
141
+
142
+ function appendItem(
143
+ name: string,
144
+ field: "facts" | "objectives" | "next_steps" | "blockers",
145
+ text: string
146
+ ): void {
147
+ if (!text?.trim()) fail(`Empty ${field.replace("_", " ")} text.`);
148
+ const p = requireProject(name);
149
+ const list = p[field] ?? [];
150
+ list.push(text.trim());
151
+ p[field] = list;
152
+ p.updated = now();
153
+ writeProject(p);
154
+ ok({ updated: true, name, field, count: list.length });
155
+ }
156
+
157
+ function removeItem(
158
+ name: string,
159
+ field: "facts" | "objectives" | "next_steps" | "blockers",
160
+ indexArg: string
161
+ ): void {
162
+ const idx = parseInt(indexArg, 10);
163
+ if (!Number.isInteger(idx) || idx < 0) fail(`Invalid index "${indexArg}".`);
164
+ const p = requireProject(name);
165
+ const list = p[field] ?? [];
166
+ if (idx >= list.length) fail(`Index ${idx} out of range (length ${list.length}).`);
167
+ const removed = list.splice(idx, 1)[0];
168
+ p[field] = list;
169
+ p.updated = now();
170
+ writeProject(p);
171
+ ok({ updated: true, name, field, removed, count: list.length });
172
+ }
173
+
174
+ function addDecision(name: string, decision: string, rationale: string): void {
175
+ if (!decision?.trim() || !rationale?.trim())
176
+ fail("Usage: add-decision <name> <decision> <rationale>");
177
+ const p = requireProject(name);
178
+ const list = p.decisions ?? [];
179
+ list.push({ ts: now(), decision: decision.trim(), rationale: rationale.trim() });
180
+ p.decisions = list;
181
+ p.updated = now();
182
+ writeProject(p);
183
+ ok({ updated: true, name, count: list.length });
184
+ }
185
+
186
+ function addHandoff(name: string, text: string): void {
187
+ if (!text?.trim()) fail("Empty handoff text.");
188
+ const p = requireProject(name);
189
+ p.handoff = text.trim();
190
+ p.updated = now();
191
+ writeProject(p);
192
+ ok({ updated: true, name });
193
+ }
194
+
195
+ // ── rm (project) ──────────────────────────────────────────────────
196
+
197
+ function cmdRm(args: string[]): void {
198
+ const name = args[0];
199
+ if (!name) fail("Usage: rm <name> (deletes the entire project state file)");
200
+ const removed = deleteProject(name);
201
+ if (!removed) fail(`No project named "${name}".`);
202
+ ok({ deleted: true, name });
203
+ }
204
+
205
+ // ── dispatch ──────────────────────────────────────────────────────
206
+
207
+ function help(): void {
208
+ console.log(`Project — manage PAL project state.
209
+
210
+ Commands:
211
+ list show all registered projects
212
+ create [name] [--path PATH] [--objectives X] register a project (defaults: name=basename(cwd), path=cwd)
213
+ resume <name> print full project JSON
214
+ complete <name> mark complete
215
+ archive <name> mark archived
216
+ pause <name> | unpause <name> toggle paused/active
217
+ add-fact <name> "text" append a stable fact / reference
218
+ add-objective <name> "text" append objective
219
+ add-next <name> "text" append next step
220
+ add-blocker <name> "text" append blocker
221
+ add-decision <name> "decision" "rationale" log a decision
222
+ add-handoff <name> "text" overwrite handoff field
223
+ rm-fact <name> <index> remove fact by index
224
+ rm-objective <name> <index> remove objective by index
225
+ rm-next <name> <index> remove next step by index
226
+ rm-blocker <name> <index> remove blocker by index
227
+ rm <name> delete the entire project file
228
+ `);
229
+ }
230
+
231
+ export function run(): void {
232
+ const [cmd, ...rest] = Bun.argv.slice(2);
233
+ if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
234
+ help();
235
+ return;
236
+ }
237
+ switch (cmd) {
238
+ case "list":
239
+ cmdList();
240
+ return;
241
+ case "create":
242
+ cmdCreate(rest);
243
+ return;
244
+ case "resume":
245
+ cmdResume(rest);
246
+ return;
247
+ case "complete":
248
+ setStatus(rest[0] ?? fail("Usage: complete <name>"), "complete");
249
+ return;
250
+ case "archive":
251
+ setStatus(rest[0] ?? fail("Usage: archive <name>"), "archived");
252
+ return;
253
+ case "pause":
254
+ setStatus(rest[0] ?? fail("Usage: pause <name>"), "paused");
255
+ return;
256
+ case "unpause":
257
+ setStatus(rest[0] ?? fail("Usage: unpause <name>"), "active");
258
+ return;
259
+ case "add-fact":
260
+ appendItem(
261
+ rest[0] ?? fail("Usage: add-fact <name> <text>"),
262
+ "facts",
263
+ rest.slice(1).join(" ")
264
+ );
265
+ return;
266
+ case "add-objective":
267
+ appendItem(
268
+ rest[0] ?? fail("Usage: add-objective <name> <text>"),
269
+ "objectives",
270
+ rest.slice(1).join(" ")
271
+ );
272
+ return;
273
+ case "add-next":
274
+ appendItem(
275
+ rest[0] ?? fail("Usage: add-next <name> <text>"),
276
+ "next_steps",
277
+ rest.slice(1).join(" ")
278
+ );
279
+ return;
280
+ case "add-blocker":
281
+ appendItem(
282
+ rest[0] ?? fail("Usage: add-blocker <name> <text>"),
283
+ "blockers",
284
+ rest.slice(1).join(" ")
285
+ );
286
+ return;
287
+ case "add-decision":
288
+ addDecision(
289
+ rest[0] ?? fail("Usage: add-decision <name> <decision> <rationale>"),
290
+ rest[1] ?? "",
291
+ rest.slice(2).join(" ")
292
+ );
293
+ return;
294
+ case "add-handoff":
295
+ addHandoff(
296
+ rest[0] ?? fail("Usage: add-handoff <name> <text>"),
297
+ rest.slice(1).join(" ")
298
+ );
299
+ return;
300
+ case "rm-fact":
301
+ removeItem(
302
+ rest[0] ?? fail("Usage: rm-fact <name> <index>"),
303
+ "facts",
304
+ rest[1] ?? ""
305
+ );
306
+ return;
307
+ case "rm-objective":
308
+ removeItem(
309
+ rest[0] ?? fail("Usage: rm-objective <name> <index>"),
310
+ "objectives",
311
+ rest[1] ?? ""
312
+ );
313
+ return;
314
+ case "rm-next":
315
+ removeItem(
316
+ rest[0] ?? fail("Usage: rm-next <name> <index>"),
317
+ "next_steps",
318
+ rest[1] ?? ""
319
+ );
320
+ return;
321
+ case "rm-blocker":
322
+ removeItem(
323
+ rest[0] ?? fail("Usage: rm-blocker <name> <index>"),
324
+ "blockers",
325
+ rest[1] ?? ""
326
+ );
327
+ return;
328
+ case "rm":
329
+ cmdRm(rest);
330
+ return;
331
+ default:
332
+ fail(`Unknown command "${cmd}". Run 'project.ts help' for usage.`);
333
+ }
334
+ }
335
+
336
+ if (import.meta.main) run();
@@ -21,6 +21,7 @@ import { parseArgs } from "node:util";
21
21
  import { inference } from "../hooks/lib/inference";
22
22
  import { SONNET_MODEL } from "../hooks/lib/models";
23
23
  import { ensureDir, paths } from "../hooks/lib/paths";
24
+ import { identity as loadSettingsIdentity } from "../hooks/lib/settings";
24
25
  import { logTokenUsage } from "../hooks/lib/token-usage";
25
26
 
26
27
  // ── Config ──
@@ -397,7 +398,8 @@ function formatDataForInference(data: SelfModelData): string {
397
398
  );
398
399
 
399
400
  if (data.opinions.length > 0) {
400
- sections.push(`\n### Opinions about Rico (confidence-scored)`);
401
+ const principalName = loadSettingsIdentity().principal.name;
402
+ sections.push(`\n### Opinions about ${principalName} (confidence-scored)`);
401
403
  for (const o of data.opinions.filter((o) => o.confidence >= 0.6)) {
402
404
  sections.push(
403
405
  `- [${o.category}] ${o.statement} (${Math.round(o.confidence * 100)}%)`
@@ -465,8 +467,6 @@ function formatDataForInference(data: SelfModelData): string {
465
467
  return sections.join("\n");
466
468
  }
467
469
 
468
- import { identity as loadSettingsIdentity } from "../hooks/lib/settings";
469
-
470
470
  function buildPrompt(aiName: string, principalName: string): string {
471
471
  return `You are writing a self-model for an AI assistant named ${aiName}. You ARE ${aiName}. Write in first person.
472
472
 
@@ -1,11 +1,11 @@
1
1
  /**
2
- * CLI tool: summarize token usage and estimated cost.
2
+ * Summarize token usage and estimated cost.
3
3
  *
4
4
  * Reads from two sources:
5
5
  * 1. Claude Code session transcripts (~/.claude/projects/)
6
6
  * 2. PAL Haiku inference logs (memory/signals/token-usage.jsonl)
7
7
  *
8
- * Usage: bun run tool:tokens [--today|--week|--month|--all] [--project <name>]
8
+ * Invoked via `pal cli usage [--today|--week|--month|--all] [--project <name>]`.
9
9
  */
10
10
 
11
11
  import { existsSync, readdirSync, readFileSync } from "node:fs";
@@ -372,7 +372,7 @@ export function readPalInference(): {
372
372
 
373
373
  // ── CLI ──
374
374
 
375
- function run() {
375
+ export function usage() {
376
376
  parseArgs({
377
377
  options: {
378
378
  today: { type: "boolean", default: false },
@@ -441,4 +441,4 @@ function run() {
441
441
  console.log(`\n Grand Total: ${fmtCost(grand.cost)}\n`);
442
442
  }
443
443
 
444
- if (import.meta.main) run();
444
+ if (import.meta.main) usage();
@@ -1,30 +0,0 @@
1
- # Context Routing
2
-
3
- Load context on-demand by reading the file at the path listed. Only load what the current task requires.
4
-
5
- ## PAL System
6
-
7
- | Topic | Path |
8
- |-------|------|
9
- | PAL system overview | `~/.pal/docs/README.md` |
10
- | System architecture | `~/.pal/docs/SYSTEM_ARCHITECTURE.md` |
11
- | Memory format & guidelines | `~/.pal/docs/MEMORY_SYSTEM.md` |
12
- | Work tracking (projects, sessions) | `~/.pal/docs/WORK_TRACKING.md` |
13
- | Opinion tracking | `~/.pal/docs/OPINION_TRACKING.md` |
14
- | Steering rules | `~/.pal/docs/STEERING_RULES.md` |
15
- | Algorithm (complex work phases) | `~/.pal/docs/ALGORITHM.md` |
16
-
17
- ## User Context (TELOS)
18
-
19
- | Topic | Path |
20
- |-------|------|
21
- | Projects & priorities | `~/.pal/telos/PROJECTS.md` |
22
- | Goals (short/medium/long-term) | `~/.pal/telos/GOALS.md` |
23
- | Beliefs & principles | `~/.pal/telos/BELIEFS.md` |
24
- | Current challenges | `~/.pal/telos/CHALLENGES.md` |
25
- | Mission & direction | `~/.pal/telos/MISSION.md` |
26
- | Strategies & approaches | `~/.pal/telos/STRATEGIES.md` |
27
- | Ideas to explore | `~/.pal/telos/IDEAS.md` |
28
- | Key lessons learned | `~/.pal/telos/LEARNED.md` |
29
- | Mental models | `~/.pal/telos/MODELS.md` |
30
- | Narrative context | `~/.pal/telos/NARRATIVES.md` |