Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ A persona is a small, greppable corpus + an identity, split into two owners:

A bundled example: [`personas/product-manager/`](personas/product-manager/).

## Why a teammate, not a prompt
You could paste "act like a security expert" into your prompt. Two reasons not to:

- **Its own context window.** Each persona runs as a native Claude Code **subagent** — Anthropic's own
mechanism — so its deep work happens off your main thread and returns one distilled result instead of
flooding your conversation. Anthropic's research team puts it plainly: *"Each subagent might explore
extensively, using tens of thousands of tokens or more, but returns only a condensed, distilled summary
of its work"* ([context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents)).
- **Structured craft it reads on the job, not a costume.** A persona is a versioned corpus of skills the
agent Reads when a task matches — not vibes baked into a one-off prompt. You adopt the author's
improvements deliberately, and your per-project customizations survive the update.

## Install a teammate as a plugin (no restart)

Install straight into a live Claude Code session:
Expand Down
10 changes: 9 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"files": {
"includes": ["**", "!**/dist", "!**/node_modules", "!**/internal", "!**/spike", "!**/coverage"]
"includes": [
"**",
"!**/dist",
"!**/node_modules",
"!**/internal",
"!**/spike",
"!**/coverage",
"!site"
]
},
"formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 100 },
"linter": { "enabled": true, "rules": { "recommended": true } },
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ it's documented here.**
| skills as the persona's craft (in-body index, Read on demand) | ✅ shipped |
| 11 official personas (product-manager · product-researcher · vc-seed · software-engineer · software-architect · security-engineer · qa · infrastructure · product-marketer · ui-ux-designer · sales) | ✅ shipped |
| security: containment + clobber/drift guards + adversarial suite | ✅ shipped |
| landing site (`site/` — vanilla static, Cloudflare Pages, deploys to truecast.dev) | ✅ shipped |
| robustness: per-persona write-through ledger + lock, atomic swaps, `--force` | ✅ shipped |
| pinning a project to a fixed version (`--pin`) | ⏳ planned |
| `sync` (re-materialize the surface from the cache) | ⏳ planned |
Expand Down
114 changes: 114 additions & 0 deletions site/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* truecast.dev — progressive enhancement only.
The page is fully readable and correct with JS disabled:
- .no-js styles show every .reveal element
- copy buttons leave the command as selectable text when there's no clipboard
All motion here is additionally gated by prefers-reduced-motion. */
(function () {
"use strict";

var doc = document.documentElement;
// Flip from the no-js fallback to the JS-driven (initially hidden) state.
// Done immediately so reveals start hidden, then animate in.
doc.classList.remove("no-js");

var reduceMotion =
window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;

/* ---------- helpers ---------- */
function each(list, fn) {
Array.prototype.forEach.call(list, fn);
}

var reveals = document.querySelectorAll(".reveal");

/* If reduced motion OR no IntersectionObserver: just show everything now.
No entrance animation. */
function showAllNow() {
each(reveals, function (el) {
el.classList.add("is-in");
});
}

/* Reduced motion: also stop the SVG SMIL motion (animateMotion isn't governed
by the CSS animation tokens). pauseAnimations() freezes them at t=0. */
if (reduceMotion) {
each(document.querySelectorAll(".diagram__svg"), function (svg) {
if (typeof svg.pauseAnimations === "function") {
try {
svg.pauseAnimations();
} catch (e) {
/* no-op: CSS already hides the travelling marks as a fallback */
}
}
});
}

if (reduceMotion || !("IntersectionObserver" in window)) {
showAllNow();
} else {
/* ---------- scroll-reveal ---------- */
var io = new IntersectionObserver(
function (entries) {
each(entries, function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
el.classList.add("is-in");
io.unobserve(el);
});
},
{ threshold: 0.18, rootMargin: "0px 0px -8% 0px" }
);

each(reveals, function (el) {
io.observe(el);
});
}

/* ---------- copy-to-clipboard (unchanged behavior) ---------- */
if (navigator.clipboard) {
var buttons = document.querySelectorAll(".copy[data-copy-target]");

var textFor = function (el) {
var parts = el.querySelectorAll(".cmd__text");
if (parts.length) {
return Array.prototype.map
.call(parts, function (p) {
return p.textContent.trim();
})
.join("\n");
}
return el.textContent.trim();
};

each(buttons, function (btn) {
btn.addEventListener("click", function () {
var target = document.getElementById(
btn.getAttribute("data-copy-target")
);
if (!target) return;
var label = btn.querySelector(".copy__label");
var defaultText = label ? label.getAttribute("data-copy-default") : null;

navigator.clipboard
.writeText(textFor(target))
.then(function () {
btn.classList.add("is-copied");
if (label) label.textContent = "Copied";
window.clearTimeout(btn._copyTimer);
btn._copyTimer = window.setTimeout(function () {
btn.classList.remove("is-copied");
if (label && defaultText) label.textContent = defaultText;
}, 1800);
})
.catch(function () {
if (label) label.textContent = "Press ⌘C";
window.clearTimeout(btn._copyTimer);
btn._copyTimer = window.setTimeout(function () {
if (label && defaultText) label.textContent = defaultText;
}, 1800);
});
});
});
}
})();
Loading