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
@@ -1,13 +1,28 @@
1
1
  import { AlertTriangle, CheckCircle2, Lightbulb, Target } from "lucide-react";
2
2
  import { Callout } from "@/components/callout";
3
+ import { ComparisonTable } from "@/components/comparison-table";
3
4
  import { CoverPage } from "@/components/cover-page";
4
5
  import { Exhibit } from "@/components/exhibit";
5
6
  import { FindingCard } from "@/components/finding-card";
6
7
  import { RecommendationCard } from "@/components/recommendation-card";
7
8
  import { Section } from "@/components/section";
9
+ import { StatGrid } from "@/components/stat-grid";
10
+ import { TableOfContents } from "@/components/table-of-contents";
8
11
  import { Timeline } from "@/components/timeline";
9
12
  import { reportData } from "@/lib/report-data";
10
13
 
14
+ const sections = [
15
+ { id: "executive-summary", title: "Executive Summary" },
16
+ { id: "situation-assessment", title: "Situation Assessment" },
17
+ { id: "key-findings", title: "Key Findings" },
18
+ { id: "risk-analysis", title: "Risk Analysis" },
19
+ { id: "strategic-opportunity", title: "Strategic Opportunity" },
20
+ { id: "recommendations", title: "Strategic Recommendations" },
21
+ { id: "target-state", title: "Target State Vision" },
22
+ { id: "roadmap", title: "Implementation Roadmap" },
23
+ { id: "call-to-action", title: "Call to Action" },
24
+ ];
25
+
11
26
  export default function ReportPage() {
12
27
  const data = reportData;
13
28
 
@@ -23,29 +38,29 @@ export default function ReportPage() {
23
38
  />
24
39
 
25
40
  <div className="report-container">
26
- <Section title="Executive Summary">
41
+ <TableOfContents items={sections} />
42
+
43
+ <Section id="executive-summary" title="Executive Summary">
27
44
  <p className="text-lg mb-6">{data.executiveSummary.context}</p>
28
45
 
29
- <Exhibit number={1} title="Assessment Methodology">
30
- <div className="flex items-start gap-8">
31
- <div>
32
- <p className="text-3xl font-bold text-primary">
33
- {data.executiveSummary.methodology.interviewCount}
34
- </p>
35
- <p className="text-sm text-muted">Interviews Conducted</p>
36
- </div>
37
- <div className="flex-1">
38
- <p className="text-sm font-semibold text-foreground mb-2">
39
- Roles Interviewed:
46
+ <StatGrid
47
+ stats={[
48
+ {
49
+ value: String(data.executiveSummary.methodology.interviewCount),
50
+ label: "Interviews",
51
+ },
52
+ { value: "—", label: "Headline metric", caption: "replace with your own" },
53
+ { value: "—", label: "Headline metric", caption: "replace with your own" },
54
+ ]}
55
+ />
56
+
57
+ <Exhibit number={1} title="Roles Interviewed">
58
+ <div className="grid grid-cols-2 gap-1">
59
+ {data.executiveSummary.methodology.roles.map((role) => (
60
+ <p key={role} className="text-sm text-muted">
61
+ {role}
40
62
  </p>
41
- <div className="grid grid-cols-2 gap-1">
42
- {data.executiveSummary.methodology.roles.map((role) => (
43
- <p key={role} className="text-sm text-muted">
44
- {role}
45
- </p>
46
- ))}
47
- </div>
48
- </div>
63
+ ))}
49
64
  </div>
50
65
  </Exhibit>
51
66
 
@@ -75,7 +90,7 @@ export default function ReportPage() {
75
90
  </ul>
76
91
  </Section>
77
92
 
78
- <Section title="Situation Assessment">
93
+ <Section id="situation-assessment" title="Situation Assessment">
79
94
  <div className="grid gap-6">
80
95
  <div>
81
96
  <h3 className="text-lg font-semibold mb-2 flex items-center gap-2">
@@ -95,7 +110,7 @@ export default function ReportPage() {
95
110
  </div>
96
111
  </Section>
97
112
 
98
- <Section title="Key Findings">
113
+ <Section id="key-findings" title="Key Findings">
99
114
  <p className="text-muted mb-6">
100
115
  Our analysis identified {data.findings.length} significant findings that
101
116
  require attention. Each finding is supported by evidence gathered during our
@@ -108,7 +123,7 @@ export default function ReportPage() {
108
123
  </div>
109
124
  </Section>
110
125
 
111
- <Section title="Risk Analysis">
126
+ <Section id="risk-analysis" title="Risk Analysis">
112
127
  <div className="grid gap-6">
113
128
  <Exhibit number={3} title="Existential Risks">
114
129
  <div className="space-y-3">
@@ -142,7 +157,7 @@ export default function ReportPage() {
142
157
  </div>
143
158
  </Section>
144
159
 
145
- <Section title="Strategic Opportunity">
160
+ <Section id="strategic-opportunity" title="Strategic Opportunity">
146
161
  <Callout label="The Path Forward">{data.strategicOpportunity.goodNews}</Callout>
147
162
 
148
163
  <h3 className="text-lg font-semibold mt-6 mb-3 flex items-center gap-2">
@@ -162,7 +177,7 @@ export default function ReportPage() {
162
177
  </ul>
163
178
  </Section>
164
179
 
165
- <Section title="Strategic Recommendations">
180
+ <Section id="recommendations" title="Strategic Recommendations">
166
181
  <p className="text-muted mb-6">
167
182
  Recommendations are prioritized by urgency and impact.
168
183
  </p>
@@ -173,9 +188,21 @@ export default function ReportPage() {
173
188
  </div>
174
189
  </Section>
175
190
 
176
- <Section title="Target State Vision">
191
+ <Section id="target-state" title="Target State Vision">
177
192
  <p className="text-lg mb-6">{data.targetState.description}</p>
178
193
 
194
+ <ComparisonTable
195
+ leftLabel="Today"
196
+ rightLabel="Target"
197
+ rows={[
198
+ {
199
+ metric: "Replace with metric",
200
+ left: "Current state value",
201
+ right: "Target state value",
202
+ },
203
+ ]}
204
+ />
205
+
179
206
  <Exhibit number={5} title="Key Capabilities Enabled">
180
207
  <div className="grid md:grid-cols-3 gap-4">
181
208
  {data.targetState.keyCapabilities.map((capability) => (
@@ -201,7 +228,7 @@ export default function ReportPage() {
201
228
  </ul>
202
229
  </Section>
203
230
 
204
- <Section title="Implementation Roadmap">
231
+ <Section id="roadmap" title="Implementation Roadmap">
205
232
  <p className="text-muted mb-6">
206
233
  The transformation should be executed in phases, with clear milestones and
207
234
  decision points.
@@ -211,7 +238,7 @@ export default function ReportPage() {
211
238
  </Exhibit>
212
239
  </Section>
213
240
 
214
- <Section title="Call to Action">
241
+ <Section id="call-to-action" title="Call to Action">
215
242
  <div className="bg-primary/5 rounded-2xl p-8 border border-primary/20">
216
243
  <h3 className="text-xl font-semibold mb-4">Immediate Next Steps</h3>
217
244
  <ol className="space-y-3 mb-6">
@@ -0,0 +1,40 @@
1
+ interface ComparisonRow {
2
+ metric: string;
3
+ left: string;
4
+ right: string;
5
+ }
6
+
7
+ interface ComparisonTableProps {
8
+ leftLabel: string;
9
+ rightLabel: string;
10
+ rows: ComparisonRow[];
11
+ metricLabel?: string;
12
+ }
13
+
14
+ export function ComparisonTable({
15
+ leftLabel,
16
+ rightLabel,
17
+ rows,
18
+ metricLabel = "Metric",
19
+ }: ComparisonTableProps) {
20
+ return (
21
+ <table className="comparison-table">
22
+ <thead>
23
+ <tr>
24
+ <th>{metricLabel}</th>
25
+ <th>{leftLabel}</th>
26
+ <th>{rightLabel}</th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ {rows.map((row) => (
31
+ <tr key={row.metric}>
32
+ <td className="metric">{row.metric}</td>
33
+ <td>{row.left}</td>
34
+ <td>{row.right}</td>
35
+ </tr>
36
+ ))}
37
+ </tbody>
38
+ </table>
39
+ );
40
+ }
@@ -1,14 +1,15 @@
1
1
  import { cn } from "@/lib/utils";
2
2
 
3
3
  interface SectionProps {
4
+ id?: string;
4
5
  title: string;
5
6
  children: React.ReactNode;
6
7
  className?: string;
7
8
  }
8
9
 
9
- export function Section({ title, children, className }: SectionProps) {
10
+ export function Section({ id, title, children, className }: SectionProps) {
10
11
  return (
11
- <section className={cn("report-section", className)}>
12
+ <section id={id} className={cn("report-section", className)}>
12
13
  <h2>{title}</h2>
13
14
  {children}
14
15
  </section>
@@ -0,0 +1,26 @@
1
+ interface Stat {
2
+ value: string;
3
+ label: string;
4
+ caption?: string;
5
+ }
6
+
7
+ interface StatGridProps {
8
+ stats: Stat[];
9
+ }
10
+
11
+ export function StatGrid({ stats }: StatGridProps) {
12
+ return (
13
+ <div
14
+ className="stat-grid"
15
+ style={{ gridTemplateColumns: `repeat(${stats.length}, minmax(0, 1fr))` }}
16
+ >
17
+ {stats.map((s) => (
18
+ <div key={s.label} className="stat">
19
+ <div className="stat-value">{s.value}</div>
20
+ <div className="stat-label">{s.label}</div>
21
+ {s.caption && <div className="stat-caption">{s.caption}</div>}
22
+ </div>
23
+ ))}
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,27 @@
1
+ interface TocItem {
2
+ id: string;
3
+ title: string;
4
+ }
5
+
6
+ interface TableOfContentsProps {
7
+ items: TocItem[];
8
+ title?: string;
9
+ }
10
+
11
+ export function TableOfContents({ items, title = "Contents" }: TableOfContentsProps) {
12
+ return (
13
+ <nav className="toc">
14
+ <h2>{title}</h2>
15
+ <ol>
16
+ {items.map((item, i) => (
17
+ <li key={item.id}>
18
+ <a href={`#${item.id}`}>
19
+ <span className="toc-number">{(i + 1).toString().padStart(2, "0")}</span>
20
+ <span className="toc-title">{item.title}</span>
21
+ </a>
22
+ </li>
23
+ ))}
24
+ </ol>
25
+ </nav>
26
+ );
27
+ }
@@ -59,9 +59,10 @@ Authoring this way means: a malformed edit only takes down its own slide, slides
59
59
  Per-slide conventions (inside each file):
60
60
  - Speaker notes: lines starting with `Note:`.
61
61
  - Layout directive: `<!-- .slide: data-layout="..." -->` at the top.
62
+ - Image references: `![alt](../assets/foo.png)` — the natural relative path from `slides/*.md` to the deck's `assets/` folder. The build rewrites this to `assets/foo.png` in the concatenated `<deck-name>.md` (which sits at the deck root, so the path stays valid for direct preview), and inlines the image bytes as a `data:` URI in the HTML so the output is truly self-contained (emailable, USB-stickable). Remote refs (`https://…`) and `data:` URIs pass through untouched.
62
63
  - See "Layouts" below for the available layout names.
63
64
 
64
- Backwards compatible: if `slides/` doesn't exist, the build falls back to a single `<deck-dir>/content.md` with `---` separators between slides.
65
+ Backwards compatible: if `slides/` doesn't exist, the build falls back to a single `<deck-dir>/content.md` with `---` separators between slides. Image refs there should use `assets/foo.png` (root-relative), since `content.md` already lives at the deck root.
65
66
 
66
67
  ### Step 3: Build
67
68
 
@@ -69,12 +70,14 @@ Backwards compatible: if `slides/` doesn't exist, the build falls back to a sing
69
70
  bun ~/.pal/skills/presentation/tools/build.ts <deck-dir> [--out <dir>] [--force]
70
71
  ```
71
72
 
72
- Output goes to `<out>/<deck-name>/`, where `<deck-name>` = the basename of `<deck-dir>`:
73
+ Output files (where `<deck-name>` = basename of `<deck-dir>`):
73
74
 
74
75
  - `<deck-name>.md` — the concatenated source (all `slides/*.md` joined with `---` separators), written to disk **first** so you can inspect, diff, or feed it into other tooling.
75
76
  - `<deck-name>.html` — the self-contained presentation (CSS, JS, fonts, logo all inlined). Email it, USB-stick it, host it anywhere.
76
77
 
77
- Defaults: `--out` defaults to the current working directory. The `<deck-name>/` subdir is always created even when `--out` is explicit so multiple decks can coexist in one folder.
78
+ Default location: `--out` defaults to the **deck-dir itself**, and the files land flat at `<deck-dir>/<deck-name>.{html,md}` next to your slides, regardless of where you ran the build from. The scaffolder's `.gitignore` already covers them.
79
+
80
+ Override: pass `--out <dir>` to redirect elsewhere. When `--out` is *not* the deck-dir, a `<deck-name>/` subdir is created under it so multiple decks can coexist in one collection folder.
78
81
 
79
82
  `--force` overwrites an existing build. Without it, the build refuses to clobber `<deck-name>.{md,html}`.
80
83
 
@@ -90,7 +93,8 @@ Catches authoring failures before you ever open the browser:
90
93
  - Title or subtitle exceeding the visual budget (h1 > 60 chars, h2 > 100).
91
94
  - Layout-content mismatch — `comparison` without a `<div class="compare">`, `metric-grid` without `<div class="metrics">`, `two-column` missing column wrappers, etc.
92
95
  - Overflow heuristics — `agenda` > 10 items, `content` > 7 bullets, `code` block > 25 lines, `table` > 10 rows, `metric-grid` ≠ 3 metrics.
93
- - Image referenced via `![](assets/...)` but the file is missing.
96
+ - **Visual-line budget**: any bullet-bearing slide (`content`, `agenda`, `comparison`, `two-column`) with > 10 flattened list lines (top-level + sub-bullets combined). 10 fits cleanly; 11+ overflows even when each line is short.
97
+ - Image referenced via `![](../assets/...)` (slide-relative) or `![](assets/...)` (root-relative, for legacy `content.md` decks) but the file is missing.
94
98
  - Layout requirements — `big-stat` without an h1, `quote` / `pull-quote` without a `> blockquote`.
95
99
 
96
100
  Exit codes: `0` = clean, `1` = errors found (or warnings under `--strict`), `2` = usage error. Run before each build in your iteration loop, or wire it into a pre-commit hook.
@@ -112,10 +116,105 @@ Open `<out>/<deck-name>/<deck-name>.html` in your browser. Iterate by editing a
112
116
  └── assets/ # images / videos referenced from slides/*.md
113
117
  ```
114
118
 
115
- Build output is **never** written inside `<deck-dir>` it lands in `<out>/<deck-name>/` (default `<cwd>/<deck-name>/`). The scaffolder's `.gitignore` covers the case of running build from inside the deck-dir.
119
+ Build output lands flat inside the deck-dir by default (`<deck-dir>/<deck-name>.{html,md}`), or under `<out>/<deck-name>/` when `--out` is explicit. Both shapes are covered by the scaffolder's `.gitignore`.
116
120
 
117
121
  (Legacy: a single `content.md` at the deck root still works — see Step 2.)
118
122
 
123
+ ## Content principles
124
+
125
+ The doctor enforces *structural* discipline (slide budgets, layout-content match, missing assets). These rules are about *content* — what goes on the slide and in the notes. They apply to every deck regardless of type. For type-specific rules, see the "see also" links at the bottom of this section.
126
+
127
+ ### Core rules
128
+
129
+ - **A slide carries what the *learner* takes away — never what happens in the room.** "We will discuss CI/CD agents" is not a slide; "CodeRabbit and CodeAnt cost ~$15/dev/mo at this scale" is.
130
+ - **Less is more on bullets.** The doctor caps `content` at 7 bullets; the content rule is stricter — 3–5 is usually right. One bullet is not a slide; either expand or fold into the previous one.
131
+ - **Two ideas → two slides.** If a slide carries two takeaways, split.
132
+ - **No sentences on slides or in notes.** Exceptions: quotes, code comments, single callout sentences. Everything else is bullets, including notes.
133
+ - **A title-only slide is valid only as a `section` divider.** Single-word slides and single-bullet slides are not valid — split or expand.
134
+ - **Vocabulary consistency.** Pick one word per concept ("agent" vs "assistant", "tool" vs "capability") and use it everywhere. Drift confuses the audience.
135
+ - **Layout choice = content choice, not decoration.** `big-stat` says "this number *is* the point." `comparison` says "you have to choose between these." `quote` says "someone earned the right to say this." Wrong layout muddies the message.
136
+ - **Define jargon before using it.** First use of a non-obvious term gets a parenthesized gloss or its own glossary slide. After that, no.
137
+
138
+ ### Bullet & sub-bullet structure
139
+
140
+ - **Top-level bullet = one fact or one claim.** 6–12 words. If you need more, split or use a sub-bullet.
141
+ - **Sub-bullets = elaboration of the parent.** A list, an example, a clarification. 2–8 words each. Don't start a new claim at a sub-bullet — that's a top-level bullet.
142
+ - **Never use em-dash continuations to extend a bullet.** `- Foo — bar — baz` is prose pretending to be a bullet. Convert to:
143
+ ```markdown
144
+ - Foo
145
+ - bar
146
+ - baz
147
+ ```
148
+ Em-dash is reserved for: title qualifiers ("Block 4 — Landscape"), and anticipated Q&A in notes (`"Question?" — short answer`).
149
+ - **Stable parallel structure inside a bullet group.** If bullet 1 starts with a verb, bullets 2–N start with verbs. If bullet 1 is `Name: description`, the rest match.
150
+ - **Concrete over abstract.** Show paths, file names, numbers, command snippets in backticks. "the project file" is weaker than `./CLAUDE.md`.
151
+
152
+ ### Notes structure
153
+
154
+ Notes are the speaker's working surface during delivery. They must be scannable, not readable. Strict format:
155
+
156
+ 1. **Source links first**, one per line, before any explanation. Multiple links are fine if the slide cites multiple sources. The speaker should never scroll to find a link they're about to open in the browser.
157
+ 2. **Named beats as top-level bullets.** A "beat" is a named chunk of context — `Eval setup`, `Headline numbers`, `Why X wins`, `Common output`, `Anticipated questions`. The beat name lets the speaker jump.
158
+ 3. **Sub-bullets under each beat.** Same 2–8 word budget. Lists, edge cases, examples. No prose.
159
+ 4. **Quotes nest under a `- Quote` beat** as a markdown blockquote (`>`). Quotes are the only place full sentences are allowed in notes.
160
+ 5. **Anticipated Q&A near the end.** Format: `- "Question?" — short answer`. The em-dash here separates the quote from the answer; this is the legal use.
161
+ 6. **Forward references are terse.** "more in Day 2 when you build your own", "see Block 2 for X". Not "we'll discuss this later" prose.
162
+
163
+ ### Voice
164
+
165
+ - **Declarative, not hedging.** "Anthropic's holdout" beats "Anthropic has chosen a different approach." If you mean it, say it; if you don't, cut it.
166
+ - **Opinionated where the room expects opinion.** Workshops are taught, not narrated. State which option you'd pick and why.
167
+ - **Concrete identifiers visible.** File paths, version numbers, URLs, exact tool names. The audience should be able to type what's on the slide and have it work.
168
+
169
+ ### Examples
170
+
171
+ **Bad slide — describes the room, not the takeaway:**
172
+
173
+ ```markdown
174
+ <!-- .slide: data-layout="content" -->
175
+ ## Code Review Agents
176
+
177
+ - We will look at three vendors
178
+ - Pricing comparison
179
+ - Live demo of CodeRabbit
180
+ ```
181
+
182
+ **Good slide — what the learner walks away with:**
183
+
184
+ ```markdown
185
+ <!-- .slide: data-layout="content" -->
186
+ ## Code review agents — when each wins
187
+
188
+ - CodeRabbit: best for large PRs, weakest at C# specifics
189
+ - CodeAnt: tightest C# rules, no PR summary
190
+ - Custom (Claude Code in CI): controllable, you pay per token
191
+ ```
192
+
193
+ **Bad notes — prose, no source link:**
194
+
195
+ ```markdown
196
+ Note: We should talk about EchoLeak here. It was a vulnerability in Microsoft 365 Copilot discovered in 2025 that allowed prompt injection through email content. The attack worked by sending a crafted email that the Copilot assistant would later read and execute instructions from. This is a good example of why we need to think about prompt injection in agentic systems.
197
+ ```
198
+
199
+ **Good notes — link first, bullets, anticipated questions:**
200
+
201
+ ```markdown
202
+ Note:
203
+ - [EchoLeak (CVE-2025-32711) — Microsoft writeup](https://msrc.microsoft.com/...)
204
+ - Zero-click prompt injection in M365 Copilot via crafted email
205
+ - Attack surface: any agent that reads attacker-controlled content
206
+ - Mitigations
207
+ - Tool allowlists, not denylists
208
+ - Human-in-loop on data exfil tools
209
+ - "Could this happen with Claude Code?" — yes, via any tool that reads external text (web fetch, MCP server, log file)
210
+ ```
211
+
212
+ ### See also (type-specific rules)
213
+
214
+ - **Workshops, training, hands-on sessions:** [`WORKSHOP.md`](WORKSHOP.md)
215
+
216
+ (Future: `PITCH.md`, `LECTURE.md`, `INTERNAL_REVIEW.md` — coming soon.)
217
+
119
218
  ## Content conventions
120
219
 
121
220
  ```markdown
@@ -188,6 +287,26 @@ Composable on any `<img>` or wrapper element:
188
287
  | `image-duotone` | Brand-tinted monochrome via filter chain |
189
288
  | `image-overlay` | Adds a brand-gradient scrim from transparent → primary at 75% bottom |
190
289
 
290
+ ## Text-alignment utilities
291
+
292
+ Composable on any block element. Use these instead of inline `style="text-align: …"` — Reveal's print stylesheet forces `text-align: left !important` on every `div`/`p`/`ol`/`ul`, which silently kills inline alignment styles in print (the on-screen view looks fine, the printed PDF lands left-aligned).
293
+
294
+ | Class | Effect |
295
+ |---|---|
296
+ | `text-center` | Center inline content (typical use: wrapping a centered `<img>` or caption) |
297
+ | `text-right` | Right-align inline content |
298
+ | `text-left` | Left-align inline content (rarely needed; default) |
299
+
300
+ Example — centering an image on a `content` slide:
301
+
302
+ ```markdown
303
+ <div class="text-center">
304
+
305
+ ![Waldo](../assets/waldo.png)
306
+
307
+ </div>
308
+ ```
309
+
191
310
  ## Design tokens
192
311
 
193
312
  The skill ships a token system in `theme-base/base.css`. Templates only declare two anchors (`--brand-primary`, `--brand-accent`); the rest derives via `color-mix(in oklch, …)`.
@@ -0,0 +1,128 @@
1
+ # Workshop-specific content rules
2
+
3
+ Read [`SKILL.md`](SKILL.md) first — the general content principles apply unchanged. This file only adds the rules that are specific to workshops, training sessions, and hands-on formats.
4
+
5
+ A workshop is not a talk. The deck is scaffolding for the room's work, not the work itself. If the slides could be read alone and convey the value, it isn't a workshop — it's a recorded lecture.
6
+
7
+ ## Rules
8
+
9
+ - **Block arc: opener → core → demo/exercise → synthesis.** Every block follows this shape.
10
+ - **Opener** — one slide that earns the block: a stat, a story, a failure case. "Why should you care for the next 45 minutes?"
11
+ - **Core** — the actual material. The teaching slides.
12
+ - **Demo or exercise** — the participants do something or watch something done. Live, not recorded.
13
+ - **Synthesis** — one slide that closes the loop: "what you now know" or "what you can do tomorrow."
14
+ - Skip a beat and the block falls flat. Especially the opener.
15
+ - **Topic shape inside a block — preferred, not required: what → proof → mechanics → advanced → exercise.** A useful default ordering for depth topics. Skip any beat that doesn't fit the topic. Reorder if a different sequence teaches better. Variability is expected; this is the shape to fall back to when nothing better suggests itself.
16
+ - **What** — what it is + landscape framing (who built it, who supports it, who doesn't), if it matters.
17
+ - **Proof** — only if you have a *genuinely* interesting stat, eval, or incident. See the big-stat rule below.
18
+ - **Mechanics** — how it actually works: scopes, locations, semantics, limits.
19
+ - **Advanced** — patterns, extensions, teaser hooks for later blocks/days.
20
+ - **Exercise** — bullets are *principles* (learner takeaway), notes carry facilitation.
21
+ - **Big-stat is for genuinely interesting numbers, not a checkbox.** Use the `big-stat` layout when you have a stat that *changes the room's mind* — a real eval result, a striking incident metric, a non-obvious cost number. Don't manufacture one. A topic without a strong stat skips the proof slide; a generic stat dilutes every later one. Format when used: h1 = the number with `<em class="unit">`, h2 = the comparison-with-context (named alternatives, named source).
22
+ - **~50% slides / ~50% hands-on.** If deck-time exceeds half the block's runtime, cut slides — not exercise time.
23
+ - **Recall check at every block boundary.** A question slide between blocks: "Before we move on — what would you do if X?" One question, no answer, deliberate silence. Forces consolidation.
24
+ - **Question slides as discussion prompts.** Bold question, no answer, no bullets. Use sparingly (2–3 per day max) — overuse breaks the room's trust that you have answers.
25
+ - **Energy curve matters.**
26
+ - Hardest cognitive lift in the morning, before lunch.
27
+ - Live coding right after lunch is malpractice (post-meal energy crash).
28
+ - Demos and discussion in the afternoon.
29
+ - End each day on synthesis or a payoff demo, never on dense content.
30
+ - **Buffer / reserve slides per block.** Every block has 1–2 slides marked optional (in the deck, not separate) used only if pace allows. Lets you stretch comfortably without scrambling. Marker convention: filename suffix `-reserve` (e.g., `045-reserve-extra-eval-example.md`) and a `<!-- reserve -->` comment at the top of the slide so the doctor can flag if any are still flagged at build time.
31
+ - **Exercise slides follow a strict template.**
32
+ - **Title prefix `Exercise — `** so the room sees the mode change.
33
+ - **Bullets = principles, not procedure.** What the learner *takes away* by doing the exercise. Procedure ("5 minutes solo, then we compare two") goes in notes.
34
+ - **Standard note beats, in order:** `Facilitation`, `Common output`, `Common mistakes`, `Anticipated questions`. Use these names verbatim — the speaker scans by them.
35
+ - **Keep principles transferable.** A bullet that only makes sense in the context of this specific repo isn't a principle; demote it to a note example.
36
+
37
+ ## Examples
38
+
39
+ **Bad opener — describes the agenda instead of earning the block:**
40
+
41
+ ```markdown
42
+ <!-- .slide: data-layout="content" -->
43
+ ## Security block
44
+
45
+ - Prompt injection
46
+ - Tool aliasing
47
+ - Credential exposure
48
+ - Hook-based guardrails
49
+ ```
50
+
51
+ **Good opener — a real incident earns the next 90 minutes:**
52
+
53
+ ```markdown
54
+ <!-- .slide: data-layout="big-stat" -->
55
+ # 9 seconds
56
+ ## Replit production database, deleted by an agent
57
+
58
+ Note:
59
+ - [Replit prod-DB deletion — postmortem](https://...)
60
+ - Agent had unscoped DB credentials in its environment
61
+ - "Drop the dev tables" → matched against prod by mistake
62
+ - This is the failure mode the next 90 minutes prevent
63
+ - "Could Claude Code do this?" — yes, if you give it `.env` with prod creds
64
+ ```
65
+
66
+ **Bad recall slide — gives the answer:**
67
+
68
+ ```markdown
69
+ <!-- .slide: data-layout="content" -->
70
+ ## Recap
71
+
72
+ - Tier 1 = enterprise sanctioned
73
+ - Tier 2 = paid + settings off
74
+ - Tier 3 = prohibited
75
+ ```
76
+
77
+ **Good recall slide — forces the room to retrieve:**
78
+
79
+ ```markdown
80
+ <!-- .slide: data-layout="quote" -->
81
+ > Your colleague asks if she can paste a 20-line C# snippet
82
+ > from our payment service into ChatGPT Plus. What do you say?
83
+
84
+ Note:
85
+ - Let the room answer first, ~30 seconds of silence is fine
86
+ - Correct: depends on classification — if Confidential, no; if Internal, sanitize and yes (settings off)
87
+ - Don't give the answer until at least one person tries
88
+ ```
89
+
90
+ **Bad exercise slide — procedure on the slide, no transferable principle:**
91
+
92
+ ```markdown
93
+ <!-- .slide: data-layout="content" -->
94
+ ## Write a CLAUDE.md
95
+
96
+ - Open your favorite repo
97
+ - Spend 5 minutes writing CLAUDE.md
98
+ - We'll pick 2 to share
99
+ - Discuss what worked
100
+ ```
101
+
102
+ **Good exercise slide — principles on the slide, procedure in notes:**
103
+
104
+ ```markdown
105
+ <!-- .slide: data-layout="content" -->
106
+ ## Exercise — write your CLAUDE.md
107
+
108
+ - Capture conventions, not procedures (procedures belong in skills)
109
+ - One fact per line, no prose paragraphs
110
+ - Glossary entries earn their place — only project-specific terms
111
+ - Order matters — most-violated rules first, model reads top-down
112
+ - Commit it; use `.local.md` only for personal overrides
113
+
114
+ Note:
115
+ - Facilitation
116
+ - 5 min solo on a repo they know
117
+ - 2 volunteers show on screen
118
+ - group critiques against the failure modes from prior slides
119
+ - Common output
120
+ - "Use Polly for retries, not custom code"
121
+ - "Tests live in `tests/Unit/`, not `src/__tests__/`"
122
+ - Common mistakes
123
+ - writing instructions that should be skills
124
+ - copying a generic style guide instead of capturing real violations
125
+ - Anticipated questions
126
+ - "Should this be in AGENTS.md or CLAUDE.md?" — symlink and stop choosing
127
+ - "What if there's already one?" — diff against it; usually adds 30%
128
+ ```