Skip to content
Open
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
51 changes: 51 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,57 @@ Read the blog skill before writing content. Preserve the user's img2img
triptych, mandatory hashtags, truthful limitations and relevant service offer.
Never omit the work report for a small, documentation-only or automation PR.

## New course to open a topic (Queen and every bee)

When a topic needs explaining, the answer is a course: **9 modules of 3
lessons each, 27 lessons** (owner, 2026-10-07). One topic is one course; two topics are two
courses, chained (the last lesson opens lesson 1 of the next). Live chain:
0 t27 basics -> 1 FPGA -> 2 AI numbers.

The recipe is a t27 module, not this prose:
[specs/course_recipe/course-27.t27](specs/course_recipe/course-27.t27) (steps,
rules, gates, cast commands; its tests hold the order). Planned courses:
[specs/course_recipe/roadmap.t27](specs/course_recipe/roadmap.t27). Read both
first; this section only points at them.

In short, so the Queen can assign it:

1. Copy the newest course spec in `apps/website/specs/course/` with its `-ru`
twin, rename ID / ROUTE / SHARE_PATH, register it in `courses.t27`.
2. Every lesson: one widget unique across all courses, one t27 spec that
compiles in the browser, black-and-white infographic or card.
3. Run the generators from `apps/website` (all-or-nothing):
`widgets-from-spec.mjs`, `widget-pages-from-spec.mjs`,
`course-from-spec.mjs`, `course-pages.mjs`; then the `check:course*`,
`check:widgets`, `check:onboarding` gates.
4. Casts are recorded only through `tri` commands; a lesson without a recording
says "pending", never fakes one.
5. The same PR carries its blog post (`body` English + `ruBody` Russian) and the
work report above.
6. Main moved under the PR: merge `origin/main` in, never rebase/force push.
A merged trinity PR is not live: ship through the
`release(board): ship trinity <sha>` PR in gHashTag/999-multibots-telegraf.

Traps found by the first course built from this section alone (trinity#1470,
full list in its "Recipe gaps"):

- Rename **ID, ROUTE, SHARE_PATH, RU_CONTRACT and GENERATED** in the copied spec.
- Code edits the recipe needs: route, starfield root and nav entry in `src/`;
blog post registered in `src/data/blog/index.ts` and `posts.ts`.
- `gallery.t27` keeps each category as one unbroken block: insert the new
widgets inside their category, then fix the index asserts in the gallery
tests; `course-from-spec.test.mjs` asserts the course count, bump it.
- Full gate list: `check:widgets`, `test:widgets-spec`, `check:widget-pages`
(run widget-pages before widgets), `check:course`, `test:course`,
`check:course-pages`, `check:course-recipe`, `check:onboarding`,
`check:play`, `tsc --noEmit`.
- A pending lesson still needs a gallery entry; ship it as a
`public/term/<id>/` placeholder with `pending`, never a fake widget.
- Russian widget words go in `i18n/widgets.ru.json` under the contract
`specs/widgets/i18n/widgets-ru.t27` (no Cyrillic in specs); the Russian text
must keep the same digits as the English.
- Work-report format: copy the `<!-- t27-work-report -->` block of trinity#1463.

**Version**: 2.0
**Date**: 2026-04-04
**Status**: Active
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Block } from '../types'

export const body: Block[] = [
{ "kind": "h", "text": "What changed" },
{ "kind": "p", "text": "The recipe for a t27 course used to live in one agent's private skill folder, where the Queen and her bees could not read it. It now lives in the repository as two t27 modules: specs/course_recipe/course-27.t27 (the steps, rules, gates and cast commands, with tests that hold their order) and specs/course_recipe/roadmap.t27 (the planned courses). AGENTS.md gains a short section that points at them, so any agent asked to open a topic knows the shape: 9 modules of 3 lessons, one topic per course, courses chained." },
{ "kind": "h", "text": "What is checked" },
{ "kind": "p", "text": "Both modules compile clean in the site's own wasm compiler: course-27.t27 has 7 tests with 26 asserts, roadmap.t27 has 4 tests with 30 asserts. Nothing yet runs them in CI; that is the next gap." },
{ "kind": "h", "text": "Limits" },
{ "kind": "p", "text": "A recipe is not a course. No new lessons ship with this PR, and the casts of the existing courses are still pending where the post of each course says so." }
]

export const ruBody: Block[] = [
{ "kind": "h", "text": "Что изменилось" },
{ "kind": "p", "text": "Рецепт курса t27 жил в личной папке навыков одного агента, куда Королева и её пчёлы не могли заглянуть. Теперь он лежит в репозитории двумя модулями t27: specs/course_recipe/course-27.t27 (шаги, правила, шлюзы и команды записи, а тесты держат их порядок) и specs/course_recipe/roadmap.t27 (план курсов). В AGENTS.md появился короткий раздел со ссылкой на них, так что любой агент, которому поручили раскрыть тему, знает форму: 9 модулей по 3 урока, одна тема — один курс, курсы идут цепочкой." },
{ "kind": "h", "text": "Что проверено" },
{ "kind": "p", "text": "Оба модуля чисто собираются компилятором сайта на wasm: в course-27.t27 7 тестов и 26 проверок, в roadmap.t27 4 теста и 30 проверок. В CI их пока ничто не запускает — это следующая дыра." },
{ "kind": "h", "text": "Ограничения" },
{ "kind": "p", "text": "Рецепт — ещё не курс. Новых уроков в этом PR нет, а записи существующих курсов по-прежнему ждут там, где об этом сказано в посте каждого курса." }
]
23 changes: 23 additions & 0 deletions apps/website/src/data/blog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,29 @@ export const postsIndex: PostMeta[] = [
],
},
},
{
slug: "the-queen-knows-how-to-build-a-course",
title: "The Queen now knows how to build a course",
summary: "[a recipe, not a course; the recipe modules compile but no CI job runs their tests yet] The t27 course recipe moved from one agent's private folder into the repository as two t27 modules, and AGENTS.md points at them, so the Queen can assign a new 27-lesson course for any topic.",
date: "2026-10-07",
readingMinutes: 2,
tags: ["t27", "Course", "Agents"],
receipts: [
{ label: "The recipe, specs/course_recipe/course-27.t27", href: "https://github.com/gHashTag/trinity/blob/main/specs/course_recipe/course-27.t27" },
{ label: "The plan, specs/course_recipe/roadmap.t27", href: "https://github.com/gHashTag/trinity/blob/main/specs/course_recipe/roadmap.t27" },
],
openQuestions: [
"No CI job runs the recipe's tests yet.",
],
published: true,
ru: {
title: "Королева теперь знает, как собрать курс",
summary: "[рецепт, а не курс; модули рецепта собираются, но их тесты пока не запускает CI] Рецепт курса t27 переехал из личной папки одного агента в репозиторий двумя модулями t27, а AGENTS.md ссылается на них, так что Королева может поручить новый курс из 27 уроков на любую тему.",
openQuestions: [
"Тесты рецепта пока не запускает ни одно задание CI.",
],
},
},
{
slug: "a-course-pr-carries-its-post",
title: "A course PR now carries its blog post",
Expand Down
2 changes: 2 additions & 0 deletions apps/website/src/data/blog/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { body as body_course_three_verifying_hardware, ruBody as ruBody_course_t
import { body as body_course_zero_t27_basics, ruBody as ruBody_course_zero_t27_basics } from './bodies/course-zero-t27-basics'
import { body as body_tri_test_now_runs_the_tests, ruBody as ruBody_tri_test_now_runs_the_tests } from './bodies/tri-test-now-runs-the-tests'
import { body as body_two_courses_twenty_seven_lessons_each, ruBody as ruBody_two_courses_twenty_seven_lessons_each } from './bodies/two-courses-twenty-seven-lessons-each'
import { body as body_the_queen_knows_how_to_build_a_course, ruBody as ruBody_the_queen_knows_how_to_build_a_course } from './bodies/the-queen-knows-how-to-build-a-course'
import { body as body_a_course_pr_carries_its_post, ruBody as ruBody_a_course_pr_carries_its_post } from './bodies/a-course-pr-carries-its-post'
import { body as body_one_outlier_twenty_three_zeros, ruBody as ruBody_one_outlier_twenty_three_zeros } from './bodies/one-outlier-twenty-three-zeros'
import { body as body_a_terminal_for_seven_backends_in_an_x_post, ruBody as ruBody_a_terminal_for_seven_backends_in_an_x_post } from './bodies/a-terminal-for-seven-backends-in-an-x-post'
Expand Down Expand Up @@ -110,6 +111,7 @@ const bodies: Record<string, PostBody> = {
'course-zero-t27-basics': { body: body_course_zero_t27_basics, ruBody: ruBody_course_zero_t27_basics },
'tri-test-now-runs-the-tests': { body: body_tri_test_now_runs_the_tests, ruBody: ruBody_tri_test_now_runs_the_tests },
'two-courses-twenty-seven-lessons-each': { body: body_two_courses_twenty_seven_lessons_each, ruBody: ruBody_two_courses_twenty_seven_lessons_each },
'the-queen-knows-how-to-build-a-course': { body: body_the_queen_knows_how_to_build_a_course, ruBody: ruBody_the_queen_knows_how_to_build_a_course },
'a-course-pr-carries-its-post': { body: body_a_course_pr_carries_its_post, ruBody: ruBody_a_course_pr_carries_its_post },
'one-outlier-twenty-three-zeros': { body: body_one_outlier_twenty_three_zeros, ruBody: ruBody_one_outlier_twenty_three_zeros },
'a-terminal-for-seven-backends-in-an-x-post': { body: body_a_terminal_for_seven_backends_in_an_x_post, ruBody: ruBody_a_terminal_for_seven_backends_in_an_x_post },
Expand Down
175 changes: 175 additions & 0 deletions specs/course_recipe/course-27.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: Apache-2.0
; course-27.t27 -- turn any topic into a t27 course: 9 modules of 3 lessons each
; This file IS the skill. SKILL.md next to it only points here. Every step, rule and gate
; below is a constant, and the test blocks hold the order and the shape, so a step cannot
; be dropped or moved without a red test. Compile and test it with the site's own compiler:
; any harness that calls loadCompiler (scripts/agents-from-specs.mjs) and runSpecTests (scripts/viewport-from-spec.mjs) in apps/website
; (or any harness that calls loadCompiler + runSpecTests from gHashTag/trinity
; apps/website/scripts). ASCII only, English only.

; THE LIVING TEMPLATE is the newest course spec on the site, not a copy kept here:
; gHashTag/trinity apps/website/specs/course/ai-numbers.t27 (course 2)
; gHashTag/trinity apps/website/specs/course/courses.t27 (the catalog, the order)
; Copy the newest course spec, rename ID / ROUTE / SHARE_PATH / RU_CONTRACT, then replace
; the words. A template kept here would drift from the generator; that is the recorded
; defect of this codebase family (one rule in three files, two of them right).

; WHAT A COURSE IS: 9 modules of 3 lessons each, 27 lessons (a TRI-27 word;
; owner's decision 2026-10-07: 9 modules of 3; a flattening to 27 modules of 1 was a
; misreading and was reverted). One course per topic.
; Two topics are two courses, chained: the last lesson of one opens lesson 1 of the next,
; and each has its own address, share pages and cards, so each can be shared on its own.
; Every lesson opens ONE main widget (unique across all courses) and ONE t27 spec that
; compiles clean in the reader's browser.
; phi^2 + 1/phi^2 = 3 | TRINITY

module course_27;

pub const KIND : str = "skill";
pub const SCHEMA_VERSION : u8 = 1;
pub const LESSONS : u8 = 27;
pub const MODULES : u8 = 9;
pub const LESSONS_PER_MODULE : u8 = 3;

; Where the course lives (gHashTag/trinity, relative to apps/website).
pub const CATALOG : str = "specs/course/courses.t27";
pub const TEMPLATE : str = "specs/course/ai-numbers.t27";
pub const GALLERY : str = "specs/widgets/gallery.t27";
pub const ROADMAP : str = "specs/course_recipe/roadmap.t27";
; The first course of the catalog (course 0, t27-basics) shares at learn/, every later course at learn/<id>/; lesson pages stay at
; learn/<lesson>/ whatever the course, so an old lesson link never breaks.
pub const SHARE_FIRST : str = "learn/";
pub const SHARE_NEXT : str = "learn/<id>/";

; --- The steps, in order -----------------------------------------------------------------
pub const STEP_COUNT : u8 = 14;
pub const STEP_IDS : [14]str = [
"pick",
"specs",
"shape",
"words",
"russian",
"widgets",
"catalog",
"generate",
"preview",
"post",
"land",
"ship",
"social",
"learn"
];
pub const STEP_SAYS : [14]str = [
"Pick the topic from the roadmap and a lower-case id; the app route is #/<id>, the share path learn/<id>/.",
"Find 27 lesson specs that compile clean in the browser player; a missing spec is written in gHashTag/t27 first (skill t27-spec-first) and reaches the site by the world scan, never hand-copied into the site.",
"Order 9 modules of 3 lessons each so each lesson uses only what earlier lessons taught; a topic that needs more than 27 lessons is two courses.",
"Copy the newest course spec, keep every SAY_ key the other courses have, and write the words: no superlatives, every number from a spec constant, a player run or a recording.",
"Write i18n/<id>.ru.json and specs/course/<id>-ru.t27: every field translated, the same digits and the same {N} placeholders as the English.",
"Give every lesson its own main widget; a new widget is a recording made with tri commands only, in black and white, with one planted one-line bug that fails exactly one named test and is put back in the same recording.",
"Add the course to specs/course/courses.t27: COURSE_COUNT, COURSE_IDS, COURSE_SPECS and the order test; prev and next links follow from the order.",
"Run widgets-from-spec, course-from-spec, course-pages (static pages, black-and-white cards, sitemap, hreflang) and onboarding-from-spec (llms.txt and agents.t27 name every course).",
"Preview the dev server: the course landing, lesson 1, the last lesson's link to the next course, a lesson of the previous course, a phone width.",
"Write the blog post in the same PR: English body and Russian ruBody, receipts that link the recordings and pages, limits said out loud, an SEO title and summary, and a black-and-white image for the networks.",
"Open the issue, then the PR with Closes #N and the t27-work-report block whose head_sha is the PR head; a reviewer bee merges with --match-head-commit, never --admin or --auto.",
"Ship through the gHashTag/999-multibots-telegraf release(board) PR and prove it live by the bundle, not by a green status.",
"Hand the post to the blog-share desk: one draft per network, logged per client in the CRM; nothing goes out without the owner's yes.",
"Save what this course taught into a tri command (a new one, or the old one updated) and into this file; the next course starts from both."
];

; --- The rules -----------------------------------------------------------------------------
pub const RULE_COUNT : u8 = 10;
pub const RULE_IDS : [10]str = [
"twenty-seven",
"spec-and-widget",
"casts-through-tri",
"black-and-white",
"post-per-pr",
"seo",
"no-superlatives",
"numbers-from-runs",
"no-reddit",
"owner-says-yes"
];
pub const RULE_SAYS : [10]str = [
"Every course is exactly 9 modules of 3 lessons each, 27 lessons; two topics are two courses, never one course of 54.",
"Every lesson opens one main widget and one t27 spec; the main widget is used by no other lesson of any course.",
"Every recording types tri commands only (sed -n to show lines, tri test, tri mutate plant, tri mutate spec, git diff); what the commands lack is added to tri first.",
"Cards, recordings and post images are white on black like the course page: no colour fields, no glows, widget pictures in greyscale.",
"A PR that changes a course carries its blog post, English and Russian, in the same PR.",
"Every course and every lesson has a static page with its own preview card, canonical link, hreflang pair and a line in learn/sitemap.xml; no page redirects.",
"No superlative in either language: no first, only, best, unique; in Russian say lesson 1, not the first lesson.",
"A number on a page comes from a spec constant, a test the player ran, or a recording; none is typed from memory.",
"No Reddit link on the course, the player or a post: r/t27ai is banned.",
"No post, mail or message leaves without the owner's explicit yes; ASK_NEEDS_YES stays true."
];

; --- The gates, all of them, before the PR -------------------------------------------------
; Run from apps/website. A course PR is not ready while any of them is red.
pub const GATE_COUNT : u8 = 8;
pub const GATES : [8]str = [
"npm run check:widgets",
"npm run test:widgets-spec",
"npm run check:widget-pages",
"npm run check:course",
"npm run test:course",
"npm run check:course-pages",
"npm run check:onboarding",
"npx tsc --noEmit -p ."
];

; --- Recording a lesson: the tri commands a cast types, in order ---------------------------
pub const CAST_STEP_COUNT : u8 = 5;
pub const CAST_STEPS : [5]str = [
"sed -n <lines>p <spec>",
"tri test <spec>",
"tri mutate plant --file <spec> --line <n> --from <a> --to <b> --expect <test>",
"tri mutate spec --file <spec> --fn <fn>",
"git diff --exit-code <spec>"
];

test a_course_is_three_cubed {
assert LESSONS_PER_MODULE == 3;
assert MODULES == 9;
assert MODULES * LESSONS_PER_MODULE == LESSONS;
assert LESSONS == 3 * 3 * 3;
}

test specs_come_before_words_and_widgets {
assert STEP_IDS[0] == "pick";
assert STEP_IDS[1] == "specs";
assert STEP_IDS[3] == "words";
assert STEP_IDS[5] == "widgets";
}

test the_catalog_follows_the_widgets_and_the_generators_follow_the_catalog {
assert STEP_IDS[6] == "catalog";
assert STEP_IDS[7] == "generate";
assert STEP_IDS[8] == "preview";
}

test the_post_is_in_the_pr_and_the_owner_posts {
assert STEP_IDS[9] == "post";
assert STEP_IDS[10] == "land";
assert STEP_IDS[12] == "social";
assert RULE_IDS[4] == "post-per-pr";
assert RULE_IDS[9] == "owner-says-yes";
}

test the_experience_goes_back_into_tri {
assert STEP_COUNT == 14;
assert STEP_IDS[13] == "learn";
assert RULE_IDS[2] == "casts-through-tri";
assert CAST_STEPS[1] == "tri test <spec>";
}

test the_course_gates_are_all_named {
assert GATE_COUNT == 8;
assert GATES[3] == "npm run check:course";
assert GATES[5] == "npm run check:course-pages";
assert GATES[6] == "npm run check:onboarding";
}

test every_later_course_shares_under_its_own_path {
assert SHARE_FIRST == "learn/";
assert SHARE_NEXT == "learn/<id>/";
}
Loading
Loading