Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
237b470be8 | ||
|
|
9538b4ea0a | ||
|
|
581e8af605 | ||
|
|
f115f6eda3 | ||
|
|
a4308a5722 | ||
|
|
6d150c4450 | ||
|
|
2a0815f004 | ||
|
|
4f6e23fdec | ||
|
|
c5e06ba156 | ||
|
|
349c6479e1 | ||
|
|
522950dd6e | ||
|
|
91e6124c37 | ||
|
|
abb660e327 | ||
|
|
d59efbcd16 | ||
|
|
1ecf625427 | ||
|
|
20072d1080 | ||
|
|
dd5db1b32d | ||
|
|
d1a9c88b30 | ||
|
|
fe9ad200c4 |
No files matched your search
@@ -0,0 +1,79 @@
|
||||
# mad skills
|
||||
|
||||
How I like code written, reviewed, and committed — written down so the
|
||||
bots I pair with can pick it up without me repeating myself.
|
||||
|
||||
These are [Agent Skills](https://agentskills.io/specification). Each
|
||||
`description` front-loads the words that should trigger it — the things
|
||||
I actually say, like "retcon", "watch me", or "citation needed".
|
||||
|
||||
I keep this repo checked out at `.agents/skills/` in a workspace and
|
||||
symlink `.claude/skills` and `.opencode/skills` to it, so [Claude
|
||||
Code](https://code.claude.com/docs/en/skills) and
|
||||
[opencode](https://opencode.ai/docs/skills/) see the same set.
|
||||
|
||||
## writing code
|
||||
|
||||
- [no-ambient-authority](no-ambient-authority/SKILL.md) — clocks,
|
||||
RNGs, `fetch`, and the filesystem come in as parameters.
|
||||
- [io-between-turns](io-between-turns/SKILL.md) — sync I/O is a pointy
|
||||
instrument.
|
||||
- [silent-failures-are-evil](silent-failures-are-evil/SKILL.md) — every
|
||||
failure path leaves a trace in the logs.
|
||||
- [lazy-tdd](lazy-tdd/SKILL.md) — works the first time? fine. Goofed?
|
||||
You owe a test.
|
||||
- [docs-files-api](docs-files-api/SKILL.md) — README sells and gets
|
||||
you to TADA; CONTRIBUTING has the rest; AGENTS.md links to it.
|
||||
Exported API docs over file docs. Avoid chit-chat.
|
||||
|
||||
## commits and PRs
|
||||
|
||||
- [disclose-gen-ai](disclose-gen-ai/SKILL.md) — `Generated-by:`
|
||||
trailers, per the ASF generative tooling guidance.
|
||||
- [retcon](retcon/SKILL.md) — regroup a PR's net diff into clean
|
||||
per-package commits; the merge result doesn't change.
|
||||
- [yarn-lock-separate-commit](yarn-lock-separate-commit/SKILL.md) —
|
||||
`yarn.lock` changes ship alone.
|
||||
|
||||
## writing prose
|
||||
|
||||
- [grice-maxims](grice-maxims/SKILL.md) — say what's needed and no
|
||||
more, only what you can back up, in an order easy to follow.
|
||||
- [hypertext-links](hypertext-links/SKILL.md) — link text says where
|
||||
the link goes; no bare URLs, no "click here".
|
||||
- [citation-needed](citation-needed/SKILL.md) — back a challenged claim
|
||||
with live links, quoted and dated.
|
||||
- [hackmd-deck](hackmd-deck/SKILL.md) — a light talk as a flashback:
|
||||
TADA, then how, then the challenges, tagged for an emoji retro.
|
||||
- [ibis-design](ibis-design/SKILL.md) — search a design space:
|
||||
issues, positions, and arguments.
|
||||
- [wiring-diagram](wiring-diagram/SKILL.md) — who holds what, drawn
|
||||
like MarkM's ERTP slides: every arrow cites the code, and Mallory
|
||||
shows what a leak doesn't reach.
|
||||
TODO: consider copying or adapting its companion for message order,
|
||||
sequence-diagram-actor-sim (agoric-sdk `.agents/skills/` on branch
|
||||
`connolly/ago-1299-agentic-planning-tdd`).
|
||||
- [obsidian-markdown](obsidian-markdown/SKILL.md) — the workspace is
|
||||
an Obsidian vault: one line per paragraph, relative links, no file
|
||||
symlinks you expect Obsidian to show.
|
||||
|
||||
## at the terminal
|
||||
|
||||
- [discord-agent-workspace](discord-agent-workspace/SKILL.md) — set
|
||||
someone up like this: Claude Code in a workspace folder, messaged
|
||||
from Discord.
|
||||
- [watchme](watchme/SKILL.md) — I record the session with `script -f`;
|
||||
read the typescript instead of asking me what happened.
|
||||
- [run-confined](run-confined/SKILL.md) — run an untrusted installer in
|
||||
a throwaway systemd user unit that can't see `~/.ssh` and friends.
|
||||
- [what-was-i-doing](what-was-i-doing/SKILL.md) — where did this file
|
||||
come from? Correlate mtimes with git history across every repo.
|
||||
- [notify-me](notify-me/SKILL.md) — desktop notification at the end
|
||||
of a long turn, sticky if I'm stepping away; not every reply.
|
||||
|
||||
## adding one
|
||||
|
||||
Name the directory for the trigger, not the topic. Write the
|
||||
`description` for the matcher: what I'd say, what I'd be doing, and
|
||||
what it's *not* for. Cite the source; quote the key line; explain the
|
||||
rest.
|
||||
@@ -34,6 +34,20 @@ Generated-by: Claude Opus 5.5
|
||||
trailer only on those commits. Don't add it to every commit.
|
||||
- **PR descriptions:** say which parts were AI-generated. The commit
|
||||
trailers stay the record that counts.
|
||||
- **Unreviewed PRs are drafts.** When an agent opens a PR under the
|
||||
human's GitHub account and the human hasn't read it yet, open it as
|
||||
a draft (`gh pr create --draft`) and start the description with:
|
||||
|
||||
```
|
||||
> [!WARNING]
|
||||
> **Unreviewed LLM output.** Generated by <Tool> <Version>; no human
|
||||
> has reviewed this code yet.
|
||||
```
|
||||
|
||||
The human removes the caveat and marks it ready after reviewing.
|
||||
The caveat exists because the PR appears to come from the human.
|
||||
A PR opened from a bot account already shows who wrote it, so it
|
||||
doesn't need the caveat.
|
||||
- **The human stays responsible.** The commit author vouches for the
|
||||
contribution. That includes disclosing material that is not their
|
||||
original creation, and making sure the tool's terms grant enough
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: discord-agent-workspace
|
||||
description: Set up a Claude Code agent you can message from Discord, working in a workspace folder of your projects — when someone says "set me up like Dan's bot", "I want to text my Claude from my phone", "Claude Code on Discord", or "make a workspace for my agent". Walks a non-developer through the Discord bot, the workspace folder (AGENTS.md, skills, repos), access control, and keeping it running. Not for chat-only Claude apps with no computer behind them.
|
||||
---
|
||||
|
||||
# Discord agent workspace
|
||||
|
||||
Dan's arrangement, reproduced for someone else:
|
||||
|
||||
- **Claude Code** runs in a terminal on a computer that stays on.
|
||||
- **The Discord channel plugin** turns a Discord bot into a pipe: allowed people's messages arrive in that session, and Claude answers with a `reply` tool ([plugin README](https://github.com/anthropics/claude-plugins-official/tree/main/external_plugins/discord)).
|
||||
- **A workspace folder** holds the projects plus written-down conventions, so each fresh session knows where it is and how the owner likes things done.
|
||||
|
||||
Your user may not be a developer. Do each step with them, one at a time; say what a command does before running it, and stop at anything that needs their account, their browser, or a secret. Never paste a token into chat; have them put it in the file or command themselves.
|
||||
|
||||
## 0. Is it worth it for them?
|
||||
|
||||
Ask what they'd send it. Good fits: tasks over files (code, documents, notes, data) that take a sentence to ask, minutes to do, and a glance to check. Poor fits: work that lives only in web apps with no files or API, or results they can't review. If it's a poor fit, say so and stop.
|
||||
|
||||
They need: Claude Code with a Claude subscription or API account, a Discord account and a server they can add a bot to, and a computer that stays on (a desktop, or a small cloud VM).
|
||||
|
||||
## 1. The workspace
|
||||
|
||||
```sh
|
||||
mkdir -p ~/agent-workspace && cd ~/agent-workspace
|
||||
git clone <each project they want the agent to work on>
|
||||
```
|
||||
|
||||
Write `AGENTS.md` at the top, and link `CLAUDE.md` to it (`ln -s AGENTS.md CLAUDE.md`) so every agent tool finds it. Keep it under a page, in their words:
|
||||
|
||||
- who they are and what the projects are for, one line each with a link;
|
||||
- house rules, e.g. "PRs are drafts", "ask before pushing", "keep Discord replies short";
|
||||
- what is off limits.
|
||||
|
||||
Skills are conventions that load when relevant. Put them in `.agents/skills/<name>/SKILL.md` and link Claude Code's location to it:
|
||||
|
||||
```sh
|
||||
mkdir -p .agents/skills .claude && ln -s ../.agents/skills .claude/skills
|
||||
```
|
||||
|
||||
Start with none or one. Add a skill when they catch themselves repeating an instruction; Dan's [mad-skills](https://git.inference.coop/dckc/mad-skills) show the shape. Claude Code also keeps its own memory per workspace, so corrections stick across sessions.
|
||||
|
||||
## 2. The Discord bot
|
||||
|
||||
Follow the plugin README with them; in short:
|
||||
|
||||
1. **Discord Developer Portal → New Application → Bot.** Turn on **Message Content Intent**. **Reset Token** and copy it.
|
||||
2. **OAuth2 → URL Generator**, scope `bot`, permissions: View Channels, Send Messages, Send Messages in Threads, Read Message History, Attach Files, Add Reactions. Open the URL and add the bot to their server.
|
||||
3. Install [Bun](https://bun.sh), which the plugin runs on.
|
||||
4. In Claude Code: `/plugin install discord@claude-plugins-official`, then `/discord:configure <token>` (they type it; it lands in `~/.claude/channels/discord/.env`).
|
||||
5. Restart from the workspace: `cd ~/agent-workspace && claude --channels plugin:discord@claude-plugins-official`.
|
||||
6. They DM the bot, get a pairing code, and run `/discord:access pair <code>` in the terminal.
|
||||
7. **Lock it down:** `/discord:access policy allowlist`. For a server channel, add it per the plugin's ACCESS.md.
|
||||
|
||||
## 3. Who can make it act
|
||||
|
||||
Whoever can message the bot can make it act with the session's powers: files, shell, and any logins on that computer (GitHub, cloud accounts). Tell them plainly, then:
|
||||
|
||||
- Allowlist only themselves at first. Access changes happen at the terminal (`/discord:access`), never because a Discord message asked.
|
||||
- Prefer narrow credentials: a GitHub fine-grained token limited to the workspace repos beats their full login.
|
||||
- Things that leave the computer (pushes, PRs, posts) go out as drafts labelled as unreviewed AI output, until they trust it.
|
||||
|
||||
## 4. Keeping it running
|
||||
|
||||
The session must stay open. Simplest: run it inside `tmux` (`tmux new -s bot`, start Claude, detach with Ctrl-b d; `tmux attach -t bot` to look in). On Linux, a systemd user unit can start it at boot and keep it out of secrets it doesn't need (`ProtectHome=read-only`, `ReadWritePaths=` the workspace and `~/.claude`, `InaccessiblePaths=` `~/.ssh` and other credential dirs).
|
||||
|
||||
## 5. First task
|
||||
|
||||
Have them send something small from their phone: "summarize what's in this workspace" or "fix the typo in X's README and show me the diff". Check together that the reply arrived and the change is what they expected.
|
||||
+29
-1
@@ -1,10 +1,38 @@
|
||||
---
|
||||
name: docs-files-api
|
||||
description: JSDoc placement rules for TypeScript/JSDoc code — use when writing or reviewing code comments, JSDoc, or file headers; deciding where @file/@see links go; documenting exported symbols or external specs; when a `//` comment explains what a declaration is or does (it belongs in JSDoc).
|
||||
description: Where docs go — README vs CONTRIBUTING vs AGENTS.md, and JSDoc placement — use when writing or reviewing a README, CONTRIBUTING, or AGENTS.md/CLAUDE.md; when someone asks "how do I run it?" and there's no README; when writing or reviewing code comments, JSDoc, or file headers; deciding where @file/@see links go; documenting exported symbols or external specs; when a `//` comment explains what a declaration is or does (it belongs in JSDoc).
|
||||
---
|
||||
|
||||
# Documentation Conventions
|
||||
|
||||
## README, CONTRIBUTING, AGENTS.md
|
||||
|
||||
A **README** sells, then gets the reader running:
|
||||
|
||||
- **Up top, about two sentences:** what it is and why to care. Then
|
||||
straight to getting started.
|
||||
- **Getting started:** get them to TADA! with no fuss. Readers skip to
|
||||
the first fenced block and copy it; if it doesn't work, boo. So that
|
||||
block runs as-is from a fresh clone and ends with something to look
|
||||
at (a URL, output). No architecture, no principles.
|
||||
- **After that:** features and benefits, as much as earns its place.
|
||||
|
||||
**Headings say something.** A heading is prime real estate: it shows in
|
||||
the outline and in link targets. "What's in it", "Overview", "Features",
|
||||
"Details" say nothing; name the point instead ("Bookmarks in, a zine
|
||||
out"). Plain labels are fine only where the reader hunts for them by
|
||||
name: "Getting started", "Deploying".
|
||||
|
||||
Everything else goes in **CONTRIBUTING**: design, layout of the code,
|
||||
how to test, conventions, deploying.
|
||||
|
||||
**AGENTS.md is a symlink to CONTRIBUTING.md** (likewise CLAUDE.md, if a
|
||||
tool wants one). Good advice is good advice, for people and bots alike.
|
||||
If agents need something people don't, give it a short section in
|
||||
CONTRIBUTING.
|
||||
|
||||
## JSDoc
|
||||
|
||||
JSDoc placement rules (from the
|
||||
[portfolio-contract contributing notes](https://github.com/Agoric/agoric-sdk/blob/master/packages/portfolio-contract/CONTRIBUTING.md#documentation-conventions)):
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: grice-maxims
|
||||
description: >-
|
||||
Grice's maxims: be concise, truthful, relevant, clear — use before any reply, and when writing or copy editing prose for a reader (docs, READMEs, skills, commit messages, PR descriptions); when someone says "tighter", "too wordy", "stop blathering", "omit needless words", or cites Strunk & White or Grice; when about to say something you lack adequate evidence for. Optimize for reader attention: say what's needed, nothing the reader already knows, in an order easy to follow.
|
||||
---
|
||||
|
||||
# Be concise; optimize for reader attention
|
||||
|
||||
Take care to apply [Grice's four maxims](https://en.wikipedia.org/wiki/Cooperative_principle#Grice's_maxims)
|
||||
of conversation: quantity, quality, relation, and manner.
|
||||
|
||||
The maxim of **quantity** is: be informative.
|
||||
|
||||
Submaxims:
|
||||
|
||||
- Make your contribution as informative as is required (for the current
|
||||
purposes of the exchange).
|
||||
**Do not make your contribution more informative than is required.**
|
||||
|
||||
The maxim of **quality** is: be truthful.
|
||||
|
||||
Supermaxim:
|
||||
|
||||
- Try to make your contribution one that is true.
|
||||
|
||||
Submaxims:
|
||||
|
||||
- Do not say what you believe is false.
|
||||
- Do not say that for which you **lack adequate evidence**.
|
||||
|
||||
The maxim of **relation** is: be relevant: the information provided
|
||||
should be relevant to the current exchange and omit any irrelevant
|
||||
information. Do not spend words on things the reader already knows.
|
||||
|
||||
The maxim of **manner** is: be clear.
|
||||
|
||||
Be brief — i.e., avoid unnecessary verbosity.
|
||||
Be orderly — i.e., provide information in an order that makes sense,
|
||||
and makes it easy for the recipient to process it.
|
||||
|
||||
If you can say it with fewer words, do.
|
||||
|
||||
## in practice
|
||||
|
||||
- Link a well-known convention; don't restate it.
|
||||
- Cut tails that repeat a name, a link's target, or the obvious.
|
||||
- Quote the key line of a source; explain the rest in your own words.
|
||||
|
||||
## sources
|
||||
|
||||
- William Strunk Jr., [The Elements of Style](https://www.gutenberg.org/files/37134/37134-h/37134-h.htm)
|
||||
(1918; later editions with E. B. White). Rule 13, "Omit needless words":
|
||||
|
||||
> Vigorous writing is concise. A sentence should contain no unnecessary
|
||||
> words, a paragraph no unnecessary sentences, for the same reason that
|
||||
> a drawing should have no unnecessary lines and a machine no
|
||||
> unnecessary parts.
|
||||
|
||||
- Lucile Vaughan Payne,
|
||||
[The Lively Art of Writing](https://openlibrary.org/books/OL7410114M/The_Lively_Art_of_Writing)
|
||||
(Prentice Hall, 1975; ISBN 978-0205091607)
|
||||
- [Cooperative principle — Wikipedia](https://en.wikipedia.org/wiki/Cooperative_principle)
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: hackmd-deck
|
||||
description: HackMD slide deck for a light talk — use when asked to draft slides, a deck, or a talk for show & tell, office hours, or a demo; when someone says "hackmd deck", "whip up some slides", or names a talk title and points at work to present (repos, git history, session transcripts). Covers the flashback story arc, emoji markers for wins and friction, and real artifacts as eye candy. Not for .pptx files or polished conference talks.
|
||||
---
|
||||
|
||||
# HackMD deck
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Good Fences Make Good Agents
|
||||
type: slide
|
||||
---
|
||||
|
||||
# Good Fences Make Good Agents
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Monday's issue drafts itself
|
||||
|
||||
---
|
||||
|
||||
## How: who holds what
|
||||
|
||||

|
||||
```
|
||||
|
||||
Example: [Good Fences Make Good Agents](../../../capsite/docs/good-fences-slides.md); an early, overboard one: [Jun 25 slides](https://hackmd.io/T9-oFWEPSb2zzqWAGFv60Q#/).
|
||||
|
||||
## Context
|
||||
|
||||
A talk at something like Agoric's engineering show & tell: "Presentations should take little to no preparation", by engineers for engineers. The work already exists in repos, commits, test output, and session transcripts.
|
||||
|
||||
## Problem
|
||||
|
||||
**I have done a bunch of stuff I think is interesting, but I can't justify the time to make a polished presentation.**
|
||||
|
||||
## Therefore
|
||||
|
||||
**Let an agent do a quick-n-dirty one: a flashback, built from the record.**
|
||||
|
||||
The arc:
|
||||
|
||||
1. 🎉 TADA! Look what I did. It must be on the thread the talk follows.
|
||||
2. Now that you're hooked, how I did that.
|
||||
3. Eye candy along the way: diagrams, verbatim code, test output.
|
||||
4. Conclusions and/or challenges that lead to discussion.
|
||||
|
||||
Ground every slide in the record: quote code and test output verbatim (trim with `...`), check claims against the code and git history, and mark what's only designed or not deployed with ⏳.
|
||||
|
||||
Mark wins, losses, challenges, and friction with emojis: visually appealing, intuitive at a glance.
|
||||
|
||||
- wins: 🎉 the TADA · ✅ shipped · ✨ sparkle, delight · 💡 aha: now I see how to address the issue · 🙌 help, or a gift from someone else's work
|
||||
- friction and follow-ups: ⏳ delays, not yet · 🧰 fragile tooling, ceremony · 💤 silent failures · 📘 docs · 🔔 alerts · 📈 observability · 🪄 a wish, often one a problem brings to mind · 🔧 fixes owed · 🧱 walls
|
||||
|
||||
Write the deck in the repo the talk is about (e.g. `docs/<title>-slides.md`), images beside it, and commit it.
|
||||
|
||||
## Resulting context
|
||||
|
||||
The deck takes 15 minutes: the agent spends 5, the presenter polishes for 10. The user pastes it into HackMD and uploads the images.
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: ibis-design
|
||||
description: >-
|
||||
IBIS / issue-based information system — use when writing or reviewing a design discussion, decision record, or outline of issues, positions, and pro/con arguments; when someone marks lines with `?`, `?!`, `?~`, `:`, `:+`, `:-`, `:~+`, `+`, `-`, `!`, `.`; when asked to "IBIS this", lay out options for a decision, record a fallback, or capture why a choice was made. Source: dckc/vattr97 wiki IBIS page.
|
||||
---
|
||||
|
||||
# IBIS design discussions
|
||||
|
||||
[IBIS page on the vattr97 wiki](https://github.com/dckc/vattr97/wiki/IBIS),
|
||||
condensed to working rules. See also
|
||||
[Issue-based information system on Wikipedia](https://en.wikipedia.org/wiki/Issue-based_information_system).
|
||||
|
||||
IBIS develops solutions to issues asynchronously and keeps the
|
||||
reasoning around so the resolution can evolve. It's an outline; a
|
||||
leading symbol on each item says what kind of element it is.
|
||||
|
||||
## symbols
|
||||
|
||||
| Symbol | Meaning |
|
||||
| :---: | :--- |
|
||||
| `?` | an issue to be considered |
|
||||
| `?!` | a resolved issue |
|
||||
| `?~` | tentatively resolved, or still open but with a fallback position |
|
||||
| `:` | a position on an issue (positions on one issue may overlap) |
|
||||
| `:+` | an accepted position |
|
||||
| `:-` | a rejected position |
|
||||
| `:~+` | tentatively accepted, or the fallback position for the issue |
|
||||
| `+` | pro: an argument for a position |
|
||||
| `-` | con: an argument against a position |
|
||||
| `!` | a to-do on an element (optionally with initials: `!jf`) |
|
||||
| `.` | a comment on an element |
|
||||
|
||||
## rules
|
||||
|
||||
1. **Phrase issues to admit overlapping positions**, as complete
|
||||
sentences — not yes/no. "? What tools should we provide for
|
||||
enterprise developers?", not "? Should we support VSCode?"
|
||||
2. **Positions are complete sentences** proposing a full or partial
|
||||
solution. They may overlap or conflict, and needn't capture every
|
||||
ramification — refine them with sub-positions.
|
||||
3. **Nesting:**
|
||||
- A sub-issue discusses a portion of its outer issue; its positions
|
||||
nest under it.
|
||||
- A sub-position refines its parent *assuming the parent is
|
||||
accepted*; its arguments nest under it.
|
||||
- Arguments are always pro or con with respect to their containing
|
||||
issue, so a `+` under a `-` is typically a mitigation ("here's why
|
||||
that con isn't so bad").
|
||||
4. **An issue is resolved only when some position is accepted.** Not
|
||||
every position needs an accept/reject verdict.
|
||||
5. **Use fallbacks (`?~`, `:~+`) to agree on a minimum bar** while
|
||||
leaving the issue open. The first fallback is often the status quo.
|
||||
Being the fallback must not preclude considering other options —
|
||||
that's what keeps fallbacks easy to agree on.
|
||||
6. **When one issue is really several choices, say so** with a `.`
|
||||
comment and split it into sibling `?` sub-issues.
|
||||
7. **Attach each argument to the position it bears on.** If it applies
|
||||
equally to the siblings, it belongs higher up, or the issue is
|
||||
several choices (rule 6). A comment that rules a position out is a
|
||||
con, and the position is rejected (`:-`).
|
||||
8. **Check the source before raising an issue.** If the design text
|
||||
already answers it, record the issue as resolved and cite that text
|
||||
as the pro.
|
||||
9. **Past three levels of nesting, split out a sub-issue.**
|
||||
|
||||
## numbering
|
||||
|
||||
Number elements so replies can refer to them ("2: …"): issues `1`,
|
||||
`2`; positions under them `1a`, `1b`; anything below that `1a-i`,
|
||||
`1a-ii`, then `1a-i-a`. The number follows the symbol: `?! 2. …`,
|
||||
`:+ 2c. …`. Renumbering breaks references made in chat or other docs,
|
||||
so append rather than renumber once people are citing numbers. Show
|
||||
dependencies between issues with "(see 5)".
|
||||
|
||||
## writing it in markdown
|
||||
|
||||
A leading `-` or `.` collides with list syntax and editor
|
||||
auto-bulleting. Put each symbol after the list marker, escaping where
|
||||
needed (`- \- con`, `- \+ pro`), or use a fenced block with indentation
|
||||
as in the example below.
|
||||
|
||||
Past a screenful, give each top-level issue its own heading
|
||||
(`### ? 3. What is the fence?`) with its elements in a fenced block
|
||||
below. Headings show up in the editor outline and give each issue a
|
||||
link target. Links don't render inside a fence, so put sources after
|
||||
the block and refer to them from the line they support.
|
||||
|
||||
## example
|
||||
|
||||
```
|
||||
? How should we test our software?
|
||||
: use a test runner of some form
|
||||
: build a QA department
|
||||
:~+ use tape
|
||||
+ it's what we currently use
|
||||
+ it's very simple
|
||||
+ it works in numerous environments
|
||||
- it's not very expressive
|
||||
- it doesn't have code coverage
|
||||
: use Jest
|
||||
+ lots of people like it
|
||||
- guess-my-syntax DSL for expressing assertions
|
||||
+ you can separate the assertion choice from the framework
|
||||
+ can do integration and unit tests very well
|
||||
!jf can it do them better than tape?
|
||||
: roll our own test runner
|
||||
: make them importable ESM
|
||||
. then we can run them in XS
|
||||
+ could be simpler than tape
|
||||
- requires implementation and maintenance
|
||||
:+ use test262
|
||||
+ required for JS standards testing
|
||||
- clunky for other uses
|
||||
. there are actually three separate choices: test runner, test environment, and assertion library
|
||||
? What assertion library should we use?
|
||||
? What test runner should we use?
|
||||
? What test environments should we support?
|
||||
: SES
|
||||
: NodeJS
|
||||
: in-browser
|
||||
```
|
||||
@@ -47,7 +47,7 @@ paper.)
|
||||
|
||||
## see also
|
||||
|
||||
- [ocap-discipline](../ocap-discipline/SKILL.md): who gets to do I/O at
|
||||
- [no-ambient-authority](../no-ambient-authority/SKILL.md): who gets to do I/O at
|
||||
all (authority passed in, not ambient); this skill is about *when*.
|
||||
- [silent-failures-are-evil](../silent-failures-are-evil/SKILL.md): an
|
||||
awaited I/O failure still needs a trace.
|
||||
+1
-1
@@ -46,5 +46,5 @@ amounts", not "fix for goof #3".
|
||||
If the goof can't reasonably be captured in an automated test (e.g. it
|
||||
lives in config, a one-off script, or untestable I/O with no seam), say
|
||||
so explicitly, explain why, and suggest the seam that would make it
|
||||
testable (see [ocap-discipline](../ocap-discipline/SKILL.md) for
|
||||
testable (see [no-ambient-authority](../no-ambient-authority/SKILL.md) for
|
||||
injecting I/O). Don't silently skip the debt.
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: ocap-discipline
|
||||
description: Use when writing or reviewing code whose functions touch I/O, clocks, random number generators, process.env, fetch, or filesystem; when someone says "ambient authority", "wash your mouth out", or "that function shouldn't reach for that"; when debating dependency injection or default-arg seams; when asked what OCap discipline is. Source: the Agoric wiki's OCap Discipline page.
|
||||
name: no-ambient-authority
|
||||
description: >-
|
||||
Use when writing or reviewing code whose functions touch I/O, clocks, random number generators, process.env, fetch, or filesystem; when someone says "ambient authority", "wash your mouth out", or "that function shouldn't reach for that"; when debating dependency injection or default-arg seams; when asked what OCap discipline is. Source: the Agoric wiki's OCap Discipline page.
|
||||
---
|
||||
|
||||
# OCap Discipline
|
||||
@@ -10,14 +11,22 @@ condensed to working rules.
|
||||
|
||||
## the background, in three lines
|
||||
|
||||
Object-capability security = memory safety + encapsulation ("no
|
||||
Object-capability security
|
||||
([capability-based security](https://en.wikipedia.org/wiki/Capability-based_security))
|
||||
= memory safety + encapsulation ("no
|
||||
reference to an object except: create it, be given it at creation, or
|
||||
receive it in a message") combined with **primitive effects only via
|
||||
references** — all world-touching primitives are embodied by objects,
|
||||
and *anything globally accessible is immutable data*. No `open(filename)`
|
||||
in the global namespace: converting a string to file access is like
|
||||
casting an integer to a pointer. JavaScript doesn't enforce this; the
|
||||
discipline is a convention (layerable via SES).
|
||||
casting an integer to a pointer; that's
|
||||
[ambient authority](https://en.wikipedia.org/wiki/Ambient_authority).
|
||||
JavaScript doesn't enforce this; the discipline is a convention
|
||||
(layerable via SES).
|
||||
|
||||
New to object capabilities? Start with
|
||||
[awesome-ocap](https://github.com/dckc/awesome-ocap), a curated list of
|
||||
capability-based languages, systems, papers, and talks.
|
||||
|
||||
## the working rules (from the wiki)
|
||||
|
||||
@@ -66,6 +75,8 @@ export const mintKey = (entropy: Entropy): string => b32(entropy(16));
|
||||
|
||||
- [OCap Discipline — Agoric wiki](https://github.com/Agoric/agoric-sdk/wiki/OCap-Discipline)
|
||||
(Miller, *Robust Composition* PhD thesis, 2006, is the deep source)
|
||||
- [ambient authority — Wikipedia](https://en.wikipedia.org/wiki/Ambient_authority)
|
||||
- [Ambient authority — Wikipedia](https://en.wikipedia.org/wiki/Ambient_authority)
|
||||
- [Capability-based security — Wikipedia](https://en.wikipedia.org/wiki/Capability-based_security)
|
||||
- [awesome-ocap: Awesome Object Capabilities and Capability-based Security](https://github.com/dckc/awesome-ocap)
|
||||
- in this workspace: `capsite/docs/webkeys.md` (URL-borne bearers),
|
||||
Agoric's eslit-based enforcement notes for contracts
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: notify-me
|
||||
description: Notify me / ping me — desktop (XDG) notification via `notify-send` at the end of any long turn (many steps, builds, test runs, deploys, waiting on background work), asked or not; and when the operator says "notify me", "XDG notify me", "ping me when you're done", "let me know about progress", or "make it stick / indefinite duration" because they're stepping away. Covers done vs. progress notices, sticky vs. transient, and updating one notice in place. Not for Discord replies (use the reply tool) or for quick back-and-forth turns.
|
||||
---
|
||||
|
||||
# Notify me
|
||||
|
||||
```sh
|
||||
notify-send -a capsite "done: diigo-cache" "3 commits on main, not pushed"
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
The operator hands a session a batch of work, then turns to another session or goes for a walk.
|
||||
|
||||
## Problem
|
||||
|
||||
**Without going to look, the operator can't tell which session is done or stuck.**
|
||||
|
||||
## Forces
|
||||
|
||||
- opencode-web notifies on every reply; notices without news teach the operator to ignore them all.
|
||||
- Silence is worse: a finished turn sits unseen.
|
||||
- The operator won't always think to ask.
|
||||
- Notices expire before the operator is back from a walk.
|
||||
- Several sessions share one desktop.
|
||||
- Progress notices pile up.
|
||||
|
||||
## Therefore
|
||||
|
||||
**At the end of any turn long enough that the operator has probably looked away, send one notice: which session, what state, what the operator should do next. Not during quick back-and-forth.**
|
||||
|
||||
Long means more than a handful of tool calls, or waiting on a build, tests, deploy, or background job. "Notify me when you're done" forces one even for a short turn.
|
||||
|
||||
- `-a` names the session (repo or task).
|
||||
- The summary leads with the status: `done`, `blocked` (including a question that stops the work), `failed`, `progress`.
|
||||
- The body says what's next: "needs a decision on the DO migration".
|
||||
|
||||
**Sticky** ("indefinite duration", "going for a walk"): `-t 0` (never expire). `-u critical` only if asked.
|
||||
|
||||
**Progress** ("notify me about progress"): one per milestone (tests pass, deployed), replacing the last rather than stacking:
|
||||
|
||||
```sh
|
||||
id=$(notify-send -p -a capsite "progress: diigo-cache" "tests pass; deploying")
|
||||
notify-send -r "$id" -a capsite "done: diigo-cache" "deployed; not pushed"
|
||||
```
|
||||
|
||||
Keep `$id` in a file (e.g. `.tmp/notify-id`) if the shell doesn't persist.
|
||||
|
||||
## Resulting context
|
||||
|
||||
The operator can leave a session alone and trust it to call. A notice means news, so it gets read.
|
||||
|
||||
`notify-send` needs the session bus; under a confined unit or ssh it may fail. Say so in the reply ([silent-failures-are-evil](../silent-failures-are-evil/SKILL.md)).
|
||||
|
||||
The agent must still remember this pattern at the end of a long turn, just when that slips.
|
||||
|
||||
## TODO
|
||||
|
||||
- A Claude Code `Stop` hook could time the turn and notify past ~2 min; opencode would need its own plugin hook.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: obsidian-markdown
|
||||
description: Markdown in an Obsidian vault that agents also write — use when an agent writes or edits any .md file in a workspace with a `.obsidian/` folder (notes, READMEs, CONTRIBUTING, AGENTS.md, skills, docs); when someone mentions Obsidian, hard wrap, ragged "poetry" margins, wikilinks, or the vault; when adding a symlink to markdown. Not for Discord or PR text (see grice-maxims).
|
||||
---
|
||||
|
||||
# Markdown for an Obsidian vault agents write in
|
||||
|
||||
The workspace is an [Obsidian](https://obsidian.md/) vault: a `.obsidian/` folder at its root. Dan reads and edits in Obsidian; agents write the same files from a terminal. Write markdown that reads well in Obsidian and on GitHub, and is easy for an agent to edit.
|
||||
|
||||
## One line per paragraph
|
||||
|
||||
Don't hard-wrap prose. A paragraph is one line; so is each list item. Break lines only between blocks: paragraphs, list items, headings, fences.
|
||||
|
||||
Wrapped paragraphs render with a ragged "poetry" margin in Dan's Obsidian, and they make diffs of a one-word edit reflow a whole paragraph. Code blocks keep their line breaks, of course.
|
||||
|
||||
When you rewrite a wrapped paragraph, unwrap it. Don't unwrap a whole file you aren't otherwise rewriting: that's a noisy diff in someone else's prose.
|
||||
|
||||
## Links
|
||||
|
||||
- Standard markdown links with relative paths: `[the webkeys pattern](docs/webkeys.md)`. Obsidian follows them, and GitHub renders them. `[[wikilinks]]` render only in Obsidian.
|
||||
- No spaces in file names, so links need no `%20`.
|
||||
- Link text says where the link goes (see hypertext-links).
|
||||
|
||||
## File names and headings
|
||||
|
||||
Obsidian shows file names in tabs, the file explorer, and search, so name files for what's in them.
|
||||
|
||||
Headings show in Obsidian's outline pane: make them say something.
|
||||
|
||||
## Frontmatter
|
||||
|
||||
Obsidian shows YAML frontmatter as Properties. Keep it valid YAML: quote values that contain `: ` or start with a special character.
|
||||
|
||||
## Symlinks
|
||||
|
||||
From [Obsidian's symlinks page](https://obsidian.md/help/symlinks):
|
||||
|
||||
> Obsidian ignores any symlink to a parent folder of the vault, or from one folder in the vault to another folder in the same vault.
|
||||
|
||||
> File symlinks (as opposed to folder symlinks) may work, but aren't officially supported at this time.
|
||||
|
||||
So a folder symlink inside the vault (`.claude/skills` → `.agents/skills`) is invisible to Obsidian, and that's fine: no duplicates. A file symlink like `AGENTS.md` → `CONTRIBUTING.md` may not show or update in Obsidian. Edit the target, never the link.
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: silent-failures-are-evil
|
||||
description: No silent failures — use when writing or reviewing error handling: catch blocks, promise rejections, fallbacks for unsupported features, or any branch that treats a result as "didn't work"; when a UI shows a failure with nothing logged; when someone says "silent failure". Every failure path leaves a trace in the logs, and no failure is invented without checking what the API actually returns.
|
||||
description: >-
|
||||
No silent failures — use when writing or reviewing error handling: catch blocks, promise rejections, fallbacks for unsupported features, or any branch that treats a result as "didn't work"; when a UI shows a failure with nothing logged; when someone says "silent failure". Every failure path leaves a trace in the logs, and no failure is invented without checking what the API actually returns.
|
||||
---
|
||||
|
||||
# Silent failures are evil
|
||||
@@ -21,7 +22,7 @@ must leave a trace.
|
||||
- **Say only what you know.** When an API can't tell you the outcome,
|
||||
report what it did tell you ("offered", not "saved").
|
||||
- **Make it testable.** Pass the logger in (see
|
||||
[ocap-discipline](../ocap-discipline/SKILL.md)) so a test can assert
|
||||
[no-ambient-authority](../no-ambient-authority/SKILL.md)) so a test can assert
|
||||
that the failure path logged; a discovered silent failure is a goof
|
||||
that owes a test ([lazy-tdd](../lazy-tdd/SKILL.md)).
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: wiring-diagram
|
||||
description: >-
|
||||
Capability wiring diagram, "who holds what" — use when asked for a wiring diagram, a picture of who holds what, a capability diagram, or "a diagram like the cookie-rotation one"; when reviewing a design for POLA and wanting to see the reference graph. MarkM ERTP-slide style: actors are balls, makers pale regions, objects rounded squares, state ledger tables, references dot-to-arrowhead; HTML/SVG plus a headless-Chromium PNG. Not for message order over time: that's a sequence diagram (sequence-diagram-actor-sim in agoric-sdk).
|
||||
---
|
||||
|
||||
# Capability wiring diagram
|
||||
|
||||
One picture that answers, for a design: **who holds a reference to what,
|
||||
and therefore who can do what.** It complements a sequence diagram,
|
||||
which shows *when* messages flow. This one shows the standing graph of
|
||||
authority that makes those messages possible. For sequences, see
|
||||
`~/projects/ymax-workspace/agoric-sdk/.agents/skills/sequence-diagram-actor-sim/SKILL.md`
|
||||
(on agoric-sdk branches `connolly/ago-1264-agentic-planning` and
|
||||
`connolly/ago-1299-agentic-planning-tdd`, not yet on master).
|
||||
|
||||
Exemplars:
|
||||
- `~/oros-bots/capsite-cookie-rotation/docs/cookie-rotation-wiring.html`
|
||||
(+ `.png`): the original.
|
||||
- `~/qtrx/packages/umb-download/docs/wiring.html` (+ `.png`): three
|
||||
makers, two outside parties, and an adversary holding a webkey.
|
||||
|
||||
Start from [`template.html`](template.html), next to this file. It has
|
||||
the drawing helpers and a small example scene to replace.
|
||||
|
||||
## Read the graph from the code, not the story
|
||||
|
||||
Every arrow must correspond to something real: a constructor argument, a
|
||||
stored id, a binding, a held token, or a file someone keeps. Before
|
||||
drawing, list the references as `holder → target: where in the code`.
|
||||
Read the factories (`make…`, `newX`, `configure`) and anything that
|
||||
stores an id: a DO id held as a string *is* a reference. If you can't
|
||||
cite where a reference comes from, it doesn't get an arrow.
|
||||
|
||||
Watch for references that are dropped on purpose (e.g. a setup route
|
||||
that mints an instance, hands its id to one holder and forgets it).
|
||||
That's often the point of the design. Say so in a caption, or just
|
||||
leave the arrow out.
|
||||
|
||||
## Vocabulary
|
||||
|
||||
| element | helper | means |
|
||||
|---|---|---|
|
||||
| blue ball | `actor(cx, cy, label)` | a person or a program with its own agency |
|
||||
| grey ball | `actor(cx, cy, label, "url(#ext)")` | outside the system: a browser extension, a third-party service |
|
||||
| pale region | `maker(x, y, w, h, "makeFoo")` | a maker (factory, DO class) and the instances it made; the stacked inner panes say "there are many" |
|
||||
| rounded square | `obj(x, y, w, h, label, stacked)` | an object or facet; `stacked` when one is made per instance |
|
||||
| ledger table | `table(x, y, title, rows, [colW0, colW1])` | an object's state; rows are key/value; `rowY(i)` and `colX` give arrow anchors |
|
||||
| dot → arrowhead | `ref(x1, y1, x2, y2, bend)` | holder references target; `bend` (±0.05–0.3) curves around things |
|
||||
| speech bubble | the `path` + `code` block | the one call the picture is about, in `E(target).method(args)` form |
|
||||
| caption | `cap(x, y, text, attrs)` | what an actor holds, and what it can't reach |
|
||||
|
||||
Facets are separate objects (`store` vs `readOnly`): drawing them apart
|
||||
is how the picture shows attenuation.
|
||||
|
||||
## Include an adversary
|
||||
|
||||
Add a Mallory who holds whatever is most likely to leak (an instance
|
||||
id, a webkey, a URL), with an arrow to exactly what it reaches. The
|
||||
caption says what she *can't* reach. Absent arrows are the security
|
||||
argument, and Mallory makes them visible.
|
||||
|
||||
## Layout
|
||||
|
||||
- Actors in a row along the top (y≈200), makers below (y≈320 onward),
|
||||
captions along the bottom (y≈ height − 14).
|
||||
- Put each maker under the actors that mostly use it, so arrows run
|
||||
short and downward.
|
||||
- Speech bubble at the top, its tail pointing at the speaker. Move the
|
||||
bubble's path (`M x,y`) and the tail segment (`h… l-38,48 l6,-48`)
|
||||
together.
|
||||
- Captions: two or three short lines, never on top of an arrow or a
|
||||
ball. Use `"text-anchor": "start"` to set one beside a ball.
|
||||
- Long arrows that cross a maker: increase `|bend|` until the curve
|
||||
clears tables and objects. The control point is the midpoint offset
|
||||
by `bend × (−dy, dx)`.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Keep `<title>` short. Write `<desc>` as prose that names every actor and
|
||||
maker, every reference, the bubble's call, and what the adversary
|
||||
can't reach. Someone who can't see the picture should get the same
|
||||
argument from it.
|
||||
|
||||
## Render and check
|
||||
|
||||
The page is self-contained (no network). Render a PNG with headless
|
||||
Chromium. The viewport comes out shorter than `--window-size`, so render
|
||||
taller and crop to the SVG size:
|
||||
|
||||
```sh
|
||||
chromium --headless=new --disable-gpu --no-sandbox --hide-scrollbars \
|
||||
--window-size=W,$((H+100)) --screenshot=full.png "file://$PWD/NAME.html"
|
||||
python3 -c "from PIL import Image; Image.open('full.png').crop((0,0,W,H)).save('NAME.png')" && rm full.png
|
||||
```
|
||||
|
||||
Then **look at the PNG** (Read it) and fix overlaps: captions on arrows,
|
||||
arrows through tables, text clipped at the bottom. Expect two or three
|
||||
rounds. Commit the HTML (source) and the PNG separately, so the source
|
||||
diff stays readable.
|
||||
|
||||
## Review checklist
|
||||
|
||||
- Does every arrow cite something in the code? Is any real reference
|
||||
missing?
|
||||
- Does each actor hold only what its job needs (POLA)? If the picture
|
||||
shows an actor holding more, that's a finding about the design, not a
|
||||
drawing problem.
|
||||
- Are facets drawn apart where they differ in authority?
|
||||
- Does the adversary's caption match what the arrows show?
|
||||
- Does `<desc>` tell the same story in prose?
|
||||
- Is any secret value drawn? Elide values (`session=…`, `{…}`): the
|
||||
picture shows *that* a row exists, not its contents.
|
||||
@@ -0,0 +1,138 @@
|
||||
<!doctype html>
|
||||
<html><head><meta charset="utf-8">
|
||||
<title>TITLE: who holds what</title>
|
||||
<!-- drawn after the style of MarkM's ERTP slides; NAME.png is a headless-Chromium screenshot of this page -->
|
||||
<style>
|
||||
html, body { margin: 0; background: #fff; }
|
||||
.sans { font-family: "Helvetica Neue", "DejaVu Sans", sans-serif; }
|
||||
.obj { font-family: "DejaVu Serif", serif; font-style: italic; }
|
||||
.mono { font-family: "DejaVu Sans Mono", monospace; }
|
||||
</style></head>
|
||||
<body>
|
||||
<svg id="c" xmlns="http://www.w3.org/2000/svg" width="1120" height="820" viewBox="0 0 1120 820"
|
||||
role="img" aria-labelledby="title desc">
|
||||
<title id="title">TITLE: who holds what</title>
|
||||
<desc id="desc">Prose for screen readers: name every actor and maker, every
|
||||
reference (who holds what), the speech bubble's call, and what the
|
||||
adversary can and cannot reach.</desc>
|
||||
<defs>
|
||||
<radialGradient id="ball" cx="40%" cy="35%" r="70%">
|
||||
<stop offset="0%" stop-color="#7cc8f8"/>
|
||||
<stop offset="100%" stop-color="#3aa6ee"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="ext" cx="40%" cy="35%" r="70%">
|
||||
<stop offset="0%" stop-color="#e3e3e3"/>
|
||||
<stop offset="100%" stop-color="#b9b9b9"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="objfill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#6cbcf4"/>
|
||||
<stop offset="100%" stop-color="#4aa8ec"/>
|
||||
</linearGradient>
|
||||
<marker id="head" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#1d4f7a"/>
|
||||
</marker>
|
||||
</defs>
|
||||
</svg>
|
||||
<script>
|
||||
const svg = document.getElementById("c");
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
const el = (name, attrs, parent = svg) => {
|
||||
const e = document.createElementNS(NS, name);
|
||||
for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);
|
||||
parent.appendChild(e);
|
||||
return e;
|
||||
};
|
||||
const text = (x, y, s, cls, attrs = {}) => {
|
||||
const t = el("text", { x, y, class: cls, "text-anchor": "middle", fill: "#1d3550", ...attrs });
|
||||
t.textContent = s;
|
||||
return t;
|
||||
};
|
||||
|
||||
const INK = "#1d4f7a";
|
||||
const dot = (x, y) => el("circle", { cx: x, cy: y, r: 4.5, fill: INK });
|
||||
/** a reference: dot at the holder, arrowhead at the target */
|
||||
const ref = (x1, y1, x2, y2, bend = 0, extra = {}) => {
|
||||
dot(x1, y1);
|
||||
const mx = (x1 + x2) / 2 - (y2 - y1) * bend, my = (y1 + y2) / 2 + (x2 - x1) * bend;
|
||||
el("path", { d: `M${x1},${y1} Q${mx},${my} ${x2},${y2}`, fill: "none", stroke: INK, "stroke-width": 2, "marker-end": "url(#head)", ...extra });
|
||||
};
|
||||
|
||||
// maker box: a pale rounded region, with stacked instances inside
|
||||
function maker(x, y, w, h, label) {
|
||||
el("rect", { x, y, width: w, height: h, rx: 26, fill: "#d6ecfb", stroke: "#b5dbf5", "stroke-width": 1.5 });
|
||||
text(x + 18, y + 30, label, "obj", { "text-anchor": "start", "font-size": 20 });
|
||||
for (let i = 2; i >= 0; i -= 1) {
|
||||
el("rect", {
|
||||
x: x + 18 + i * 9, y: y + 48 + i * 9, width: w - 52, height: h - 82, rx: 22,
|
||||
fill: "#c8e5fa", stroke: "#8ec9f2", "stroke-width": 1.5, "fill-opacity": 0.9,
|
||||
});
|
||||
}
|
||||
}
|
||||
// an object: rounded square, optionally stacked (several made)
|
||||
function obj(x, y, w, h, label, stacked = false) {
|
||||
const n = stacked ? 3 : 1;
|
||||
for (let i = n - 1; i >= 0; i -= 1) {
|
||||
el("rect", { x: x + i * 7, y: y - i * 7, width: w, height: h, rx: 12, fill: "url(#objfill)", stroke: "#2f8fd6", "stroke-width": 1.5 });
|
||||
}
|
||||
text(x + w / 2, y + h / 2 + 7, label, "obj", { "font-size": 19 });
|
||||
}
|
||||
function actor(cx, cy, label, fill = "url(#ball)") {
|
||||
el("circle", { cx, cy, r: 52, fill, stroke: "#2f8fd6", "stroke-width": 1.5 });
|
||||
text(cx, cy + 6, label, "sans", { "font-size": label.length > 8 ? 15 : 17 });
|
||||
}
|
||||
// a table like the ERTP ledger
|
||||
function table(x, y, title, rows, colW) {
|
||||
const rowH = 28, w = colW[0] + colW[1];
|
||||
el("rect", { x, y, width: w, height: 36 + rowH * rows.length, fill: "#5fb5f2", stroke: "#2f8fd6", "stroke-width": 1.5 });
|
||||
text(x + w / 2, y + 25, title, "obj", { "font-size": 18 });
|
||||
rows.forEach(([k, v], i) => {
|
||||
const ry = y + 36 + i * rowH;
|
||||
el("line", { x1: x, y1: ry, x2: x + w, y2: ry, stroke: "#2f8fd6", "stroke-width": 1.2 });
|
||||
text(x + 10, ry + 19, k, "mono", { "text-anchor": "start", "font-size": 14 });
|
||||
if (v) text(x + colW[0] + 10, ry + 19, v, "mono", { "text-anchor": "start", "font-size": 14 });
|
||||
});
|
||||
el("line", { x1: x + colW[0], y1: y + 36, x2: x + colW[0], y2: y + 36 + rowH * rows.length, stroke: "#1d4f7a", "stroke-width": 1.5 });
|
||||
return { rowY: (i) => y + 36 + i * rowH + rowH / 2, x, w, colX: x + colW[0] };
|
||||
}
|
||||
|
||||
// ---- scene: replace from here down ----
|
||||
|
||||
// speech bubble: the one invocation the picture is about; tail toward the speaker
|
||||
el("path", {
|
||||
d: "M30,22 h470 a16,16 0 0 1 16,16 v34 a16,16 0 0 1 -16,16 h-330 l-38,48 l6,-48 h-102 a16,16 0 0 1 -16,-16 v-34 a16,16 0 0 1 16,-16 z",
|
||||
fill: "#d9d9d9", stroke: "#555", "stroke-width": 2,
|
||||
});
|
||||
const code = el("text", { x: 46, y: 62, class: "mono", "font-size": 17, fill: "#222" });
|
||||
code.innerHTML = '<tspan fill="#b0177b">await</tspan> E(store).set(<tspan fill="#1a7f37">"cookie"</tspan>, freshCookie);';
|
||||
|
||||
// actors: people, programs, outside services (grey: "url(#ext)")
|
||||
actor(150, 200, "Alice");
|
||||
actor(560, 200, "client");
|
||||
actor(930, 200, "Mallory");
|
||||
|
||||
// makers, with what they made inside
|
||||
maker(30, 320, 470, 470, "makeMapStore");
|
||||
maker(560, 320, 530, 470, "makeWebApp");
|
||||
obj(80, 420, 120, 80, "store");
|
||||
obj(80, 600, 120, 80, "readOnly", true);
|
||||
const kv = table(260, 430, "kv", [["cookie", "session=…"], ["", ""], ["", ""]], [90, 110]);
|
||||
obj(620, 560, 140, 90, "access", true);
|
||||
const init = table(830, 420, "init", [["url", "…"], ["headerStore", ""]], [130, 85]);
|
||||
|
||||
// references: holder first; one comment per arrow, in the form "holder → target"
|
||||
ref(140, 228, 125, 418, 0.05); // Alice → store
|
||||
ref(172, 224, 508, 210, 0.04); // Alice → client (she wired it)
|
||||
ref(554, 218, 660, 553, 0.12); // client → access
|
||||
ref(916, 220, 720, 553, -0.08); // Mallory → access
|
||||
ref(160, 470, kv.x - 2, kv.rowY(0), 0.05); // store → kv
|
||||
ref(150, 666, kv.x - 2, kv.rowY(0) + 6, -0.25); // readOnly → kv
|
||||
ref(760, 600, init.x - 2, init.rowY(1), -0.1); // access → init
|
||||
ref(init.colX + 40, init.rowY(1), 208, 640, -0.28); // init.headerStore → readOnly
|
||||
|
||||
// captions: what each actor holds, what the adversary can't reach
|
||||
const cap = (x, y, s, attrs = {}) => text(x, y, s, "sans", { "font-size": 14, fill: "#33536f", ...attrs });
|
||||
cap(930, 118, "holds the instance id:");
|
||||
cap(930, 136, "uses the cookie, can't reach store");
|
||||
cap(265, 806, "shapes: keyShape \"cookie\", valueShape M.string()");
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: yarn-lock-separate-commit
|
||||
description: yarn.lock in its own commit — use when changing Yarn dependencies (package.json deps, workspace renames, yarn install), when organizing or reviewing commits that touch yarn.lock, when a rebase conflicts on yarn.lock, or during a retcon. Every lockfile change ships alone as `chore: Update yarn.lock`, after the package.json commit that caused it.
|
||||
description: >-
|
||||
yarn.lock in its own commit — use when changing Yarn dependencies (package.json deps, workspace renames, yarn install), when organizing or reviewing commits that touch yarn.lock, when a rebase conflicts on yarn.lock, or during a retcon. Every lockfile change ships alone as `chore: Update yarn.lock`, after the package.json commit that caused it.
|
||||
---
|
||||
|
||||
# yarn.lock in a separate commit
|
||||
|
||||
Reference in new issue
Block a user