portable-agent-layer 0.31.0 → 0.33.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 (48) hide show
  1. package/assets/skills/consulting-report/SKILL.md +8 -2
  2. package/assets/skills/consulting-report/demo/app/globals.css +115 -0
  3. package/assets/skills/consulting-report/demo/app/page.tsx +75 -28
  4. package/assets/skills/consulting-report/demo/components/comparison-table.tsx +40 -0
  5. package/assets/skills/consulting-report/demo/components/section.tsx +3 -2
  6. package/assets/skills/consulting-report/demo/components/stat-grid.tsx +26 -0
  7. package/assets/skills/consulting-report/demo/components/table-of-contents.tsx +27 -0
  8. package/assets/skills/consulting-report/template/app/globals.css +115 -0
  9. package/assets/skills/consulting-report/template/app/page.tsx +55 -28
  10. package/assets/skills/consulting-report/template/components/comparison-table.tsx +40 -0
  11. package/assets/skills/consulting-report/template/components/section.tsx +3 -2
  12. package/assets/skills/consulting-report/template/components/stat-grid.tsx +26 -0
  13. package/assets/skills/consulting-report/template/components/table-of-contents.tsx +27 -0
  14. package/assets/skills/presentation/SKILL.md +124 -5
  15. package/assets/skills/presentation/WORKSHOP.md +128 -0
  16. package/assets/skills/presentation/theme-base/base.css +113 -0
  17. package/assets/skills/presentation/theme-base/layouts.css +11 -2
  18. package/assets/skills/presentation/tools/build.ts +136 -6
  19. package/assets/skills/presentation/tools/doctor.ts +106 -317
  20. package/assets/skills/presentation/tools/lib/lint-helpers.ts +150 -0
  21. package/assets/skills/presentation/tools/lib/lint-rules.ts +744 -0
  22. package/assets/skills/presentation/tools/lib/lint-types.ts +40 -0
  23. package/assets/skills/presentation/tools/new-deck.ts +9 -4
  24. package/assets/skills/presentation/vendor/reveal/plugin/highlight/github-dark.css +118 -0
  25. package/assets/skills/projects/SKILL.md +111 -0
  26. package/assets/skills/telos/SKILL.md +4 -1
  27. package/assets/templates/AGENTS.md.template +28 -7
  28. package/assets/templates/PAL/ALGORITHM.md +2 -0
  29. package/assets/templates/PAL/README.md +0 -1
  30. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +1 -1
  31. package/assets/templates/pal-settings.json +2 -2
  32. package/package.json +1 -1
  33. package/src/hooks/UserPromptOrchestrator.ts +3 -1
  34. package/src/hooks/handlers/auto-graduate.ts +169 -0
  35. package/src/hooks/handlers/inject-retrieval.ts +50 -0
  36. package/src/hooks/handlers/project-touch.ts +39 -0
  37. package/src/hooks/lib/context.ts +9 -8
  38. package/src/hooks/lib/paths.ts +2 -0
  39. package/src/hooks/lib/projects.ts +270 -0
  40. package/src/hooks/lib/retrieval-index.ts +223 -0
  41. package/src/hooks/lib/retrieval.ts +170 -0
  42. package/src/hooks/lib/security.ts +2 -0
  43. package/src/hooks/lib/stop.ts +9 -1
  44. package/src/hooks/lib/text-similarity.ts +13 -9
  45. package/src/hooks/lib/wisdom.ts +155 -1
  46. package/src/tools/agent/project.ts +336 -0
  47. package/src/tools/self-model.ts +3 -3
  48. package/assets/templates/PAL/CONTEXT_ROUTING.md +0 -30
@@ -244,6 +244,14 @@
244
244
  .reveal tr:nth-child(even) td { background: var(--brand-surface); }
245
245
  .reveal tr:last-child td { border-bottom: 0; }
246
246
 
247
+ /* ── Text alignment utilities — composable on any block element.
248
+ * `!important` is needed because Reveal's print stylesheet forces
249
+ * `text-align: left !important` on every div/p/ol/ul, which would otherwise
250
+ * override author intent (inline styles or any non-`!important` rule). */
251
+ .reveal .text-center { text-align: center !important; }
252
+ .reveal .text-right { text-align: right !important; }
253
+ .reveal .text-left { text-align: left !important; }
254
+
247
255
  /* ── Image utilities (composable on any <img> or wrapper) */
248
256
  .reveal .image-rounded img,
249
257
  .reveal img.image-rounded { border-radius: var(--radius-md); }
@@ -323,3 +331,108 @@
323
331
  color: var(--brand-accent);
324
332
  height: 2px;
325
333
  }
334
+
335
+ /* ── Print
336
+ * Reveal's vendored print stylesheet (`@media print { html:not(.print-pdf) … }`)
337
+ * forces every heading to `color:#000!important` and `p/li/td` to `#000`. That
338
+ * inverts the white-on-brand text we use on `section` and `closing` dividers
339
+ * (whose gradient background is on the section element itself, so it still
340
+ * renders blue) — producing black-on-blue when the user hits Cmd+P without
341
+ * `?print-pdf`. Restore brand text colors here, and force color-adjust:exact
342
+ * so the gradient prints reliably. */
343
+ @media print {
344
+ .reveal section[data-layout="section"],
345
+ .reveal section[data-layout="closing"] {
346
+ -webkit-print-color-adjust: exact !important;
347
+ print-color-adjust: exact !important;
348
+ }
349
+ .reveal section[data-layout="section"] h1,
350
+ .reveal section[data-layout="section"] h4,
351
+ .reveal section[data-layout="closing"] h1 {
352
+ color: #fff !important;
353
+ }
354
+ .reveal section[data-layout="section"] h2,
355
+ .reveal section[data-layout="section"] h3 {
356
+ color: rgba(255, 255, 255, 0.7) !important;
357
+ }
358
+ .reveal section[data-layout="section"] p,
359
+ .reveal section[data-layout="section"] li,
360
+ .reveal section[data-layout="closing"] h2,
361
+ .reveal section[data-layout="closing"] p,
362
+ .reveal section[data-layout="closing"] li {
363
+ color: rgba(255, 255, 255, 0.85) !important;
364
+ }
365
+ .reveal section[data-layout="title"] h1 {
366
+ color: var(--brand-primary) !important;
367
+ }
368
+
369
+ /* ── Cmd+P scope (browser print on the regular URL, NOT ?print-pdf)
370
+ * Reveal forces every section to `display:block; position:static; padding:60px 20px;
371
+ * transform:none`, which strips the flex centering from title/section/closing/quote/
372
+ * big-stat/pull-quote and leaves their content top-aligned. Restore centering, then
373
+ * frame every printed page with a hairline so it reads like a printed sheet. */
374
+
375
+ /* Hairline frame — inset so it lives inside the printable area and survives
376
+ * the printer's unprintable-margin clipping. */
377
+ html:not(.print-pdf) .reveal .slides section {
378
+ box-shadow: inset 0 0 0 1px var(--neutral-300) !important;
379
+ }
380
+
381
+ /* Make every section fill the printable page. Two effects, no layout
382
+ * coercion:
383
+ * 1. The inset frame above wraps the full page edge instead of
384
+ * shrink-wrapping content.
385
+ * 2. The flex-centered layouts below have a tall container to center
386
+ * within (without a tall container, `justify-content: center` has
387
+ * nothing to center against). */
388
+ html:not(.print-pdf) .reveal .slides section {
389
+ min-height: 100vh !important;
390
+ box-sizing: border-box !important;
391
+ }
392
+
393
+ /* Apply `--table-scale` to td/th in print. Reveal's print stylesheet forces
394
+ * `font-size: 20pt !important` on all td, which would otherwise override
395
+ * the screen rule. Scale font AND padding together — scaling font alone
396
+ * leaves the row height dominated by static cell padding, dampening the
397
+ * shrink effect. */
398
+ html:not(.print-pdf) .reveal section[data-layout="table"] td,
399
+ html:not(.print-pdf) .reveal section[data-layout="table"] th {
400
+ font-size: calc(20pt * var(--table-scale, 1)) !important;
401
+ padding: calc(0.5rem * var(--table-scale, 1)) calc(1rem * var(--table-scale, 1)) !important;
402
+ }
403
+
404
+ /* Reveal's print stylesheet forces every section to `display: block`,
405
+ * stripping the on-screen flex centering from the layouts that use it.
406
+ * Restore `display: flex` for those layouts ONLY — `flex-direction`,
407
+ * `justify-content`, and `align-items` come from layouts.css unchanged.
408
+ * Layouts not listed here stay as `display: block` and sit at the top of
409
+ * the page; that preserves the natural flow of layouts that depend on it
410
+ * (image-text and two-column rely on inline-block siblings, content/agenda/
411
+ * comparison/metric-grid/code/table all flow as block stacks). */
412
+ html:not(.print-pdf) .reveal .slides section[data-layout="title"],
413
+ html:not(.print-pdf) .reveal .slides section[data-layout="section"],
414
+ html:not(.print-pdf) .reveal .slides section[data-layout="closing"],
415
+ html:not(.print-pdf) .reveal .slides section[data-layout="quote"],
416
+ html:not(.print-pdf) .reveal .slides section[data-layout="big-stat"],
417
+ html:not(.print-pdf) .reveal .slides section[data-layout="pull-quote"] {
418
+ display: flex !important;
419
+ }
420
+ }
421
+
422
+ /* ── Print-view polish (`?print-pdf` URL — Reveal stacks each slide as a `.pdf-page`)
423
+ * Reveal sizes `.pdf-page` to slide dims and stacks them flush-left with no
424
+ * separation. Center each one and draw a hairline frame so the print preview
425
+ * reads like a sheaf of printed pages, not a left-aligned wall of slides.
426
+ *
427
+ * Why box-shadow inset and not `border`: Reveal forces `@page { margin: 0 }`,
428
+ * so an outside border would land at the edge of paper and be clipped by the
429
+ * printer's unprintable margin. An inset shadow draws the line *inside* the
430
+ * page box and survives the print. */
431
+ html.print-pdf .reveal .slides .pdf-page {
432
+ box-shadow: inset 0 0 0 1px var(--neutral-300);
433
+ }
434
+ @media screen {
435
+ html.print-pdf .reveal .slides .pdf-page {
436
+ margin: 16px auto !important;
437
+ }
438
+ }
@@ -213,9 +213,13 @@
213
213
  letter-spacing: var(--tracking-wide);
214
214
  }
215
215
 
216
- /* ── 9. table ── styling already in base; just ensure breathing room */
216
+ /* ── 9. table ── styling already in base; just ensure breathing room.
217
+ * `--table-scale` is set per-slide by build.ts (`injectTableScale`) when row
218
+ * count exceeds 6 — multiplies the cell font-size to keep dense tables on a
219
+ * single page. Defaults to 1 (no change) when the var is unset. */
217
220
  .reveal section[data-layout="table"] table {
218
221
  margin-top: var(--space-3);
222
+ font-size: calc(var(--text-sm) * var(--table-scale, 1));
219
223
  }
220
224
 
221
225
  /* ── 10. comparison ── 2-3 option boxes with numbered badge + top stripe */
@@ -265,7 +269,12 @@
265
269
  max-height: 65vh;
266
270
  margin: var(--space-2) 0;
267
271
  }
268
- .reveal section[data-layout="code"] pre code { font-size: 0.9em; }
272
+ /* Code font size scales down for long blocks. The build script sets
273
+ --code-scale on each code-layout section based on line count: 1.0 for ≤15
274
+ lines, 0.6 for ≥25 lines, linear in between. Defaults to 1.0 if unset. */
275
+ .reveal section[data-layout="code"] pre code {
276
+ font-size: calc(0.9em * var(--code-scale, 1));
277
+ }
269
278
 
270
279
  /* ── 12. big-stat ── One giant number + a muted caption beneath. Use sparingly.
271
280
  * Author surface:
@@ -8,7 +8,8 @@
8
8
  // <out>/<deck-name>/<deck-name>.md concatenated slides (written first)
9
9
  // <out>/<deck-name>/<deck-name>.html self-contained presentation
10
10
  //
11
- // --out defaults to process.cwd(). The deck-name subdir is always created
11
+ // --out defaults to the deck-dir itself (output lands at <deck-dir>/<deck-name>/,
12
+ // which the scaffolder gitignores). The deck-name subdir is always created
12
13
  // inside --out, even when --out is explicitly provided. Existing files in
13
14
  // the subdir are preserved unless --force is passed.
14
15
 
@@ -19,6 +20,71 @@ import { dataUri, escapeForTextarea, readText } from "./lib/inline";
19
20
  import { THEME_BASE, VENDOR_REVEAL } from "./lib/paths";
20
21
  import { getTemplate } from "./lib/registry";
21
22
 
23
+ // Walk markdown line-by-line, skipping fenced code blocks, applying `transform`
24
+ // to each non-fenced line. Used by both the concat-step path rewrite and the
25
+ // HTML-step image inliner so neither touches example image syntax inside ``` blocks.
26
+ async function mapMarkdownOutsideFences(
27
+ md: string,
28
+ transform: (line: string) => string | Promise<string>
29
+ ): Promise<string> {
30
+ const out: string[] = [];
31
+ let inFence = false;
32
+ for (const line of md.split("\n")) {
33
+ if (/^```/.test(line)) {
34
+ inFence = !inFence;
35
+ out.push(line);
36
+ continue;
37
+ }
38
+ out.push(inFence ? line : await transform(line));
39
+ }
40
+ return out.join("\n");
41
+ }
42
+
43
+ // Rewrite `../assets/X` (the natural relative path from a `slides/*.md` file)
44
+ // to `assets/X` so the concatenated markdown — which lives at the deck root —
45
+ // resolves images correctly when previewed directly. Bare `X.png` references
46
+ // (no path) are left to the doctor to flag; we don't guess where they live.
47
+ function rewriteImageRefsForConcat(md: string): Promise<string> {
48
+ return mapMarkdownOutsideFences(md, (line) =>
49
+ line.replace(/(!\[[^\]]*\]\()([^)]+)(\))/g, (whole, open, ref, close) => {
50
+ const trimmed = ref.trim();
51
+ if (/^(https?:|data:)/i.test(trimmed)) return whole;
52
+ if (trimmed.startsWith("../assets/")) {
53
+ return `${open}${trimmed.slice(3)}${close}`;
54
+ }
55
+ return whole;
56
+ })
57
+ );
58
+ }
59
+
60
+ // Inline every local image reference in the concatenated markdown as a data: URI
61
+ // so the resulting HTML is truly self-contained (emailable, USB-stickable).
62
+ // Resolves refs against `deckDir` (the concat-md's location). Missing files are
63
+ // left untouched — the doctor flags them; build doesn't crash on author errors.
64
+ async function inlineImagesInMarkdown(md: string, deckDir: string): Promise<string> {
65
+ return mapMarkdownOutsideFences(md, async (line) => {
66
+ const matches = [...line.matchAll(/(!\[[^\]]*\]\()([^)]+)(\))/g)];
67
+ if (matches.length === 0) return line;
68
+ let result = line;
69
+ for (const m of matches) {
70
+ const [whole, open, ref, close] = m;
71
+ const trimmed = ref.trim();
72
+ if (/^(https?:|data:)/i.test(trimmed)) continue;
73
+ const abs = resolve(deckDir, trimmed);
74
+ try {
75
+ await access(abs, fsConst.F_OK);
76
+ } catch {
77
+ continue;
78
+ }
79
+ // dataUri returns `url("data:...")` for CSS use; strip the `url("…")` wrapper for <img>.
80
+ const wrapped = await dataUri(abs);
81
+ const inner = wrapped.replace(/^url\("/, "").replace(/"\)$/, "");
82
+ result = result.replace(whole, `${open}${inner}${close}`);
83
+ }
84
+ return result;
85
+ });
86
+ }
87
+
22
88
  const ASPECTS: Record<string, [number, number]> = {
23
89
  "16:9": [1920, 1080],
24
90
  "16:10": [1920, 1200],
@@ -65,6 +131,62 @@ function deckSlug(deckDir: string): string {
65
131
  return slug || "deck";
66
132
  }
67
133
 
134
+ // Build-time injection: code-layout slides with > 15 lines of code get a
135
+ // `style="--code-scale: X"` attribute baked into the slide directive. CSS in
136
+ // layouts.css multiplies the base code font by this variable. Linear from 1.0
137
+ // at 15 lines to 0.6 at 25 lines; clamped at 0.6 beyond. Build-time keeps the
138
+ // attribute on the rendered <section>, so navigation/Highlight re-runs cannot
139
+ // strip it.
140
+ function injectCodeScale(slideMarkdown: string): string {
141
+ const layoutRe = /<!--\s*\.slide:\s*data-layout="code"([^>]*)-->/i;
142
+ const layoutMatch = layoutRe.exec(slideMarkdown);
143
+ if (!layoutMatch) return slideMarkdown;
144
+
145
+ const codeRe = /```[^\n]*\n([\s\S]*?)\n```/;
146
+ const codeMatch = codeRe.exec(slideMarkdown);
147
+ if (!codeMatch) return slideMarkdown;
148
+
149
+ const lines = codeMatch[1].split("\n").length;
150
+ if (lines <= 15) return slideMarkdown;
151
+
152
+ const scale = Math.max(0.6, 1 - (lines - 15) * 0.04).toFixed(2);
153
+ // Don't double-inject if a previous build already set it.
154
+ const extras = layoutMatch[1].replace(/\s+style="--code-scale:\s*[^"]+"/i, "").trim();
155
+ const attrs = extras
156
+ ? `${extras} style="--code-scale: ${scale}"`
157
+ : `style="--code-scale: ${scale}"`;
158
+ const newDirective = `<!-- .slide: data-layout="code" ${attrs} -->`;
159
+ return slideMarkdown.replace(layoutMatch[0], newDirective);
160
+ }
161
+
162
+ // Build-time injection: table-layout slides with > 4 rows get a
163
+ // `style="--table-scale: X"` attribute baked into the slide directive. CSS
164
+ // multiplies cell font-size AND cell padding by this var so both shrink
165
+ // together (font-only shrinking is dampened by static padding). Linear from
166
+ // 1.0 at 4 rows to 0.6 at ≥10 rows. Mirrors `injectCodeScale`.
167
+ function injectTableScale(slideMarkdown: string): string {
168
+ const layoutRe = /<!--\s*\.slide:\s*data-layout="table"([^>]*)-->/i;
169
+ const layoutMatch = layoutRe.exec(slideMarkdown);
170
+ if (!layoutMatch) return slideMarkdown;
171
+
172
+ // Count markdown table rows (lines starting with `|`) excluding the
173
+ // separator (`| --- | --- |`) which doesn't render as a row.
174
+ const sepRe = /^\s*\|(\s*:?-+:?\s*\|)+\s*$/;
175
+ let rows = 0;
176
+ for (const line of slideMarkdown.split("\n")) {
177
+ if (/^\s*\|/.test(line) && !sepRe.test(line)) rows++;
178
+ }
179
+ if (rows <= 4) return slideMarkdown;
180
+
181
+ const scale = Math.max(0.6, 1 - (rows - 4) * 0.067).toFixed(2);
182
+ const extras = layoutMatch[1].replace(/\s+style="--table-scale:\s*[^"]+"/i, "").trim();
183
+ const attrs = extras
184
+ ? `${extras} style="--table-scale: ${scale}"`
185
+ : `style="--table-scale: ${scale}"`;
186
+ const newDirective = `<!-- .slide: data-layout="table" ${attrs} -->`;
187
+ return slideMarkdown.replace(layoutMatch[0], newDirective);
188
+ }
189
+
68
190
  async function buildConcat(deckDir: string): Promise<string> {
69
191
  const slidesDir = join(deckDir, "slides");
70
192
  if (await exists(slidesDir)) {
@@ -73,7 +195,8 @@ async function buildConcat(deckDir: string): Promise<string> {
73
195
  throw new Error(`slides/ is empty at ${slidesDir}`);
74
196
  }
75
197
  const parts = await Promise.all(files.map((f) => readText(join(slidesDir, f))));
76
- return `${parts.map((p) => p.trim()).join("\n\n---\n\n")}\n`;
198
+ const joined = `${parts.map((p) => injectTableScale(injectCodeScale(p.trim()))).join("\n\n---\n\n")}\n`;
199
+ return rewriteImageRefsForConcat(joined);
77
200
  }
78
201
  const legacy = join(deckDir, "content.md");
79
202
  if (await exists(legacy)) {
@@ -90,7 +213,7 @@ async function main() {
90
213
  }
91
214
  const deckDir = resolve(argv[0]);
92
215
 
93
- let outRoot = process.cwd();
216
+ let outRoot = deckDir;
94
217
  let force = false;
95
218
  for (let i = 1; i < argv.length; i++) {
96
219
  if (argv[i] === "--out") outRoot = resolve(argv[++i]);
@@ -110,7 +233,10 @@ async function main() {
110
233
  }
111
234
 
112
235
  const slug = deckSlug(deckDir);
113
- const outDir = join(outRoot, slug);
236
+ // When --out is the deck-dir itself (the default), write directly into it —
237
+ // no extra <slug>/ subdir. Otherwise create the subdir so multiple decks
238
+ // can coexist under one shared --out.
239
+ const outDir = resolve(outRoot) === deckDir ? deckDir : join(outRoot, slug);
114
240
  const concatPath = join(outDir, `${slug}.md`);
115
241
  const htmlPath = join(outDir, `${slug}.html`);
116
242
 
@@ -145,7 +271,7 @@ async function main() {
145
271
  const skeleton = await readText(join(THEME_BASE, "skeleton.html"));
146
272
  const revealCss = await readText(join(VENDOR_REVEAL, "reveal.css"));
147
273
  const highlightCss = await readText(
148
- join(VENDOR_REVEAL, "plugin", "highlight", "monokai.css")
274
+ join(VENDOR_REVEAL, "plugin", "highlight", "github-dark.css")
149
275
  );
150
276
  const revealJs = await readText(join(VENDOR_REVEAL, "reveal.js"));
151
277
  const markdownJs = await readText(
@@ -156,7 +282,11 @@ async function main() {
156
282
  );
157
283
  const notesJs = await readText(join(VENDOR_REVEAL, "plugin", "notes", "notes.js"));
158
284
 
159
- const contentMd = await readFile(concatPath, "utf8");
285
+ // Read the concat back from disk, then inline image refs as data: URIs so
286
+ // the HTML is self-contained. The on-disk concat keeps plain `assets/X` paths
287
+ // for direct markdown preview; only the HTML embeds full image bytes.
288
+ const contentMdRaw = await readFile(concatPath, "utf8");
289
+ const contentMd = await inlineImagesInMarkdown(contentMdRaw, deckDir);
160
290
 
161
291
  let deckOverridesCss = "";
162
292
  const overridesPath = join(deckDir, "overrides.css");