Each recipe ends with npm run check. If it is not green, it is not done.
Add or edit a page
node scripts/site/new-page.mjs <name> --title "RiskMandate — …" --desc "…" \
--body /path/to/body.html --css /path/to/page.css --donor work.html --cta "Get in touch"
node scripts/site/add-licence-chrome.mjs # licence line + site line, no header icons, if the donor predates them
# add the page to site/pages.json: a group and a section of Product, How it works or Open source, or unlisted
node scripts/site/generate.mjs # injects the menu, writes the twin, sitemap, llms.txt
npm run check
- A page is one self-contained document: its CSS, its markup, its inlined data, the four shared
modules. Copy classes from a donor (
work.htmlhas cards, lists, tables, notices, a prompt block). - Colours are tokens on
:root. A literal hex outside:rootis a bug. - Components never use
innerHTML; text goes in throughtextContent. - The voice: plain, sourced, dated. Say what is not built next to what is. British spelling.
- Edit the page, never its
.mdtwin. Rerungenerate.mjsafter every edit. - A renamed page leaves a redirect stub at the old address (see
site/abp-vaults.html).
Add a behaviour-policy vault
Inputs are four files; everything else is derived.
mkdir -p site/vaults/<slug>/data && cp -r site/vaults/gmail-readonly/data/vocabulary site/vaults/<slug>/data/
# write site/vaults/<slug>/vault.json who, which shape, status "template", renderer version
# write site/vaults/<slug>/data/grant.json abp/profile/v1 — one row per capability, evidence, barrier, via, material, undo
# write site/vaults/<slug>/data/mandate.json abp/mandate/v1 — want / do_not_want / unstated over the 23; status starting-point
# write site/vaults/<slug>/data/scenarios.json six scenarios: three normal, three advanced; each wants and refuses primitives
node scripts/site/build-abp-vault.mjs <slug> # derives the documents, delta, validity, zip, loader, history
node scripts/site/build-abp-vault.mjs <slug> --check
# add the vault to site/vaults/index.json (slug, app, title, shape, family, blurb, logo, brand, group; vid + key once pushed)
node scripts/site/build-abp-pages.mjs # the library page and abp-vault-<slug>.html
node scripts/site/generate.mjs && npm run check
- A grant is measured or documented, never typed. Measured needs a system we are entitled
to run and the evidence file in
history/. Documented quotes the vendor's page, with URL and date read, in each row'snote. Anything unsettled goes inresearch_needed; disagreements between the vendor's own pages go incontradictions, unresolved; permissions the grammar cannot name go innot_in_grammar. materialon every row:own,organisation,third_party,mixed.- The credential is the grant, not a barrier. A key that permits a thing is not what stops it.
- Note the door: the same capability through two paths is one row with two
viaentries. - No score field exists and none is added.
- The vault id and the read key come from the push; until the lead pushes it, leave
vidandkeyout of the catalogue entry so the tile lists as asked for, not built, and say so in your work file.site/vaults/index.json's note explains the fields. - The PDF in
dist/isrender-abp-vault-pdf.mjs(Playwright); the zip is deterministic andchecked by CI.
Correct a mandate or answer an open question
Edit data/mandate.json (move rows between want, do_not_want, unstated) or data/grant.json (move a row's evidence, put the quote in note, delete the entry from research_needed). Rebuild, --check, and the history records whether the counts moved.
Cut a release
git fetch origin dev && git merge origin/dev # first; see the rules of engagement
node scripts/site/release.mjs 1.17.0 "What changed, in a line"
# write site/versions/1.17.0.md — what changed, why, what was not changed and why not
node scripts/site/generate.mjs && npm run check
git commit -am "v1.17.0 — what changed, in a line"
Notes are prose, in the voice of site/versions/1.15.0.md: bold lead phrases, links to the pages, a paragraph on what was deliberately not done. The number is claimed at merge time; if dev took it, run release.mjs again with the next one. CI tags the commit; nothing else to do.
Which number moves. The third, by default: 1.19.0 → 1.19.1. A fix, a page edit, a new vault, a new brief, an every-page chrome change, a restructured page: all patches. The second number is for a release that changes what the site is or what it sells: a new section in the menu, a new product on the pricing page, a page family rebuilt around a new idea. Two agents merging in one day should produce 1.19.1 and 1.19.2, not 1.20.0 and 1.21.0. The first number is the lead's to move.
Cut a Lab edition
A Lab page's content hash moved → node scripts/site/render-lab-pdfs.mjs <slug> (Playwright; serves the site on a local port). It appends to lab-editions.json with the digest; add a note to the entry saying what separates this edition from the last. Then generate.mjs lists it on the page. Never edit or remove an edition.
Add an interview page
A page sent to one kind of person (a founder, an investor, a CISO, an insurer), carrying a prompt they paste into their own assistant, which interviews them by voice and writes a summary they send back. The pattern is sgit.ai's brief of 24 September 2026 (D19).
cp site/interviews/_template.json site/interviews/<slug>.json # fill every field; the prompt exactly as it is to be copied
node scripts/site/build-interview-pages.mjs # writes site/interview-<slug>.html
# add interview-<slug>.html to site/pages.json: group More, unlisted, inside the More run
node scripts/site/generate.mjs && npm run check
- Six parts, always in this order: who it is for and why you; what we want to learn (three to six);
how it works in three steps; the prompt with a copy button; what happens to your answers; send it back. The generator enforces the order; do not hand-edit the page.
- The prompt says what RiskMandate is using only what the site claims. Record every change you make
to a prompt someone else wrote in the JSON's
source.changes. - Static, no tracking: the page loads nothing and sends nothing. Before publishing, run the prompt
once in the assistant it names and check the summary has every section it asks for.
Add a story
A story is a data file: site/stories/<slug>.json, told with the cast in site/stories/cast.json. The order is story, narrative, punchline, cast, storyboard, prompt; the picture is last and comes from whichever image model is being tried. Everything lives in the one folder, served as riskmandate.ai/stories/: the data, images/, the pages and their twins, and board.json.
cp site/stories/just-a-draft.json site/stories/<slug>.json # title, punchline, status, cast, source, truth, panels, prompt, for_merch
node scripts/site/build-stories.mjs # writes site/stories/index.html and site/stories/<slug>.html
# add stories/<slug>.html to site/pages.json: group Reading, unlisted, right after the other story entries
node scripts/site/generate.mjs && npm run check
statusisstoryboarduntil a model has drawn it; thendrawn, with the picture insite/stories/images/as WebP ("image": "images/<name>.webp"), its alt text, the model, the date, andnotes: what to correct.- Every line of dialogue names a cast member in the story's
cast; the build refuses an unknown one.A new character goes into
cast.jsonwith what they stand for on the model, first. - The prompt is
sharedplus one line per panel; the page appends the cast's looks and the style. - A page in this folder links from the site root (
/articles.html,/stories/<slug>.html) and setsRM.data.base="/"so the shared menu does too.cut()in the builder rewrites the donor's chrome that way; write body links absolute yourself. - The rules are the site's: fictionalised and said so, nobody's product drawn, nothing scored, the
site's words, and a ladder has steps (the build refuses the other word).
Change an agent's behaviour policy, or add an agent to the team
The team is data: site/team/team.json and one site/team/<agent>.json per agent, in the grammar the vaults use (23 primitives, four barriers, six evidence tiers, three undo classes). A grant row is what was observed or measured on the thing itself, with a note saying what was done and when, never typed from memory; a mandate is the lead's words. node scripts/site/build-team.mjs derives the delta and writes site/team/; the build refuses a row without a barrier, an evidence tier, an undo class and a note, a mandate that wants and refuses the same row, and anything credential-shaped. A new agent is a new JSON file, its slug in team.json → agents, two entries in site/pages.json (unlisted, in the Behaviour policies run), and, if it runs in Claude Code, a role in .claude/agents/<slug>.md.
Run a stories check-in
The stories are made with the lead and a studio model through one sgit vault, in the Email-FS-lite protocol; the site's agent is the publisher, the one party that touches both the vault and the site. The design is docs/briefs/architecture__the-stories-vault-and-the-three-way-workflow.md; the commands are in stories-vault/README.md and in the publisher's brief inside the vault. The lead's word in a session is written down as a message with send --from dinis.human, so the record is in the vault. board makes a revision when something moved and keeps the diff; --note says why. The vault key and the push token come from the lead in the session and go in no file. Say in your work file that you pushed the vault, and why.
Write a brief
docs/briefs/<kind>__<slug>.md. Header: title as a claim, date, author @website-agent, trigger (what the lead said, quoted), reads against (the sources with versions). Numbered sections. Tables where a table is honest. End with what this does not settle or decisions I need. Then add one line to .claude/onboarding/01-map.md and, if it changes the state, to 03-state-and-next.md. If the brief arrived as a file from outside, it also goes into the register: copy it byte for byte to site/assets/briefs/, add an entry to site/briefs-register.json with its sha256, size, arrival time and status, and render it on site/briefs.html. The test checks the digest.
Give a brief, a task or a work file its page on the console
Nothing to do but rerun the build. node scripts/site/build-admin.mjs renders every docs/**/*.md under site/admin/briefs/<slug>/, every .claude/briefs/Txx-*.md under site/admin/work/Txx/, every .claude/work/<branch>.md under site/admin/work/branches/<branch>/, and the onboarding files, CLAUDE.md and the prompts under site/admin/agents/. The counts on the console — what needs the lead, what is in flight, memos not fully worked — are read off 03-state-and-next.md (the queue table and Decisions the lead owns), the work files and briefs-register.json, so keeping those true is what keeps the console true. test_admin.mjs fails if a document has no page; build-admin.mjs --check fails in CI if the console is stale. Never hand-edit anything under site/admin/ except console.css.
Register a document that arrived from outside
See the paragraph above. Statuses: received, partly, processed, superseded, and they only move forward. produced names what exists because of it; not_done names what does not.
Touch a shared module
Edit scripts/site/modules/<name>.js, run node scripts/site/sync-modules.mjs, then generate.mjs. That is an every-page edit: claim it, commit it alone, merge it fast.
Change the chrome (header, footer, menu)
There is no template. The chrome is copied from a donor when a page is scaffolded, and add-licence-chrome.mjs shows the pattern for retrofitting one element to every page: an idempotent script with --check, run in CI. Do the same for anything new rather than sed-and-hope; then update new-page.mjs's FOOTER/header constants so scaffolded pages carry it too. A behaviour on every page (the drawer, the menu) lives in scripts/site/modules/ and is pushed out by sync-modules.mjs. After either kind of change, rerun every page builder (build-abp-pages, build-reviewer-pages, build-uk-support, build-business-cases, build-interview-pages) before npm run check: the pages they cut from a donor carry the donor's chrome, and the check compares them byte for byte. The version chip is the example: v1.34.7 made it visible on a laptop and put it first in the phone drawer.
Merge a branch into dev
.claude/commands/merge-to-dev.md is the checklist. Short form: merge dev in, regenerate, check, release last, check again, --no-ff into dev, delete your work file, push, watch CI.
Run it locally
bash scripts/run-locally__riskmandate_ai.sh → http://localhost:10070/. Use localhost: /scenarios/ and the vault pages decrypt with Web Crypto, which needs a secure context.
Drain the agent's inbox (the comms vault's lanes)
export COMMS_KEY=… # from the lead, or the environment; never a file
cd "$SCRATCH" && sgit clone "$COMMS_KEY" comms-riskmandate
node "$REPO/scripts/intake/drain.mjs" --vault comms-riskmandate --dry-run # look, then
node "$REPO/scripts/intake/drain.mjs" --vault comms-riskmandate
cd comms-riskmandate && sgit commit -m "@Agent drain: …" && sgit push --token "$SGSEND_TOKEN"
Accepted messages are agent-contact/accepted/*.eml; a contact message is answered from the mailbox, a registration goes to the programme's procedure (docs/programme/early-access-invitations.md). Full notes in scripts/intake/README.md. To change a lane or a key: edit the contact file, run build-agents.mjs, configure.mjs, release.
Publish on LinkedIn (the page, and the founders' profiles)
RiskMandate's page is https://www.linkedin.com/company/riskmandate/ (from 21 October 2026). Every article on this site, and every newsroom piece we want read, gets a LinkedIn item: an article of its own, a post that links it, or a place in a combined piece. Every item ends at https://customers.riskmandate.ai/, where we want people to try it.
- Add the item to
scripts/linkedin/items.mjs(kindarticle,postorprofile;shotsfor its pictures). - Serve the site on :8414 and run
node scripts/linkedin/capture.mjs --only <id>: riskmandate.ai is shot from theworking copy; the customers site and the newsroom are fetched by curl, which checks certificates against the session's CA bundle.
--coversredraws the 1280×720 covers only. node scripts/linkedin/build.mjswritessite/assets/linkedin/items.jsonand rebuilds the private desk,site/linkedin.html;--checkruns innpm run check;--todolists the site's articles with no item yet.- Release and merge: an article links its pictures at riskmandate.ai, so they must be live before it is pasted.
- The lead pastes from the desk, then sets
publishedandurlon the item; rebuild, commit.
The newsroom's thinking is retold on site/newsroom.html, built by node scripts/site/newsroom/build.mjs from scripts/site/newsroom/items.mjs: add an article there when the newsroom publishes one worth pointing to.