diff --git a/.gitignore b/.gitignore index 7aab2b78f8..bad690c296 100644 --- a/.gitignore +++ b/.gitignore @@ -439,3 +439,7 @@ fpga-flash # Enumerating them one by one was tried first and is whack-a-mole: the widened # corpus immediately hit three more. !apps/website/public/t27/** + +# apps/website/docs: hand-written contracts for the website (e.g. the optional +# agent control API), not generated reports; the *.md rule above would drop them. +!apps/website/docs/** diff --git a/apps/website/.env.example.queen b/apps/website/.env.example.queen index 717cba9a71..287be00928 100644 --- a/apps/website/.env.example.queen +++ b/apps/website/.env.example.queen @@ -13,3 +13,12 @@ # Ollama also has to allow the browser's origin: OLLAMA_ORIGINS=http://localhost:4179 VITE_QUEEN_OLLAMA_URL=http://localhost:11434 # VITE_QUEEN_OLLAMA_MODEL=qwen3:4b + +# Optional live control plane for the Skill and Cron Explorers. Not a secret: +# the URL is compiled into the public bundle. When set, the CONTROL strip on a +# skill or job card gets working buttons that POST to +# {url}/crons//run | /enable | /disable and {url}/skills//run +# with the JSON body {"id", "requestedAt"} — see docs/agent-control-api.md. +# When unset (the default, and the deployed state today) the strip says so and +# every action goes through the spec and the hosts' own panels instead. +# VITE_AGENT_CONTROL_URL=https://example.invalid/agent-control diff --git a/apps/website/docs/agent-control-api.md b/apps/website/docs/agent-control-api.md new file mode 100644 index 0000000000..7a6c06ed84 --- /dev/null +++ b/apps/website/docs/agent-control-api.md @@ -0,0 +1,55 @@ +# Agent control API (optional control plane for skills and crons) + +Status: **contract only — no server implements this today.** The Skill and Cron +Explorers on t27.ai read their catalogs from `.t27` specs +(`public/t27/files/specs/{skills,crons}/*.t27` → `scripts/agents-from-specs.mjs` +→ `public/skills/spec-skills.json`, `public/crons/spec-crons.json`). The +management strip on each card works without any server: `ENABLED` is read from +the spec and changed by editing the spec (GitHub edit link), and "run now" +points at the host that actually owns the job (GitHub Actions workflow page, +Railway service, Inngest dashboard). A timer inside a process has no outside +handle and the strip says so. + +When `VITE_AGENT_CONTROL_URL` is set at build time, the strip additionally gets +live buttons that call the endpoints below. When it is unset the strip shows +"Контур управления не подключён — действия идут через спеку и панели хостов" / +"No control plane connected — actions go through the spec and the hosts' panels". + +## Endpoints + +All requests are `POST`, body `application/json`: + +```json +{ "id": "", "requestedAt": "" } +``` + +| Endpoint | Meaning | +|-------------------------------------------|----------------------------------------------------------------| +| `POST {url}/crons//run` | Start the job now (`` e.g. `github-actions/trinity/site-live-gate`) | +| `POST {url}/crons//enable` | Ask the plane to set the job's `ENABLED` to `true` | +| `POST {url}/crons//disable` | Ask the plane to set the job's `ENABLED` to `false` | +| `POST {url}/skills//run` | Launch the skill once (`` e.g. `trinity/doctor`) | + +`` is the catalog id with each `/`-separated segment URL-encoded; the +segments themselves are kept as path segments. + +## Responses + +- `202 Accepted` — the request was queued; body may carry `{ "runId": "..." }`. +- `200 OK` — for `enable`/`disable`, the new state was recorded. +- `404 Not Found` — the id names no known skill or job. +- `409 Conflict` — the job is a timer or otherwise has no outside handle. +- `501 Not Implemented` — the plane knows the id but cannot act on that host. + +Any non-2xx is shown verbatim (status and body) in the strip; nothing is +retried and nothing is reported as done that the server did not confirm. + +## Honesty rules the client keeps + +- `enable`/`disable` never edit the spec themselves; the spec remains the + source of truth and a plane that flips state must land the change as a + commit to `specs/crons/.t27`, or the site will keep showing the spec's + value. +- No credentials are sent by the browser (`credentials: 'omit'`); a plane that + needs auth must sit behind its own gateway. +- The URL is public (it is in the bundle); it must not embed a token. diff --git a/apps/website/i18n/agents.ru.json b/apps/website/i18n/agents.ru.json new file mode 100644 index 0000000000..8b16de117a --- /dev/null +++ b/apps/website/i18n/agents.ru.json @@ -0,0 +1,183 @@ +{ + "$spec": "specs/i18n/agents-ru.t27", + "locale": "ru", + "entries": { + "github-actions/999-multibots-telegraf/ci": { + "SUMMARY": "Раз в день прогоняет весь конвейер сборки, проверки типов и тестов, чтобы зависимость, гниющая без пуша, всё равно была поймана." + }, + "github-actions/999-multibots-telegraf/security": { + "SUMMARY": "Еженедельная проверка безопасности: поиск секретов по всей истории, аудит зависимостей и сканирование кода." + }, + "github-actions/t27/formal-mutation": { + "SUMMARY": "Раз в неделю мутирует сгенерированный RTL и требует, чтобы каждый формальный гейт покраснел на своей мутации — доказательство, что гейты кусаются." + }, + "github-actions/t27/pr-dashboard": { + "SUMMARY": "Каждый час пересобирает панель pull request из текущего состояния открытых PR." + }, + "github-actions/trinity/agent-cron-cleanup": { + "SUMMARY": "Каждые два часа находит застрявшие деплои агентов и убивает их." + }, + "github-actions/trinity/agent-queue-drain": { + "SUMMARY": "Каждые пять минут ищет задачи в очереди и свободные слоты агентов и запускает следующего агента, когда есть место." + }, + "github-actions/trinity/brain-ci": { + "SUMMARY": "Еженедельный стресс-тест мозга — длинная версия гейта здоровья, который получает каждый коммит." + }, + "github-actions/trinity/codegen": { + "SUMMARY": "Раз в день проверяет генератор кода VIBEE независимо от того, пушил ли кто-нибудь." + }, + "github-actions/trinity/discover-callers": { + "SUMMARY": "Читает GitHub в поисках репозиториев, запускающих RTL-проверку, чтобы галерея считала своих пользователей сама." + }, + "github-actions/trinity/pages-health-check": { + "SUMMARY": "Каждые пятнадцать минут запрашивает опубликованный сайт, следует редиректу и передеплоит только когда свидетельства говорят, что сломан именно деплой." + }, + "github-actions/trinity/signal-health-self": { + "SUMMARY": "Применяет проверку signal-health к репозиторию, который её продаёт, и записывает числа в данные сайта." + }, + "github-actions/trinity/site-live-gate": { + "SUMMARY": "Сравнивает бандл с контент-хешем на апексе со сборкой main, чтобы «задеплоено» означало, что читатель это видит." + }, + "inngest/999-multibots-telegraf/check-stuck-trainings": { + "SUMMARY": "Находит обучения моделей, застрявшие в pending или processing дольше порога, и сверяет их с Replicate." + }, + "inngest/999-multibots-telegraf/daily-sales-advisor": { + "SUMMARY": "Собирает каждому владельцу бота утренний отчёт: вчерашняя выручка, расходы недели и что реально покупали пользователи." + }, + "inngest/999-multibots-telegraf/health-check": { + "SUMMARY": "Каждые полчаса опрашивает основной API и сервис Inngest и сообщает, кто ответил." + }, + "inngest/999-multibots-telegraf/log-monitor": { + "SUMMARY": "Раз в день читает логи приложения, поручает модели свести ошибки и предупреждения и отправляет дайджест в Telegram." + }, + "inngest/999-multibots-telegraf/periodic-webhook-health-check": { + "SUMMARY": "Каждый час проверяет оба URL вебхука Telegram и шлёт админу критическую тревогу, когда не отвечает ни один." + }, + "inngest/999-multibots-telegraf/skill-detector": { + "SUMMARY": "Раз в день ищет в успешных генерациях повторяющиеся паттерны моделей и промптов и сохраняет новые как переиспользуемые навыки бота." + }, + "railway-cron/999/jobsearch-cron": { + "SUMMARY": "Сервис поиска вакансий по расписанию в проекте Railway, доступен на cron.t27.ai." + }, + "t27/measure-corpus": { + "SUMMARY": "Выбор правильного инструмента для любого утверждения о корпусе спек t27 и список ловушек, которые уже давали неверные опубликованные числа. Использовать перед тем, как цитировать любое число о корпусе." + }, + "t27/phi-loop": { + "SUMMARY": "Девятифазный цикл spec-first разработки: Issue → Spec → TDD → Code → Gen → Seal → Verify → Land → Learn." + }, + "t27/tri": { + "SUMMARY": "Команды tri для работы со скилом: begin, правка .t27-спеки, seal hash, gen, test, verdict, experience. Реестр скилов описан в compiler/skill/registry.t27." + }, + "t27/tri-pipeline": { + "SUMMARY": "Выполнение команд tri (gen, test, verify, seal, verdict) для spec-first разработки." + }, + "t27/wave-audit": { + "SUMMARY": "Полный аудит проекта t27: разбор GitHub issues, поиск слабых мест, обзор SOTA, декомпозированный план и реализация критичных исправлений." + }, + "t27/wrap-up": { + "SUMMARY": "Оформление и загрузка итогов сессии в NotebookLM как постоянной семантической памяти." + }, + "timer/999-multibots-telegraf/agent-autopilot-L1118": { + "SUMMARY": "Один цикл автопилота — проход генерации и затем тик канала; тик пропускается целиком, если предыдущий цикл ещё идёт." + }, + "timer/999-multibots-telegraf/bot-owner-billing-L474": { + "SUMMARY": "Пересчитывает долг каждого владельца бота по журналу платежей, предупреждает на мягком, тревожном и критическом порогах и отключает бота, три дня остающегося критическим." + }, + "timer/999-multibots-telegraf/crmProactive-L375": { + "SUMMARY": "Один проактивный ход продавца для владельца: обходит очередь CRM и кладёт перед ним следующую карточку-черновик без просьбы." + }, + "timer/999-multibots-telegraf/generate-jobs-L164": { + "SUMMARY": "Через час после завершения выметает из памяти законченные задания генерации, когда потерявший соединение клиент успел забрать результат." + }, + "timer/999-multibots-telegraf/notificationHandler-L318": { + "SUMMARY": "Разгружает очередь уведомлений и доставляет ждущие в ней сообщения Telegram." + }, + "timer/999-multibots-telegraf/notificationHandler-L324": { + "SUMMARY": "Удаляет уведомления, настолько старые, что их уже никто не прочитает." + }, + "timer/999-multibots-telegraf/production-monitor-L80": { + "SUMMARY": "Повторяет проверки контейнера, nginx, порта, домена, вебхука и ресурсов системы и применяет включённые исправления." + }, + "timer/999-multibots-telegraf/provider-health-monitor-L344": { + "SUMMARY": "Проверяет каждого не отключённого AI-провайдера и записывает, отвечает ли он сейчас." + }, + "timer/999-multibots-telegraf/render-server-L10845": { + "SUMMARY": "Пингует каждого websocket-пира и отключает тех, кто не ответил в прошлом раунде." + }, + "timer/999-multibots-telegraf/render-server-L2301": { + "SUMMARY": "Удаляет из карты в памяти задания рендера старше часа." + }, + "timer/999-multibots-telegraf/render-server-L2325": { + "SUMMARY": "Читает журнал улья и доставляет отчёт королевы хранителям: тревоги — сразу, обычные события — пакетом." + }, + "timer/999-multibots-telegraf/render-server-L919": { + "SUMMARY": "Перечитывает из Postgres список отозванных сессий, чтобы отозванная сессия браузера перестала приниматься." + }, + "timer/999-multibots-telegraf/taskCache-L27": { + "SUMMARY": "Сбрасывает записи кэша видеогенерации старше пятнадцати минут, чтобы завершённая задача не блокировала следующий запрос." + }, + "timer/999-multibots-telegraf/video-task-store-L156": { + "SUMMARY": "Забывает видеозадачи старше часа и переписывает хранилище на диск, если что-то было удалено." + }, + "trinity/blog-post": { + "SUMMARY": "Написать и опубликовать пост в блог t27.ai: где блог реально живёт, схема Post с обязательными свидетельствами, обложка и проверки." + }, + "trinity/board-sync": { + "SUMMARY": "Автосинхронизация GitHub Project Board: заполнить пустые поля, согласовать метки со статусом доски, проверить полноту полей." + }, + "trinity/cloud": { + "SUMMARY": "Панель Trinity Cloud Dev: контейнеры, тренировочная ферма, issues, PR, расходы. Для проверки состояния роя агентов и CI/CD." + }, + "trinity/doctor": { + "SUMMARY": "ЛЕКАРЬ: диагностика, лечение, коммит грязных файлов, надзор за мусором, документацией и дубликатами, честный отчёт. Каждый цикл = действие + доказательство." + }, + "trinity/farm-garden": { + "SUMMARY": "Автономный управляющий популяцией фермы: наблюдает, эволюционирует и перерабатывает рабочих обучения HSLM." + }, + "trinity/fpga-synth": { + "SUMMARY": "Конвейер синтеза FPGA от .tri-спеки до битстрима. Для работы с Verilog, FPGA и аппаратным синтезом." + }, + "trinity/god-mode": { + "SUMMARY": "GOD MODE: панель наблюдения за агентами — состояние роя, очередь задач, соблюдение конвейера, предохранители, активность git, нарушения правил." + }, + "trinity/implement-issue": { + "SUMMARY": "Прочитать GitHub issue, создать ветку, реализовать решение и открыть PR. Для работы по номеру задачи." + }, + "trinity/queen-hive-visuals": { + "SUMMARY": "Поддержка плавающего улья Королевы, оригинального логотипа TRINITY остриём вниз, чётких табло задач и согласованных движений камеры." + }, + "trinity/review-code": { + "SUMMARY": "Ревью недавних изменений кода: ошибки, стиль, улучшения. После написания кода или перед коммитом." + }, + "trinity/run-tests": { + "SUMMARY": "Собрать проект, проверить предупреждения, прогнать все тесты и отчитаться. Быстрая проверка здоровья кодовой базы." + }, + "trinity/scholar": { + "SUMMARY": "Саморазвивающийся исследовательский агент: сканирует веб на релевантные технологии, оценивает находки, предлагает улучшения. Perplexity Sonar через MCP." + }, + "trinity/status": { + "SUMMARY": "Живая панель системы: 12-мерная оценка уробороса, verdict v3, агенты, здоровье сборки, рекомендации." + }, + "trinity/tech-tree": { + "SUMMARY": "Дерево технологических зависимостей: DAG задач, предпосылки, критический путь, заблокированные и готовые задачи. Единый источник порядка работ." + }, + "trinity/tri": { + "SUMMARY": "Панель статуса TRI: по умолчанию режим мысли агента, /tri full — визуальная панель." + }, + "trinity/trinity-test": { + "SUMMARY": "Прогон тестовых наборов Trinity и разбор результатов: VSA, VM, Firebird, WASM или всё целиком." + }, + "trinity/ux-wave": { + "SUMMARY": "Автономные волны улучшения UX: 4 параллельных агента на волну, аудит каждые 5 волн, коммит исправлений. Для чата и сети Queen UI." + }, + "trinity/vibee-gen": { + "SUMMARY": "Генерация кода Zig или Verilog из спецификаций VIBEE. При создании или обновлении .tri-спек и перегенерации кода." + }, + "trinity/vsa-verify": { + "SUMMARY": "Проверка математических доказательств и операций VSA: bind/unbind/bundle, тождества phi." + }, + "trinity/wave": { + "SUMMARY": "Управление волнами фермы: аккаунты Railway, запуск рабочих, раскатка конфигов, прогресс волны. Для масштабирования тренировочной фермы." + } + } +} diff --git a/apps/website/package.json b/apps/website/package.json index bf9ecf21e8..6909325b2d 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -14,8 +14,8 @@ "check:spec-catalog": "node --experimental-strip-types qa/spec-catalog-contract.mjs", "core": "node scripts/spec-core.mjs", "check:shared-core": "node --experimental-strip-types qa/shared-spec-core.mjs", - "prebuild": "npm run core -- index && node scripts/skills-core.mjs index", - "prebuild:ci": "npm run core -- index && node scripts/skills-core.mjs index", + "prebuild": "npm run core -- index && node scripts/skills-core.mjs index && node scripts/agents-from-specs.mjs", + "prebuild:ci": "npm run core -- index && node scripts/skills-core.mjs index && node scripts/agents-from-specs.mjs", "dev": "vite", "build": "vite build", "build:ci": "vite build", @@ -64,7 +64,9 @@ "check:clients-console": "node --experimental-strip-types qa/clients-console-contract.mjs", "check:explorer-languages": "node qa/explorer-language-contract.mjs", "test:sync-skills": "node --test scripts/sync-skills.test.mjs scripts/skills-core.test.mjs", - "test:sync-crons": "node --test scripts/sync-crons.test.mjs" + "test:sync-crons": "node --test scripts/sync-crons.test.mjs", + "check:agents": "node --experimental-strip-types qa/agents-spec-contract.mjs", + "test:agents-specs": "node --test scripts/agents-from-specs.test.mjs" }, "dependencies": { "@babylonjs/core": "^9.25.0", diff --git a/apps/website/public/crons/spec-crons.json b/apps/website/public/crons/spec-crons.json new file mode 100644 index 0000000000..abd29b5fbb --- /dev/null +++ b/apps/website/public/crons/spec-crons.json @@ -0,0 +1 @@ +{"codeOnly":[],"compilerWasmSha256":"4d9c0447b5ca288790ab03d3dffc2dda629ed47af9d6731a105fc923aea0aee4","contentSha256":"049cd543e1ad1e538233eb9d45ad87c3bcd7f1161b025e20eaea782a946dff1c","counts":{"byHost":{"github-actions":12,"inngest":6,"railway-cron":1,"timer":14},"codeOnly":0,"specOnly":0,"specPlusCode":33,"specs":33,"typecheckOk":33,"withRuns":0},"crons":[{"code":{"health":"warn","kind":"github-actions","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/.github/workflows/ci.yml#L10","where":{"file":".github/workflows/ci.yml","host":"github-actions","line":10,"service":null}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"github-actions","ID":"github-actions/999-multibots-telegraf/ci","KIND":"cron","NAME":"CI/CD Pipeline","NOTE":"GitHub Actions on this repository have not run since 2026-06-02 (billing); the schedule stands in the file but does not fire.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 2 * * *","SERVICE":".github/workflows/ci.yml","SUMMARY_EN":"Runs the whole build, type-check and test pipeline once a day so a dependency that rots without a push still gets caught.","TZ":"UTC"},"health":"ok","id":"github-actions/999-multibots-telegraf/ci","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_ci","name":{"en":"CI/CD Pipeline"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/999-multibots-telegraf/actions/workflows/ci.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"8aea8946d266dd32d1455a5ae70c1b8a3b79f0ee4ac7889f68f14d0918094484","specPath":"specs/crons/999-multibots-telegraf-ci.t27","summary":{"en":"Runs the whole build, type-check and test pipeline once a day so a dependency that rots without a push still gets caught.","ru":"Раз в день прогоняет весь конвейер сборки, проверки типов и тестов, чтобы зависимость, гниющая без пуша, всё равно была поймана."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","kind":"github-actions","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/.github/workflows/security.yml#L10","where":{"file":".github/workflows/security.yml","host":"github-actions","line":10,"service":null}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"github-actions","ID":"github-actions/999-multibots-telegraf/security","KIND":"cron","NAME":"Security Checks","NOTE":"GitHub Actions on this repository have not run since 2026-06-02 (billing); the schedule stands in the file but does not fire.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 3 * * 0","SERVICE":".github/workflows/security.yml","SUMMARY_EN":"Runs the weekly security sweep: secret detection over the full history, dependency audit and code scanning.","TZ":"UTC"},"health":"ok","id":"github-actions/999-multibots-telegraf/security","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_security","name":{"en":"Security Checks"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/999-multibots-telegraf/actions/workflows/security.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"85fd8a7d31b783c57da0443c9ca2156d6d786867bea9dfee682f6208f876e91c","specPath":"specs/crons/999-multibots-telegraf-security.t27","summary":{"en":"Runs the weekly security sweep: secret detection over the full history, dependency audit and code scanning.","ru":"Еженедельная проверка безопасности: поиск секретов по всей истории, аудит зависимостей и сканирование кода."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/t27/blob/e804cd062af9bd4424a8579ea54b1f21381580de/.github/workflows/formal-mutation.yml#L25","where":{"file":".github/workflows/formal-mutation.yml","host":"github-actions","line":25,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/t27/formal-mutation","KIND":"cron","NAME":"formal-mutation","NOTE":"Weekly rather than per-push because a full run costs about thirty yosys invocations.","ON_FAILURE":"log","REPO":"t27","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 5 * * 1","SERVICE":".github/workflows/formal-mutation.yml","SUMMARY_EN":"Proves the formal gates actually bite by mutating the generated RTL once a week and requiring each gate to go red for its own mutation.","TZ":"UTC"},"health":"ok","id":"github-actions/t27/formal-mutation","inSpecCorpus":false,"messages":[],"moduleName":"cron_t27_formal_mutation","name":{"en":"formal-mutation"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/t27/actions/workflows/formal-mutation.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"f23f8e3b111bc55516c6e1860493acf41325dd40e6bf1b39842ba6c0dd906fbe","specPath":"specs/crons/t27-formal-mutation.t27","summary":{"en":"Proves the formal gates actually bite by mutating the generated RTL once a week and requiring each gate to go red for its own mutation.","ru":"Раз в неделю мутирует сгенерированный RTL и требует, чтобы каждый формальный гейт покраснел на своей мутации — доказательство, что гейты кусаются."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/t27/blob/e804cd062af9bd4424a8579ea54b1f21381580de/.github/workflows/pr-dashboard.yml#L6","where":{"file":".github/workflows/pr-dashboard.yml","host":"github-actions","line":6,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/t27/pr-dashboard","KIND":"cron","NAME":"PR Dashboard","ON_FAILURE":"log","REPO":"t27","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 * * * *","SERVICE":".github/workflows/pr-dashboard.yml","SUMMARY_EN":"Rebuilds the pull-request dashboard every hour from the current state of the open pull requests.","TZ":"UTC"},"health":"ok","id":"github-actions/t27/pr-dashboard","inSpecCorpus":false,"messages":[],"moduleName":"cron_t27_pr_dashboard","name":{"en":"PR Dashboard"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/t27/actions/workflows/pr-dashboard.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"a16c8b2d502ca53955b1e51f3816d111ac056e5fe80904edbbc52fc269e45a92","specPath":"specs/crons/t27-pr-dashboard.t27","summary":{"en":"Rebuilds the pull-request dashboard every hour from the current state of the open pull requests.","ru":"Каждый час пересобирает панель pull request из текущего состояния открытых PR."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/agent-cron-cleanup.yml#L4","where":{"file":".github/workflows/agent-cron-cleanup.yml","host":"github-actions","line":4,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/agent-cron-cleanup","KIND":"cron","NAME":"Trinity Agent Cron Cleanup","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 */2 * * *","SERVICE":".github/workflows/agent-cron-cleanup.yml","SUMMARY_EN":"Finds agent deployments that got stuck and kills them, every two hours.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/agent-cron-cleanup","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_agent_cron_cleanup","name":{"en":"Trinity Agent Cron Cleanup"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/agent-cron-cleanup.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"f4db9e04ccb4ab2e3a1e501a15c2824109b7827d910ebf70874e5ab1ded9c6cf","specPath":"specs/crons/trinity-agent-cron-cleanup.t27","summary":{"en":"Finds agent deployments that got stuck and kills them, every two hours.","ru":"Каждые два часа находит застрявшие деплои агентов и убивает их."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/agent-queue-drain.yml#L4","where":{"file":".github/workflows/agent-queue-drain.yml","host":"github-actions","line":4,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/agent-queue-drain","KIND":"cron","NAME":"Trinity Agent Queue Drain","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"dispatches agent-spawn-pool.yml for a queued issue; no named skill invocation found in source","SCHEDULE":"*/5 * * * *","SERVICE":".github/workflows/agent-queue-drain.yml","SUMMARY_EN":"Looks for queued issues and free agent slots every five minutes and starts the next agent when there is room.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/agent-queue-drain","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_agent_queue_drain","name":{"en":"Trinity Agent Queue Drain"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/agent-queue-drain.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"e334d5dc5fc0c176e420a131ea712b9f8d2e1c03b56a36388a683081b5defda3","specPath":"specs/crons/trinity-agent-queue-drain.t27","summary":{"en":"Looks for queued issues and free agent slots every five minutes and starts the next agent when there is room.","ru":"Каждые пять минут ищет задачи в очереди и свободные слоты агентов и запускает следующего агента, когда есть место."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/brain-ci.yml#L11","where":{"file":".github/workflows/brain-ci.yml","host":"github-actions","line":11,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/brain-ci","KIND":"cron","NAME":"S³AI Brain CI — \"Functional MRI\" Gate","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 0 * * 0","SERVICE":".github/workflows/brain-ci.yml","SUMMARY_EN":"Runs the weekly brain stress test, the long version of the health gate that every commit already gets.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/brain-ci","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_brain_ci","name":{"en":"S³AI Brain CI — \"Functional MRI\" Gate"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/brain-ci.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"c8cd980319751e2b53501dc645f1beb6980bdbf77adbb98a158a12be441d957e","specPath":"specs/crons/trinity-brain-ci.t27","summary":{"en":"Runs the weekly brain stress test, the long version of the health gate that every commit already gets.","ru":"Еженедельный стресс-тест мозга — длинная версия гейта здоровья, который получает каждый коммит."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/codegen.yml#L10","where":{"file":".github/workflows/codegen.yml","host":"github-actions","line":10,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/codegen","KIND":"cron","NAME":"Codegen Validation","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 0 * * *","SERVICE":".github/workflows/codegen.yml","SUMMARY_EN":"Validates the VIBEE code generator once a day, independently of whether anyone pushed.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/codegen","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_codegen","name":{"en":"Codegen Validation"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/codegen.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"d71a2dd240cabe9e1e5d667259d245209ec4532632c30328971e84ec4cbc4bd6","specPath":"specs/crons/trinity-codegen.t27","summary":{"en":"Validates the VIBEE code generator once a day, independently of whether anyone pushed.","ru":"Раз в день проверяет генератор кода VIBEE независимо от того, пушил ли кто-нибудь."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/discover-callers.yml#L16","where":{"file":".github/workflows/discover-callers.yml","host":"github-actions","line":16,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/discover-callers","KIND":"cron","NAME":"Discover RTL check callers","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"41 5 * * *","SERVICE":".github/workflows/discover-callers.yml","SUMMARY_EN":"Reads GitHub for repositories that run the RTL check, so the gallery counts its own users instead of being hand-written.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/discover-callers","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_discover_callers","name":{"en":"Discover RTL check callers"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/discover-callers.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"1aa5ec92373f3a0a9df24da027b852896fc303ec4eb50e4ee780d9abdea5aced","specPath":"specs/crons/trinity-discover-callers.t27","summary":{"en":"Reads GitHub for repositories that run the RTL check, so the gallery counts its own users instead of being hand-written.","ru":"Читает GitHub в поисках репозиториев, запускающих RTL-проверку, чтобы галерея считала своих пользователей сама."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/pages-health-check.yml#L17","where":{"file":".github/workflows/pages-health-check.yml","host":"github-actions","line":17,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/pages-health-check","KIND":"cron","NAME":"Pages Health Check","NOTE":"An earlier version judged a bare status code, failed ninety-six times a day on a site that was up, and dispatched a redeploy each time.","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"*/15 * * * *","SERVICE":".github/workflows/pages-health-check.yml","SUMMARY_EN":"Fetches the published site every fifteen minutes, follows the redirect, and redeploys only when the evidence says the deploy is what broke.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/pages-health-check","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_pages_health_check","name":{"en":"Pages Health Check"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/pages-health-check.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"f8199eaa1c9707bff5ba89d553a644725b072fce2f7c4c5de7dfe4b6e9620ac1","specPath":"specs/crons/trinity-pages-health-check.t27","summary":{"en":"Fetches the published site every fifteen minutes, follows the redirect, and redeploys only when the evidence says the deploy is what broke.","ru":"Каждые пятнадцать минут запрашивает опубликованный сайт, следует редиректу и передеплоит только когда свидетельства говорят, что сломан именно деплой."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/signal-health-self.yml#L17","where":{"file":".github/workflows/signal-health-self.yml","host":"github-actions","line":17,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/signal-health-self","KIND":"cron","NAME":"Signal health (self)","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"23 4 * * *","SERVICE":".github/workflows/signal-health-self.yml","SUMMARY_EN":"Applies the signal-health check to the repository that sells it and writes the numbers into the website data.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/signal-health-self","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_signal_health_self","name":{"en":"Signal health (self)"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/signal-health-self.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"b5bd1483d24af17707d8fcf03dbb6f011bc8462773423ebe9b8ee4e506a76a4e","specPath":"specs/crons/trinity-signal-health-self.t27","summary":{"en":"Applies the signal-health check to the repository that sells it and writes the numbers into the website data.","ru":"Применяет проверку signal-health к репозиторию, который её продаёт, и записывает числа в данные сайта."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"github-actions","repoPrivate":false,"sourceUrl":"https://github.com/gHashTag/trinity/blob/cdc1d68d697fca070d9ad0f2d36aa4074de140f4/.github/workflows/site-live-gate.yml#L33","where":{"file":".github/workflows/site-live-gate.yml","host":"github-actions","line":33,"service":null}},"control":"github-actions-dispatch","discarded":0,"fields":{"CONTROL":"github-actions-dispatch","ENABLED":true,"HOST":"github-actions","ID":"github-actions/trinity/site-live-gate","KIND":"cron","NAME":"Site live gate","ON_FAILURE":"log","REPO":"trinity","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"17 6 * * *","SERVICE":".github/workflows/site-live-gate.yml","SUMMARY_EN":"Compares the content-hashed entry bundle served at the apex against a build of main, so that 'deployed' means a reader can actually see it.","TZ":"UTC"},"health":"ok","id":"github-actions/trinity/site-live-gate","inSpecCorpus":false,"messages":[],"moduleName":"cron_trinity_site_live_gate","name":{"en":"Site live gate"},"runNow":{"kind":"link","url":"https://github.com/gHashTag/trinity/actions/workflows/site-live-gate.yml","via":"github-actions"},"runs":[],"runsResolved":[],"sha256":"76ed9aa7465a9ba704074563535c41d2df1fcfa2ac76059bfe2eb5e3e52c5933","specPath":"specs/crons/trinity-site-live-gate.t27","summary":{"en":"Compares the content-hashed entry bundle served at the apex against a build of main, so that 'deployed' means a reader can actually see it.","ru":"Сравнивает бандл с контент-хешем на апексе со сборкой main, чтобы «задеплоено» означало, что читатель это видит."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"inngest","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/inngest_app/functions/training/checkStuckTrainings.ts#L53","where":{"file":"src/inngest_app/functions/training/checkStuckTrainings.ts","host":"railway:999-multibots-telegraf","line":53,"service":"inngest"}},"control":"inngest-dashboard","discarded":0,"fields":{"CONTROL":"inngest-dashboard","ENABLED":true,"HOST":"inngest","ID":"inngest/999-multibots-telegraf/check-stuck-trainings","KIND":"cron","NAME":"check-stuck-trainings","NOTE":"Records whose Replicate id is still a placeholder are skipped, because there is nothing on the provider to ask about.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"*/30 * * * *","SERVICE":"src/inngest_app/functions/training/checkStuckTrainings.ts","SUMMARY_EN":"Finds model trainings still marked pending or processing past the stuck threshold and reconciles them against Replicate.","TZ":"UTC"},"health":"ok","id":"inngest/999-multibots-telegraf/check-stuck-trainings","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_check_stuck_trainings","name":{"en":"check-stuck-trainings"},"runNow":{"kind":"link","url":"https://inngestinngest-production-6a21.up.railway.app","via":"inngest"},"runs":[],"runsResolved":[],"sha256":"fb82b6e4149b8be9916c35694faaeca85b555a6e3044cd3bc35f4d836ce60e49","specPath":"specs/crons/999-multibots-telegraf-check-stuck-trainings.t27","summary":{"en":"Finds model trainings still marked pending or processing past the stuck threshold and reconciles them against Replicate.","ru":"Находит обучения моделей, застрявшие в pending или processing дольше порога, и сверяет их с Replicate."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"inngest","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/inngest_app/functions/analytics/dailySalesAdvisor.ts#L56","where":{"file":"src/inngest_app/functions/analytics/dailySalesAdvisor.ts","host":"railway:999-multibots-telegraf","line":56,"service":"inngest"}},"control":"inngest-dashboard","discarded":0,"fields":{"CONTROL":"inngest-dashboard","ENABLED":true,"HOST":"inngest","ID":"inngest/999-multibots-telegraf/daily-sales-advisor","KIND":"cron","NAME":"daily-sales-advisor","NOTE":"One report per owner across all of their bots, assembled from the payments ledger rather than from a balance column.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 9 * * *","SERVICE":"src/inngest_app/functions/analytics/dailySalesAdvisor.ts","SUMMARY_EN":"Builds every bot owner a morning report of yesterday's revenue, the week's costs and which services their users actually bought.","TZ":"UTC"},"health":"ok","id":"inngest/999-multibots-telegraf/daily-sales-advisor","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_daily_sales_advisor","name":{"en":"daily-sales-advisor"},"runNow":{"kind":"link","url":"https://inngestinngest-production-6a21.up.railway.app","via":"inngest"},"runs":[],"runsResolved":[],"sha256":"0e2f8e5085ed1ea2b4b2f7732c9f9de00fcd19fda8d13e1f735865aa2cd32179","specPath":"specs/crons/999-multibots-telegraf-daily-sales-advisor.t27","summary":{"en":"Builds every bot owner a morning report of yesterday's revenue, the week's costs and which services their users actually bought.","ru":"Собирает каждому владельцу бота утренний отчёт: вчерашняя выручка, расходы недели и что реально покупали пользователи."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"inngest","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/inngest_app/functions/monitoring/criticalErrorMonitor.ts#L243","where":{"file":"src/inngest_app/functions/monitoring/criticalErrorMonitor.ts","host":"railway:999-multibots-telegraf","line":243,"service":"inngest"}},"control":"inngest-dashboard","discarded":0,"fields":{"CONTROL":"inngest-dashboard","ENABLED":true,"HOST":"inngest","ID":"inngest/999-multibots-telegraf/health-check","KIND":"cron","NAME":"health-check","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"*/30 * * * *","SERVICE":"src/inngest_app/functions/monitoring/criticalErrorMonitor.ts","SUMMARY_EN":"Probes the main API and the Inngest service every half hour and reports what answered.","TZ":"UTC"},"health":"ok","id":"inngest/999-multibots-telegraf/health-check","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_health_check","name":{"en":"health-check"},"runNow":{"kind":"link","url":"https://inngestinngest-production-6a21.up.railway.app","via":"inngest"},"runs":[],"runsResolved":[],"sha256":"b1bf93d0a864129e5c53a03b96cfe5d52069b5b65a7472dcef61a58cd6b87e1f","specPath":"specs/crons/999-multibots-telegraf-health-check.t27","summary":{"en":"Probes the main API and the Inngest service every half hour and reports what answered.","ru":"Каждые полчаса опрашивает основной API и сервис Inngest и сообщает, кто ответил."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"inngest","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/inngest_app/functions/monitoring/logMonitor.ts#L360","where":{"file":"src/inngest_app/functions/monitoring/logMonitor.ts","host":"railway:999-multibots-telegraf","line":360,"service":"inngest"}},"control":"inngest-dashboard","discarded":0,"fields":{"CONTROL":"inngest-dashboard","ENABLED":true,"HOST":"inngest","ID":"inngest/999-multibots-telegraf/log-monitor","KIND":"cron","NAME":"log-monitor","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 10 * * *","SERVICE":"src/inngest_app/functions/monitoring/logMonitor.ts","SUMMARY_EN":"Reads the application logs once a day, has a model summarise the errors and warnings, and sends the digest to Telegram.","TZ":"UTC"},"health":"ok","id":"inngest/999-multibots-telegraf/log-monitor","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_log_monitor","name":{"en":"log-monitor"},"runNow":{"kind":"link","url":"https://inngestinngest-production-6a21.up.railway.app","via":"inngest"},"runs":[],"runsResolved":[],"sha256":"3f1d2105328665203dc3850fea9f0245b7a6adbe5558e255756d168478812921","specPath":"specs/crons/999-multibots-telegraf-log-monitor.t27","summary":{"en":"Reads the application logs once a day, has a model summarise the errors and warnings, and sends the digest to Telegram.","ru":"Раз в день читает логи приложения, поручает модели свести ошибки и предупреждения и отправляет дайджест в Telegram."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"inngest","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/inngest_app/functions/webhookHealthGuard.ts#L218","where":{"file":"src/inngest_app/functions/webhookHealthGuard.ts","host":"railway:999-multibots-telegraf","line":218,"service":"inngest"}},"control":"inngest-dashboard","discarded":0,"fields":{"CONTROL":"inngest-dashboard","ENABLED":true,"HOST":"inngest","ID":"inngest/999-multibots-telegraf/periodic-webhook-health-check","KIND":"cron","NAME":"periodic-webhook-health-check","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"0 * * * *","SERVICE":"src/inngest_app/functions/webhookHealthGuard.ts","SUMMARY_EN":"Tests both Telegram webhook URLs every hour and sends the admin a critical alarm when neither of them answers.","TZ":"UTC"},"health":"ok","id":"inngest/999-multibots-telegraf/periodic-webhook-health-check","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_periodic_webhook_health_check","name":{"en":"periodic-webhook-health-check"},"runNow":{"kind":"link","url":"https://inngestinngest-production-6a21.up.railway.app","via":"inngest"},"runs":[],"runsResolved":[],"sha256":"a3b21a33a8a8022b527e3f46362fb6df63fb6f2e5ee642a439d88862dd4722f3","specPath":"specs/crons/999-multibots-telegraf-periodic-webhook-health-check.t27","summary":{"en":"Tests both Telegram webhook URLs every hour and sends the admin a critical alarm when neither of them answers.","ru":"Каждый час проверяет оба URL вебхука Telegram и шлёт админу критическую тревогу, когда не отвечает ни один."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"inngest","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/inngest_app/functions/analytics/skillDetector.ts#L32","where":{"file":"src/inngest_app/functions/analytics/skillDetector.ts","host":"railway:999-multibots-telegraf","line":32,"service":"inngest"}},"control":"inngest-dashboard","discarded":0,"fields":{"CONTROL":"inngest-dashboard","ENABLED":true,"HOST":"inngest","ID":"inngest/999-multibots-telegraf/skill-detector","KIND":"cron","NAME":"skill-detector","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"the \"skills\" it saves are bot prompt patterns, not Claude Code skills; no skill invocation found in source","SCHEDULE":"0 10 * * *","SERVICE":"src/inngest_app/functions/analytics/skillDetector.ts","SUMMARY_EN":"Mines successful generations for repeated model and prompt patterns once a day and saves the new ones as reusable skills.","TZ":"UTC"},"health":"ok","id":"inngest/999-multibots-telegraf/skill-detector","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_skill_detector","name":{"en":"skill-detector"},"runNow":{"kind":"link","url":"https://inngestinngest-production-6a21.up.railway.app","via":"inngest"},"runs":[],"runsResolved":[],"sha256":"d228015ce607198f53341b0b9dfc55a2cd0616e69487598d0cbabc34cc6892cf","specPath":"specs/crons/999-multibots-telegraf-skill-detector.t27","summary":{"en":"Mines successful generations for repeated model and prompt patterns once a day and saves the new ones as reusable skills.","ru":"Раз в день ищет в успешных генерациях повторяющиеся паттерны моделей и промптов и сохраняет новые как переиспользуемые навыки бота."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","kind":"railway-cron","repoPrivate":true,"sourceUrl":null,"where":{"file":null,"host":"railway:jobsearch-cron","line":null,"service":"jobsearch-cron"}},"control":"railway-dashboard","discarded":0,"fields":{"CONTROL":"railway-dashboard","ENABLED":true,"HOST":"railway-cron","ID":"railway-cron/999/jobsearch-cron","KIND":"cron","NAME":"jobsearch-cron","NOTE":"The schedule is configured in the Railway dashboard for project 999 and is not readable from the checkout; the probe below is what this catalog can verify.","ON_FAILURE":"unknown","REPO":"railway","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SCHEDULE":"","SCHEDULE_NOTE":"configured in the Railway dashboard (project 999); not readable from the checkout","SERVICE":"jobsearch-cron","SUMMARY_EN":"The scheduled job-search service of the Railway project, reachable at cron.t27.ai.","TZ":"UTC"},"health":"ok","id":"railway-cron/999/jobsearch-cron","inSpecCorpus":false,"messages":[],"moduleName":"cron_railway_jobsearch_cron","name":{"en":"jobsearch-cron"},"runNow":{"kind":"link","url":"https://railway.com/project/564d9ebd-7aa8-44fe-93ec-e0b03c87158d/service/3196472e-b885-43e0-b553-0644664e4496","via":"railway"},"runs":[],"runsResolved":[],"sha256":"8729edafdc8239d187c0f2b7d98238438d40ec439b39087e1654bb683366ec3a","specPath":"specs/crons/railway-jobsearch-cron.t27","summary":{"en":"The scheduled job-search service of the Railway project, reachable at cron.t27.ai.","ru":"Сервис поиска вакансий по расписанию в проекте Railway, доступен на cron.t27.ai."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/apps/vibee-editor/render/scripts/agent-autopilot.ts#L1118","where":{"file":"apps/vibee-editor/render/scripts/agent-autopilot.ts","host":"railway:vibee-render","line":1118,"service":"render"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/agent-autopilot-L1118","INTERVAL_MS":1800000,"KIND":"cron","NAME":"agent-autopilot-L1118","NOTE":"Daemon mode only, under --loop or AUTOPILOT_LOOP=1; AUTOPILOT_INTERVAL_MS overrides the thirty-minute period, with a one-minute floor.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"apps/vibee-editor/render/scripts/agent-autopilot.ts:1118","SUMMARY_EN":"Runs one autopilot cycle -- the generation pass and then the channel tick -- and skips the tick entirely when the previous cycle is still running.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/agent-autopilot-L1118","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_agent_autopilot_L1118","name":{"en":"agent-autopilot-L1118"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"39c25a3a6c40cff4e3ad9aa098e7492803a9cc78a935e74966c4f95939c85228","specPath":"specs/crons/999-multibots-telegraf-agent-autopilot-L1118.t27","summary":{"en":"Runs one autopilot cycle -- the generation pass and then the channel tick -- and skips the tick entirely when the previous cycle is still running.","ru":"Один цикл автопилота — проход генерации и затем тик канала; тик пропускается целиком, если предыдущий цикл ещё идёт."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/services/bot-owner-billing.ts#L474","where":{"file":"src/services/bot-owner-billing.ts","host":"railway:999-multibots-telegraf","line":474,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/bot-owner-billing-L474","INTERVAL_MS":86400000,"KIND":"cron","NAME":"bot-owner-billing-L474","NOTE":"The first run after a restart is skipped on purpose, and an owner whose payments cannot be read is left alone rather than accused.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/services/bot-owner-billing.ts:474","SUMMARY_EN":"Recomputes every bot owner's debt from the payments ledger, warns them at the soft, warning and critical thresholds, and disables a bot that stays critical for three days.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/bot-owner-billing-L474","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_bot_owner_billing_L474","name":{"en":"bot-owner-billing-L474"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"b2fb3c2d66630369c42ae4aba3f80538661c5591de99f1b088c569185be83197","specPath":"specs/crons/999-multibots-telegraf-bot-owner-billing-L474.t27","summary":{"en":"Recomputes every bot owner's debt from the payments ledger, warns them at the soft, warning and critical thresholds, and disables a bot that stays critical for three days.","ru":"Пересчитывает долг каждого владельца бота по журналу платежей, предупреждает на мягком, тревожном и критическом порогах и отключает бота, три дня остающегося критическим."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/services/crmProactive.ts#L375","where":{"file":"src/services/crmProactive.ts","host":"railway:999-multibots-telegraf","line":375,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/crmProactive-L375","INTERVAL_MS":1800000,"KIND":"cron","NAME":"crmProactive-L375","NOTE":"The period is CRM_PROACTIVE_MINUTES from src/index.ts, thirty minutes by default; the first sweep waits two minutes after start.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/services/crmProactive.ts:375","SUMMARY_EN":"Runs one proactive seller turn for the owner: walks the CRM queue and puts the next draft card in front of them without being asked.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/crmProactive-L375","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_crmProactive_L375","name":{"en":"crmProactive-L375"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"68cef02f70476660ff1d1df04010d0a3d6cbe249c35da94d97f4c2a641e57d5e","specPath":"specs/crons/999-multibots-telegraf-crmProactive-L375.t27","summary":{"en":"Runs one proactive seller turn for the owner: walks the CRM queue and puts the next draft card in front of them without being asked.","ru":"Один проактивный ход продавца для владельца: обходит очередь CRM и кладёт перед ним следующую карточку-черновик без просьбы."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/apps/vibee-editor/render/generate-jobs.ts#L164","where":{"file":"apps/vibee-editor/render/generate-jobs.ts","host":"railway:vibee-render","line":164,"service":"render"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/generate-jobs-L164","INTERVAL_MS":300000,"KIND":"cron","NAME":"generate-jobs-L164","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"apps/vibee-editor/render/generate-jobs.ts:164","SUMMARY_EN":"Sweeps finished generation jobs out of memory an hour after they end, once a caller who lost the connection has had time to collect the result.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/generate-jobs-L164","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_generate_jobs_L164","name":{"en":"generate-jobs-L164"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"c72b9a7b5fa9804c513eb5c2ebff0868bbf2a7226347826e64bbd34f6bef6475","specPath":"specs/crons/999-multibots-telegraf-generate-jobs-L164.t27","summary":{"en":"Sweeps finished generation jobs out of memory an hour after they end, once a caller who lost the connection has had time to collect the result.","ru":"Через час после завершения выметает из памяти законченные задания генерации, когда потерявший соединение клиент успел забрать результат."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/handlers/notificationHandler.ts#L318","where":{"file":"src/handlers/notificationHandler.ts","host":"railway:999-multibots-telegraf","line":318,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/notificationHandler-L318","INTERVAL_MS":60000,"KIND":"cron","NAME":"notificationHandler-L318","NOTE":"Wrapped in an exclusive tick, so two bot processes cannot deliver the same notification twice.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/handlers/notificationHandler.ts:318","SUMMARY_EN":"Drains the notification queue and delivers the Telegram messages waiting in it.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/notificationHandler-L318","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_notificationHandler_L318","name":{"en":"notificationHandler-L318"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"9114a4bdfb275175f60c5a111bdc07bbfcbb62c55a0871176b8f93e3436bdd9f","specPath":"specs/crons/999-multibots-telegraf-notificationHandler-L318.t27","summary":{"en":"Drains the notification queue and delivers the Telegram messages waiting in it.","ru":"Разгружает очередь уведомлений и доставляет ждущие в ней сообщения Telegram."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/handlers/notificationHandler.ts#L324","where":{"file":"src/handlers/notificationHandler.ts","host":"railway:999-multibots-telegraf","line":324,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/notificationHandler-L324","INTERVAL_MS":3600000,"KIND":"cron","NAME":"notificationHandler-L324","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/handlers/notificationHandler.ts:324","SUMMARY_EN":"Deletes notification messages old enough that nobody is going to read them.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/notificationHandler-L324","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_notificationHandler_L324","name":{"en":"notificationHandler-L324"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"44d2e3c8843519a512128399557c8d7af29ca2491f769f91c845dc44e0b63f9a","specPath":"specs/crons/999-multibots-telegraf-notificationHandler-L324.t27","summary":{"en":"Deletes notification messages old enough that nobody is going to read them.","ru":"Удаляет уведомления, настолько старые, что их уже никто не прочитает."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/utils/production-monitor.ts#L80","where":{"file":"src/utils/production-monitor.ts","host":"railway:999-multibots-telegraf","line":80,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":false,"HOST":"timer","ID":"timer/999-multibots-telegraf/production-monitor-L80","INTERVAL_MS":300000,"KIND":"cron","NAME":"production-monitor-L80","NOTE":"Started only by running this module from the command line with the `monitor` argument; the bot's own entry point never starts it.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/utils/production-monitor.ts:80","SUMMARY_EN":"Re-runs the container, nginx, port, domain, webhook and system-resource checks, and applies the fixes that are enabled.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/production-monitor-L80","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_production_monitor_L80","name":{"en":"production-monitor-L80"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"880d0b6c01b2b1e4cc9bc92c11ef300b8335bdf8ca842308bca7d3fd7e979254","specPath":"specs/crons/999-multibots-telegraf-production-monitor-L80.t27","summary":{"en":"Re-runs the container, nginx, port, domain, webhook and system-resource checks, and applies the fixes that are enabled.","ru":"Повторяет проверки контейнера, nginx, порта, домена, вебхука и ресурсов системы и применяет включённые исправления."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/services/provider-health-monitor.ts#L344","where":{"file":"src/services/provider-health-monitor.ts","host":"railway:999-multibots-telegraf","line":344,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/provider-health-monitor-L344","INTERVAL_MS":300000,"KIND":"cron","NAME":"provider-health-monitor-L344","NOTE":"Providers listed in HEALTH_SKIP_PROVIDERS are deliberately not watched and do not raise the alarm.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/services/provider-health-monitor.ts:344","SUMMARY_EN":"Checks every AI provider that is not parked and records whether it is currently answering.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/provider-health-monitor-L344","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_provider_health_monitor_L344","name":{"en":"provider-health-monitor-L344"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"0044630e40dc2673a2d1210e848f1a3cd41cc2b21b541bec98e305f0192a2de7","specPath":"specs/crons/999-multibots-telegraf-provider-health-monitor-L344.t27","summary":{"en":"Checks every AI provider that is not parked and records whether it is currently answering.","ru":"Проверяет каждого не отключённого AI-провайдера и записывает, отвечает ли он сейчас."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/apps/vibee-editor/render/render-server.ts#L10845","where":{"file":"apps/vibee-editor/render/render-server.ts","host":"railway:vibee-render","line":10845,"service":"render"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/render-server-L10845","INTERVAL_MS":30000,"KIND":"cron","NAME":"render-server-L10845","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"apps/vibee-editor/render/render-server.ts:10845","SUMMARY_EN":"Pings every websocket peer and drops the ones that did not answer the previous round.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/render-server-L10845","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_render_server_L10845","name":{"en":"render-server-L10845"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"bfcd81ea5f3b3040dccce76d91dd699347e49c2df843529218ee138560e2d924","specPath":"specs/crons/999-multibots-telegraf-render-server-L10845.t27","summary":{"en":"Pings every websocket peer and drops the ones that did not answer the previous round.","ru":"Пингует каждого websocket-пира и отключает тех, кто не ответил в прошлом раунде."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/apps/vibee-editor/render/render-server.ts#L2301","where":{"file":"apps/vibee-editor/render/render-server.ts","host":"railway:vibee-render","line":2301,"service":"render"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/render-server-L2301","INTERVAL_MS":60000,"KIND":"cron","NAME":"render-server-L2301","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"apps/vibee-editor/render/render-server.ts:2301","SUMMARY_EN":"Removes render jobs older than an hour from the in-memory job map.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/render-server-L2301","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_render_server_L2301","name":{"en":"render-server-L2301"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"37e3dca7f4836af5ecfde5eaaa2aa993ac19087ac902adbb4015c04527a2af62","specPath":"specs/crons/999-multibots-telegraf-render-server-L2301.t27","summary":{"en":"Removes render jobs older than an hour from the in-memory job map.","ru":"Удаляет из карты в памяти задания рендера старше часа."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/apps/vibee-editor/render/render-server.ts#L2325","where":{"file":"apps/vibee-editor/render/render-server.ts","host":"railway:vibee-render","line":2325,"service":"render"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/render-server-L2325","INTERVAL_MS":600000,"KIND":"cron","NAME":"render-server-L2325","NOTE":"Checks often and writes rarely: the cursor lives in the database, because this Railway project has no volume and a deploy wipes local state.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"apps/vibee-editor/render/render-server.ts:2325","SUMMARY_EN":"Reads the hive journal and delivers the queen's report to the keepers, passing alarms straight through and batching ordinary events.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/render-server-L2325","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_render_server_L2325","name":{"en":"render-server-L2325"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"11b24d4332b7e356f77b233a20d578c3ec31da71ff34be37d16d29d7d4471d5b","specPath":"specs/crons/999-multibots-telegraf-render-server-L2325.t27","summary":{"en":"Reads the hive journal and delivers the queen's report to the keepers, passing alarms straight through and batching ordinary events.","ru":"Читает журнал улья и доставляет отчёт королевы хранителям: тревоги — сразу, обычные события — пакетом."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/apps/vibee-editor/render/render-server.ts#L919","where":{"file":"apps/vibee-editor/render/render-server.ts","host":"railway:vibee-render","line":919,"service":"render"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/render-server-L919","INTERVAL_MS":5000,"KIND":"cron","NAME":"render-server-L919","NOTE":"Only starts when a session signing key is configured; a deployed render server refuses to boot without one.","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"apps/vibee-editor/render/render-server.ts:919","SUMMARY_EN":"Re-reads the session revocation list from Postgres so a revoked browser session stops being accepted.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/render-server-L919","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_render_server_L919","name":{"en":"render-server-L919"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"f86d77af65dfb35cc4adf4202c8f06577e82d7856edbcac656ef735eeba19a52","specPath":"specs/crons/999-multibots-telegraf-render-server-L919.t27","summary":{"en":"Re-reads the session revocation list from Postgres so a revoked browser session stops being accepted.","ru":"Перечитывает из Postgres список отозванных сессий, чтобы отозванная сессия браузера перестала приниматься."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/modules/videoGenerator/taskCache.ts#L27","where":{"file":"src/modules/videoGenerator/taskCache.ts","host":"railway:999-multibots-telegraf","line":27,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/taskCache-L27","INTERVAL_MS":300000,"KIND":"cron","NAME":"taskCache-L27","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/modules/videoGenerator/taskCache.ts:27","SUMMARY_EN":"Drops video-generation cache entries past their fifteen-minute lifetime so a finished task cannot block the next request.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/taskCache-L27","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_taskCache_L27","name":{"en":"taskCache-L27"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"ca7f9a9edce47fe23ef59515d87ba7f391b878a9c5115b43e9c32b4a70fe90db","specPath":"specs/crons/999-multibots-telegraf-taskCache-L27.t27","summary":{"en":"Drops video-generation cache entries past their fifteen-minute lifetime so a finished task cannot block the next request.","ru":"Сбрасывает записи кэша видеогенерации старше пятнадцати минут, чтобы завершённая задача не блокировала следующий запрос."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","kind":"timer","repoPrivate":true,"sourceUrl":"https://github.com/gHashTag/999-multibots-telegraf/blob/3f01d449e5a04d37f6ff4d76d5572f499c201789/src/services/video-task-store.ts#L156","where":{"file":"src/services/video-task-store.ts","host":"railway:999-multibots-telegraf","line":156,"service":"bot"}},"control":"code-only","discarded":0,"fields":{"CONTROL":"code-only","ENABLED":true,"HOST":"timer","ID":"timer/999-multibots-telegraf/video-task-store-L156","INTERVAL_MS":600000,"KIND":"cron","NAME":"video-task-store-L156","ON_FAILURE":"log","REPO":"999-multibots-telegraf","RUNS":[],"RUNS_NOTE":"no skill invocation found in source","SERVICE":"src/services/video-task-store.ts:156","SUMMARY_EN":"Forgets video tasks older than an hour and rewrites the store to disk when anything was removed.","TZ":"UTC"},"health":"ok","id":"timer/999-multibots-telegraf/video-task-store-L156","inSpecCorpus":false,"messages":[],"moduleName":"cron_999_multibots_telegraf_video_task_store_L156","name":{"en":"video-task-store-L156"},"runNow":{"kind":"disabled","reason":"timer"},"runs":[],"runsResolved":[],"sha256":"dc4e3d6c76cac57a36074da8a9d6aac8df1f1c55bb68a593c2cadf94c94351b1","specPath":"specs/crons/999-multibots-telegraf-video-task-store-L156.t27","summary":{"en":"Forgets video tasks older than an hour and rewrites the store to disk when anything was removed.","ru":"Забывает видеозадачи старше часа и переписывает хранилище на диск, если что-то было удалено."},"typecheckOk":true,"witness":"spec+code"}],"generatedAt":"2026-09-09T12:45:33.186Z","i18n":[{"bundle":"apps/website/i18n/agents.ru.json","coverage":{"n":33,"total":33},"enabled":true,"fields":["SUMMARY","NAME"],"locale":"ru","missing":[],"scope":["specs/skills","specs/crons"],"sha256":"9c8158d024c8ad711a31465040d57da20881650d816641f52279ea452b7bcd44","spec":"specs/i18n/agents-ru.t27"}],"version":1} diff --git a/apps/website/public/skills/spec-skills.json b/apps/website/public/skills/spec-skills.json new file mode 100644 index 0000000000..ef278e53ab --- /dev/null +++ b/apps/website/public/skills/spec-skills.json @@ -0,0 +1 @@ +{"codeOnly":[],"compilerWasmSha256":"4d9c0447b5ca288790ab03d3dffc2dda629ed47af9d6731a105fc923aea0aee4","contentSha256":"49b277eefcb35d7c440065e59a2f147b05bda0011b20710f7c1dbb515aea667f","counts":{"codeOnly":0,"runBy":0,"specOnly":0,"specPlusCode":26,"specs":26,"typecheckOk":26},"generatedAt":"2026-09-09T12:45:33.186Z","i18n":[{"bundle":"apps/website/i18n/agents.ru.json","coverage":{"n":26,"total":26},"enabled":true,"fields":["SUMMARY","NAME"],"locale":"ru","missing":[],"scope":["specs/skills","specs/crons"],"sha256":"9c8158d024c8ad711a31465040d57da20881650d816641f52279ea452b7bcd44","spec":"specs/i18n/agents-ru.t27"}],"skills":[{"code":{"health":"warn","link":"unbound","path":"t27/measure-corpus/SKILL.md","sha256":"b8fb7503f660a81b2fee57cde8998b18bbf6c6f334ca2ee69424c3ccf99f3211"},"discarded":0,"fields":{"COMMAND":"","ENABLED":true,"ID":"t27/measure-corpus","KIND":"skill","NAME":"MEASURE THE CORPUS","REPO":"t27","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Pick the right instrument for a claim about the t27 spec corpus, and avoid the specific traps that have produced wrong published numbers. Use before quoting any figure about specs, errors, or gates.","TAGS":["corpus","measurement","honesty"],"TIMEOUT_MIN":30},"health":"ok","id":"t27/measure-corpus","inSpecCorpus":false,"messages":[],"moduleName":"skill_t27_measure_corpus","name":{"en":"MEASURE THE CORPUS"},"runBy":[],"sha256":"740d067747a4de74acb2be6ba7072f11ef58da37700addab7e7a172cc0c397aa","specPath":"specs/skills/t27-measure-corpus.t27","summary":{"en":"Pick the right instrument for a claim about the t27 spec corpus, and avoid the specific traps that have produced wrong published numbers. Use before quoting any figure about specs, errors, or gates.","ru":"Выбор правильного инструмента для любого утверждения о корпусе спек t27 и список ловушек, которые уже давали неверные опубликованные числа. Использовать перед тем, как цитировать любое число о корпусе."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"t27/phi-loop/SKILL.md","sha256":"d8af7f6a9cbc39ed4557291528f36748e07292ef752f5159e2827f59b2dd7bc6"},"discarded":0,"fields":{"COMMAND":"","ENABLED":true,"ID":"t27/phi-loop","KIND":"skill","NAME":"PHI LOOP","REPO":"t27","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Execute 9-phase spec-first development workflow (Issue → Spec → TDD → Code → Gen → Seal → Verify → Land → Learn)","TAGS":["workflow","spec-first","tdd"],"TIMEOUT_MIN":30},"health":"ok","id":"t27/phi-loop","inSpecCorpus":false,"messages":[],"moduleName":"skill_t27_phi_loop","name":{"en":"PHI LOOP"},"runBy":[],"sha256":"19dba96ffef309c75a31bc54d282f25026afa8beeb3a6f97600946700a5f74db","specPath":"specs/skills/t27-phi-loop.t27","summary":{"en":"Execute 9-phase spec-first development workflow (Issue → Spec → TDD → Code → Gen → Seal → Verify → Land → Learn)","ru":"Девятифазный цикл spec-first разработки: Issue → Spec → TDD → Code → Gen → Seal → Verify → Land → Learn."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","link":"bound","path":"t27/tri/skill.md","sha256":"8fb4d4796c2dc7adab9670a65709ce0451633e6f1f076d5e490270596636042a"},"discarded":0,"fields":{"COMMAND":"/tri","ENABLED":true,"ID":"t27/tri","KIND":"skill","NAME":"tri","REPO":"t27","SOURCE":"skill.md","SPECS":["compiler/skill/registry.t27"],"SUMMARY_EN":"This skill should be used when user asks to \"tri skill begin\", \"PHI LOOP\", \"edit .t27 spec\", \"seal hash\", \"tri gen\", \"tri test\", \"tri verdict\", \"tri experience save\", \"tri notebook query\", \"tri notebook wrapup\", or any task requiring spe...","TAGS":["tri","workflow","seal"],"TIMEOUT_MIN":30},"health":"ok","id":"t27/tri","inSpecCorpus":false,"messages":[],"moduleName":"skill_t27_tri","name":{"en":"tri"},"runBy":[],"sha256":"99f03be9bbefc5f7c3b0637717e69650f11b0bf8644563b4e03bd24d12a66c89","specPath":"specs/skills/t27-tri.t27","summary":{"en":"This skill should be used when user asks to \"tri skill begin\", \"PHI LOOP\", \"edit .t27 spec\", \"seal hash\", \"tri gen\", \"tri test\", \"tri verdict\", \"tri experience save\", \"tri notebook query\", \"tri notebook wrapup\", or any task requiring spe...","ru":"Команды tri для работы со скилом: begin, правка .t27-спеки, seal hash, gen, test, verdict, experience. Реестр скилов описан в compiler/skill/registry.t27."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","link":"bound","path":"t27/tri-pipeline/SKILL.md","sha256":"b4bfc290b86a4a1d4513cf9ace0a43526ca8fe98e0d97ff1eb59ea7997aa7427"},"discarded":0,"fields":{"COMMAND":"","ENABLED":true,"ID":"t27/tri-pipeline","KIND":"skill","NAME":"TRI Pipeline","REPO":"t27","SOURCE":"SKILL.md","SPECS":["specs/pipeline/e2e_test.t27"],"SUMMARY_EN":"Execute tri commands (gen, test, verify, seal, verdict) for spec-first development","TAGS":["tri","pipeline","verify"],"TIMEOUT_MIN":30},"health":"ok","id":"t27/tri-pipeline","inSpecCorpus":false,"messages":[],"moduleName":"skill_t27_tri_pipeline","name":{"en":"TRI Pipeline"},"runBy":[],"sha256":"716635d0a782f18bfd6d112d88aeacc9e1961b81a85aec42b91a970fd55ce662","specPath":"specs/skills/t27-tri-pipeline.t27","summary":{"en":"Execute tri commands (gen, test, verify, seal, verdict) for spec-first development","ru":"Выполнение команд tri (gen, test, verify, seal, verdict) для spec-first разработки."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"t27/wave-audit/SKILL.md","sha256":"4cd0dd22105ae7fe86091a69ec297df729661181f147c0b59f8e8e94a90e9100"},"discarded":0,"fields":{"COMMAND":"","ENABLED":true,"ID":"t27/wave-audit","KIND":"skill","NAME":"Wave Audit — Comprehensive Project Analysis","REPO":"t27","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Full-spectrum audit of t27 project: GitHub issues analysis, weakness identification, SOTA research review, decomposed plan, and implementation of critical fixes.","TAGS":["audit","issues","plan"],"TIMEOUT_MIN":30},"health":"ok","id":"t27/wave-audit","inSpecCorpus":false,"messages":[],"moduleName":"skill_t27_wave_audit","name":{"en":"Wave Audit — Comprehensive Project Analysis"},"runBy":[],"sha256":"50c024be3d15fb0d68356ef3e918d71a1ddd3a6b315478927a996a5760efc1db","specPath":"specs/skills/t27-wave-audit.t27","summary":{"en":"Full-spectrum audit of t27 project: GitHub issues analysis, weakness identification, SOTA research review, decomposed plan, and implementation of critical fixes.","ru":"Полный аудит проекта t27: разбор GitHub issues, поиск слабых мест, обзор SOTA, декомпозированный план и реализация критичных исправлений."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"ok","link":"bound","path":"t27/wrap-up/skill.md","sha256":"a4a9b4fd9d022c212add3479cde725f6b9bd56bc31d2d600ba771cbd009329f9"},"discarded":0,"fields":{"COMMAND":"/wrap-up","ENABLED":true,"ID":"t27/wrap-up","KIND":"skill","NAME":"wrap-up","REPO":"t27","SOURCE":"skill.md","SPECS":["specs/automation/wrapup-auto.t27"],"SUMMARY_EN":"Format and upload session wrap-up to NotebookLM for persistent semantic memory","TAGS":["memory","notebooklm","session"],"TIMEOUT_MIN":30},"health":"ok","id":"t27/wrap-up","inSpecCorpus":false,"messages":[],"moduleName":"skill_t27_wrap_up","name":{"en":"wrap-up"},"runBy":[],"sha256":"5f6e7ed53ca0f4ca52320a9c1ebdbbf079e371836de109c94dc440847fa0f2dc","specPath":"specs/skills/t27-wrap-up.t27","summary":{"en":"Format and upload session wrap-up to NotebookLM for persistent semantic memory","ru":"Оформление и загрузка итогов сессии в NotebookLM как постоянной семантической памяти."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/blog-post/SKILL.md","sha256":"2cb755fd4a60a76d568a0742aeda34da60a2441774139646e0ebf4bd2a185493"},"discarded":0,"fields":{"COMMAND":"/blog-post","ENABLED":true,"ID":"trinity/blog-post","KIND":"skill","NAME":"blog-post","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Write and publish a post to the t27.ai blog. Covers where the blog actually lives (not where it looks like it lives), the Post schema with its required receipts and openQuestions, the honesty rules the data file itself imposes, and the b...","TAGS":["blog","publish","website"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/blog-post","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_blog_post","name":{"en":"blog-post"},"runBy":[],"sha256":"028f49b8d26ed69b4f8db6991666a5f51b9a2c2737291ceb3258bfd11ff7c3e7","specPath":"specs/skills/trinity-blog-post.t27","summary":{"en":"Write and publish a post to the t27.ai blog. Covers where the blog actually lives (not where it looks like it lives), the Post schema with its required receipts and openQuestions, the honesty rules the data file itself imposes, and the b...","ru":"Написать и опубликовать пост в блог t27.ai: где блог реально живёт, схема Post с обязательными свидетельствами, обложка и проверки."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/board-sync/SKILL.md","sha256":"f4bfa63737cf7a2078ae6e42f68943384214159243243ae8ec971ddeaac1825e"},"discarded":0,"fields":{"COMMAND":"/board [sync|audit|fix]","ENABLED":true,"ID":"trinity/board-sync","KIND":"skill","NAME":"board","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"GitHub Project Board auto-sync — fill empty fields, sync labels with board status, audit field completeness. Keeps board as realtime source of truth.","TAGS":["github","board","labels"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/board-sync","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_board_sync","name":{"en":"board"},"runBy":[],"sha256":"45ece51f52ec529802e387903a06b3fc58f3dcc088823785511c924c57567c32","specPath":"specs/skills/trinity-board-sync.t27","summary":{"en":"GitHub Project Board auto-sync — fill empty fields, sync labels with board status, audit field completeness. Keeps board as realtime source of truth.","ru":"Автосинхронизация GitHub Project Board: заполнить пустые поля, согласовать метки со статусом доски, проверить полноту полей."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/cloud/SKILL.md","sha256":"53aa338dddc4e6b8a0fd82f6d3b8dc9ac9b245b3457559c3c44f325ede0130e6"},"discarded":0,"fields":{"COMMAND":"/cloud [status|agents|events|full]","ENABLED":true,"ID":"trinity/cloud","KIND":"skill","NAME":"cloud","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Show Trinity Cloud Dev dashboard — containers, training farm, issues, PRs, costs. Use when checking agent swarm status, cloud containers, or CI/CD pipeline.","TAGS":["dashboard","railway","agents"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/cloud","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_cloud","name":{"en":"cloud"},"runBy":[],"sha256":"7eb9c81b10e1dd99998207ce71b792342f1f8bdd3852b974f27eb20418ca480e","specPath":"specs/skills/trinity-cloud.t27","summary":{"en":"Show Trinity Cloud Dev dashboard — containers, training farm, issues, PRs, costs. Use when checking agent swarm status, cloud containers, or CI/CD pipeline.","ru":"Панель Trinity Cloud Dev: контейнеры, тренировочная ферма, issues, PR, расходы. Для проверки состояния роя агентов и CI/CD."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/doctor/SKILL.md","sha256":"68efdf79301066d5e64e72babc91ddb867899786186fa360e6775b8365059815"},"discarded":0,"fields":{"COMMAND":"/doctor [quick|full|scan|junk|docs|dupes] [lang:ru|en]","ENABLED":true,"ID":"trinity/doctor","KIND":"skill","NAME":"doctor","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"HEALER — diagnose, heal, commit dirty files, monitor junk & docs & duplicates, report honestly. Every loop = action + proof.","TAGS":["health","repo","report"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/doctor","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_doctor","name":{"en":"doctor"},"runBy":[],"sha256":"4e74c213b43b2b2c253dda134a93795b3a6e627a4711d5fc8dffb3067c1cb0b0","specPath":"specs/skills/trinity-doctor.t27","summary":{"en":"HEALER — diagnose, heal, commit dirty files, monitor junk & docs & duplicates, report honestly. Every loop = action + proof.","ru":"ЛЕКАРЬ: диагностика, лечение, коммит грязных файлов, надзор за мусором, документацией и дубликатами, честный отчёт. Каждый цикл = действие + доказательство."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/farm-garden/SKILL.md","sha256":"6721489885755a64bfdd07f0407b0ddad366c713cb2723098c0fc63b4e196bb4"},"discarded":0,"fields":{"COMMAND":"","ENABLED":true,"ID":"trinity/farm-garden","KIND":"skill","NAME":"Garden — Quantum Gardener for Trinity Farm","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Autonomous farm population manager. Monitors, evolves, and recycles HSLM training workers.","TAGS":["farm","hslm","workers"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/farm-garden","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_farm_garden","name":{"en":"Garden — Quantum Gardener for Trinity Farm"},"runBy":[],"sha256":"4afded3071ac34a51d06749ea6983dfd0238d1dcb78856ae19b836122019c080","specPath":"specs/skills/trinity-farm-garden.t27","summary":{"en":"Autonomous farm population manager. Monitors, evolves, and recycles HSLM training workers.","ru":"Автономный управляющий популяцией фермы: наблюдает, эволюционирует и перерабатывает рабочих обучения HSLM."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/fpga-synth/SKILL.md","sha256":"de3915e66cdab0f3207f53f6ea22a85b258c8a998b614231eebf7afab2ca9ec3"},"discarded":0,"fields":{"COMMAND":"/fpga-synth ","ENABLED":true,"ID":"trinity/fpga-synth","KIND":"skill","NAME":"fpga-synth","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Run FPGA synthesis pipeline from .tri spec to bitstream. Use when working with Verilog, FPGA, or hardware synthesis.","TAGS":["fpga","verilog","synthesis"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/fpga-synth","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_fpga_synth","name":{"en":"fpga-synth"},"runBy":[],"sha256":"2d3563e302746a958794670cd14404a7c4e6854dcf546468dd5f91907ff299e4","specPath":"specs/skills/trinity-fpga-synth.t27","summary":{"en":"Run FPGA synthesis pipeline from .tri spec to bitstream. Use when working with Verilog, FPGA, or hardware synthesis.","ru":"Конвейер синтеза FPGA от .tri-спеки до битстрима. Для работы с Verilog, FPGA и аппаратным синтезом."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/god-mode/SKILL.md","sha256":"0c48e328ae4cf1cbaffef270e7dbe16fac59a7fba845547179bcfe078f65ee7a"},"discarded":0,"fields":{"COMMAND":"/god-mode [status|agents|tasks|violations]","ENABLED":true,"ID":"trinity/god-mode","KIND":"skill","NAME":"god-mode","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"GOD MODE — Agent monitoring dashboard. Shows swarm status, task queue, pipeline compliance, circuit breakers, git activity, and rule violations.","TAGS":["dashboard","agents","monitoring"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/god-mode","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_god_mode","name":{"en":"god-mode"},"runBy":[],"sha256":"577f3aa5eb4280c6be165fd7d0458fb849988246800eef63c77b3c774091706f","specPath":"specs/skills/trinity-god-mode.t27","summary":{"en":"GOD MODE — Agent monitoring dashboard. Shows swarm status, task queue, pipeline compliance, circuit breakers, git activity, and rule violations.","ru":"GOD MODE: панель наблюдения за агентами — состояние роя, очередь задач, соблюдение конвейера, предохранители, активность git, нарушения правил."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/implement-issue/SKILL.md","sha256":"88ccafaece204489bd38ee3f2fbd5f7db3d38275c2ba7e5a9bfca2852be596bb"},"discarded":0,"fields":{"COMMAND":"/implement-issue [issue-number]","ENABLED":true,"ID":"trinity/implement-issue","KIND":"skill","NAME":"implement-issue","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Read a GitHub issue, create a branch, implement the solution, and open a PR. Use when given an issue number to implement.","TAGS":["github","issue","pr"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/implement-issue","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_implement_issue","name":{"en":"implement-issue"},"runBy":[],"sha256":"789be8f18768603dfb45d8664ad51fc1bb1311f82a0288c3a93efc19c08534e6","specPath":"specs/skills/trinity-implement-issue.t27","summary":{"en":"Read a GitHub issue, create a branch, implement the solution, and open a PR. Use when given an issue number to implement.","ru":"Прочитать GitHub issue, создать ветку, реализовать решение и открыть PR. Для работы по номеру задачи."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/queen-hive-visuals/SKILL.md","sha256":"c6d914dc81c84fc13dacca37de258563261a451a139e3c1644bbdc8b7a97b414"},"discarded":0,"fields":{"COMMAND":"/queen-hive-visuals","ENABLED":true,"ID":"trinity/queen-hive-visuals","KIND":"skill","NAME":"queen-hive-visuals","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Maintain QUEEN's floating hive, original point-down TRINITY logo, sharp issue displays and matching camera interactions.","TAGS":["queen","ui","3d"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/queen-hive-visuals","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_queen_hive_visuals","name":{"en":"queen-hive-visuals"},"runBy":[],"sha256":"327c5189a740d092290fe5dd10de3cf4da56b54a44d1bec9be8414451e70ae0e","specPath":"specs/skills/trinity-queen-hive-visuals.t27","summary":{"en":"Maintain QUEEN's floating hive, original point-down TRINITY logo, sharp issue displays and matching camera interactions.","ru":"Поддержка плавающего улья Королевы, оригинального логотипа TRINITY остриём вниз, чётких табло задач и согласованных движений камеры."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/review-code/SKILL.md","sha256":"e5712744615c2422d9a0c81413422e6e1a2ac97c4bfac8bb508f1cd04bf05d75"},"discarded":0,"fields":{"COMMAND":"/review-code [file-or-branch] (optional)","ENABLED":true,"ID":"trinity/review-code","KIND":"skill","NAME":"review-code","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Review recent code changes for bugs, style issues, and improvements. Use after writing code or before committing.","TAGS":["review","quality"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/review-code","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_review_code","name":{"en":"review-code"},"runBy":[],"sha256":"c5cf6004cce2295f921b7d983e252a9aafbd92ad662db2d5b595824c2774b7a4","specPath":"specs/skills/trinity-review-code.t27","summary":{"en":"Review recent code changes for bugs, style issues, and improvements. Use after writing code or before committing.","ru":"Ревью недавних изменений кода: ошибки, стиль, улучшения. После написания кода или перед коммитом."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/run-tests/SKILL.md","sha256":"7ead75ab36b840ec5588013b0de8f2c23ed3a27a0d3a5160d7e68334e5992a79"},"discarded":0,"fields":{"COMMAND":"/run-tests [module] (optional - all, vsa, vm, bsd, hslm, tri-api)","ENABLED":true,"ID":"trinity/run-tests","KIND":"skill","NAME":"run-tests","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Build the project, check for warnings, run all tests, and report results. Quick health check for the codebase.","TAGS":["tests","build","zig"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/run-tests","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_run_tests","name":{"en":"run-tests"},"runBy":[],"sha256":"8c683823554ee75c69b4dd27468eb819bd8f7ea003e91cf216b80be03dca13a6","specPath":"specs/skills/trinity-run-tests.t27","summary":{"en":"Build the project, check for warnings, run all tests, and report results. Quick health check for the codebase.","ru":"Собрать проект, проверить предупреждения, прогнать все тесты и отчитаться. Быстрая проверка здоровья кодовой базы."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/scholar/SKILL.md","sha256":"5c35431008a4d2694cf2e7ad65c01d6f1559d5836b8f7cfb0c3422f752d5b2c2"},"discarded":0,"fields":{"COMMAND":"/scholar [scan|eval|apply|full|report|topic:\"query\"]","ENABLED":true,"ID":"trinity/scholar","KIND":"skill","NAME":"scholar","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Self-Evolving Research Agent — scans web for relevant tech, evaluates findings, proposes improvements. Uses Perplexity Sonar API via MCP.","TAGS":["research","perplexity","mcp"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/scholar","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_scholar","name":{"en":"scholar"},"runBy":[],"sha256":"70bc5c1e10d9905fa22a38de1c7b88926b75d69b6a82305e69613820f70f83ae","specPath":"specs/skills/trinity-scholar.t27","summary":{"en":"Self-Evolving Research Agent — scans web for relevant tech, evaluates findings, proposes improvements. Uses Perplexity Sonar API via MCP.","ru":"Саморазвивающийся исследовательский агент: сканирует веб на релевантные технологии, оценивает находки, предлагает улучшения. Perplexity Sonar через MCP."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/status/SKILL.md","sha256":"e4bc75ff45cdc52968f1ef0ac6e1a1a482072f78e14283b1e71b6fe13a054890"},"discarded":0,"fields":{"COMMAND":"/status [quick|full] [lang:ru|en]","ENABLED":true,"ID":"trinity/status","KIND":"skill","NAME":"status","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Live system dashboard — 12-dimension ouroboros score, verdict v3, agents, build health, recommendations","TAGS":["dashboard","ouroboros","verdict"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/status","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_status","name":{"en":"status"},"runBy":[],"sha256":"09185d50913f410e9c5cdd3338d2fd38adb97f0997208c6440c89b2325268b9c","specPath":"specs/skills/trinity-status.t27","summary":{"en":"Live system dashboard — 12-dimension ouroboros score, verdict v3, agents, build health, recommendations","ru":"Живая панель системы: 12-мерная оценка уробороса, verdict v3, агенты, здоровье сборки, рекомендации."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/tech-tree/SKILL.md","sha256":"f84c8ea67ab696a9bc3828ccfe8b8708572fe5ddb7574f409ddf75c467744a0f"},"discarded":0,"fields":{"COMMAND":"/tree [next|path|blocked|update]","ENABLED":true,"ID":"trinity/tech-tree","KIND":"skill","NAME":"tree","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Technology dependency tree — shows issue DAG, prereqs, critical path, blocked/ready tasks. Single source of truth for work order.","TAGS":["issues","dag","planning"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/tech-tree","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_tech_tree","name":{"en":"tree"},"runBy":[],"sha256":"951d3d8a6dd0c1c6c62a303c660a45205624bf2f558fd090b34fe5b45b9f1501","specPath":"specs/skills/trinity-tech-tree.t27","summary":{"en":"Technology dependency tree — shows issue DAG, prereqs, critical path, blocked/ready tasks. Single source of truth for work order.","ru":"Дерево технологических зависимостей: DAG задач, предпосылки, критический путь, заблокированные и готовые задачи. Единый источник порядка работ."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/tri/SKILL.md","sha256":"4c38d1005219336c88583e985b5208653fa199d12481d98173604922dc35e4a5"},"discarded":0,"fields":{"COMMAND":"/tri [short] [full] [audit] [coverage] [lang:ru|en] [humor|prof|toxic|zen]","ENABLED":true,"ID":"trinity/tri","KIND":"skill","NAME":"tri","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"TRI status dashboard — agent thought mode by default, /tri full for visual dashboard","TAGS":["tri","dashboard","status"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/tri","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_tri","name":{"en":"tri"},"runBy":[],"sha256":"09f58e175bfeb73fb49d30bfdc6e0b197d775e6883fa8bb9417dffc92ffefebd","specPath":"specs/skills/trinity-tri.t27","summary":{"en":"TRI status dashboard — agent thought mode by default, /tri full for visual dashboard","ru":"Панель статуса TRI: по умолчанию режим мысли агента, /tri full — визуальная панель."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/trinity-test/SKILL.md","sha256":"648c7f095f0b9739a17d7a5d47fbfbe3e6dc2b2bd940fb6cea837632940b6c56"},"discarded":0,"fields":{"COMMAND":"/trinity-test [module] (vsa, vm, firebird, all)","ENABLED":true,"ID":"trinity/trinity-test","KIND":"skill","NAME":"trinity-test","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Run Trinity test suites and analyze results. Use for testing VSA, VM, Firebird, WASM, or full test suite.","TAGS":["tests","vsa","vm"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/trinity-test","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_trinity_test","name":{"en":"trinity-test"},"runBy":[],"sha256":"b70f5629049065477023aa4b4d51eb8272de0750a5c4809d7d445c698729c1a0","specPath":"specs/skills/trinity-trinity-test.t27","summary":{"en":"Run Trinity test suites and analyze results. Use for testing VSA, VM, Firebird, WASM, or full test suite.","ru":"Прогон тестовых наборов Trinity и разбор результатов: VSA, VM, Firebird, WASM или всё целиком."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/ux-wave/SKILL.md","sha256":"9690999e8d1e2091c80ef838e41a1d4dc48d9ffea1cf29a39294238f69954b28"},"discarded":0,"fields":{"COMMAND":"/ux-wave [wave-number] [audit]","ENABLED":true,"ID":"trinity/ux-wave","KIND":"skill","NAME":"ux-wave","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Autonomous UX improvement waves — launch 4 parallel agents per wave, audit every 5 waves, commit fixes. For Queen UI chat & network polish.","TAGS":["ux","agents","queen"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/ux-wave","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_ux_wave","name":{"en":"ux-wave"},"runBy":[],"sha256":"e63fa9e43303eefdf53f1f95499c15bb7794c7f08c9d14c1958f61e362e75ada","specPath":"specs/skills/trinity-ux-wave.t27","summary":{"en":"Autonomous UX improvement waves — launch 4 parallel agents per wave, audit every 5 waves, commit fixes. For Queen UI chat & network polish.","ru":"Автономные волны улучшения UX: 4 параллельных агента на волну, аудит каждые 5 волн, коммит исправлений. Для чата и сети Queen UI."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/vibee-gen/SKILL.md","sha256":"fc01e69f1395b590603692a29d9bb1ee533104bb716b5bb284738027df17b395"},"discarded":0,"fields":{"COMMAND":"/vibee-gen ","ENABLED":true,"ID":"trinity/vibee-gen","KIND":"skill","NAME":"vibee-gen","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Generate Zig or Verilog code from VIBEE specifications. Use when creating or updating .tri specs and regenerating code.","TAGS":["codegen","zig","verilog"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/vibee-gen","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_vibee_gen","name":{"en":"vibee-gen"},"runBy":[],"sha256":"769952ebb9d14eba7cdd2ee226b9d266de20e692ac0ee1e66ba6f622564a91f9","specPath":"specs/skills/trinity-vibee-gen.t27","summary":{"en":"Generate Zig or Verilog code from VIBEE specifications. Use when creating or updating .tri specs and regenerating code.","ru":"Генерация кода Zig или Verilog из спецификаций VIBEE. При создании или обновлении .tri-спек и перегенерации кода."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/vsa-verify/SKILL.md","sha256":"8b9c4518b1bc5dd468b12697a436f7d959278466b5282e575709d7981722905b"},"discarded":0,"fields":{"COMMAND":"/vsa-verify [operation or proof to verify]","ENABLED":true,"ID":"trinity/vsa-verify","KIND":"skill","NAME":"vsa-verify","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Verify VSA (Vector Symbolic Architecture) mathematical proofs and operations. Use for math verification, bind/unbind/bundle testing, phi identity checks.","TAGS":["vsa","math","verify"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/vsa-verify","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_vsa_verify","name":{"en":"vsa-verify"},"runBy":[],"sha256":"311455eaad1ec081b56395b38564babf52e10966f35b98b862d97bc5be60ff52","specPath":"specs/skills/trinity-vsa-verify.t27","summary":{"en":"Verify VSA (Vector Symbolic Architecture) mathematical proofs and operations. Use for math verification, bind/unbind/bundle testing, phi identity checks.","ru":"Проверка математических доказательств и операций VSA: bind/unbind/bundle, тождества phi."},"typecheckOk":true,"witness":"spec+code"},{"code":{"health":"warn","link":"unbound","path":"trinity/wave/SKILL.md","sha256":"ecf06fdfa467158f6758066aba41a8e49699f37e61c18e62716da765a7eb5487"},"discarded":0,"fields":{"COMMAND":"/wave [status|spawn |deploy|progress]","ENABLED":true,"ID":"trinity/wave","KIND":"skill","NAME":"wave","REPO":"trinity","SOURCE":"SKILL.md","SPECS":[],"SUMMARY_EN":"Farm wave management — create Railway accounts, spawn workers, deploy configs, track wave progress. Use for scaling training farm.","TAGS":["farm","railway","scaling"],"TIMEOUT_MIN":30},"health":"ok","id":"trinity/wave","inSpecCorpus":false,"messages":[],"moduleName":"skill_trinity_wave","name":{"en":"wave"},"runBy":[],"sha256":"0d205500e7adf560ff03ecfd59e90ee13b51f4624e27555d4142e5b184100d6b","specPath":"specs/skills/trinity-wave.t27","summary":{"en":"Farm wave management — create Railway accounts, spawn workers, deploy configs, track wave progress. Use for scaling training farm.","ru":"Управление волнами фермы: аккаунты Railway, запуск рабочих, раскатка конфигов, прогресс волны. Для масштабирования тренировочной фермы."},"typecheckOk":true,"witness":"spec+code"}],"version":1} diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-agent-autopilot-L1118.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-agent-autopilot-L1118.t27 new file mode 100644 index 0000000000..80ee30a651 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-agent-autopilot-L1118.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-agent-autopilot-L1118.t27 -- cron timer/999-multibots-telegraf/agent-autopilot-L1118 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/agent-autopilot-L1118). +; The schedule was read from 999-multibots-telegraf:apps/vibee-editor/render/scripts/agent-autopilot.ts#L1118. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_agent_autopilot_L1118; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/agent-autopilot-L1118"; +pub const NAME : str = "agent-autopilot-L1118"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "apps/vibee-editor/render/scripts/agent-autopilot.ts:1118"; +pub const SUMMARY_EN : str = "Runs one autopilot cycle -- the generation pass and then the channel tick -- and skips the tick entirely when the previous cycle is still running."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 1800000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Daemon mode only, under --loop or AUTOPILOT_LOOP=1; AUTOPILOT_INTERVAL_MS overrides the thirty-minute period, with a one-minute floor."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-bot-owner-billing-L474.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-bot-owner-billing-L474.t27 new file mode 100644 index 0000000000..7afcf42c31 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-bot-owner-billing-L474.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-bot-owner-billing-L474.t27 -- cron timer/999-multibots-telegraf/bot-owner-billing-L474 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/bot-owner-billing-L474). +; The schedule was read from 999-multibots-telegraf:src/services/bot-owner-billing.ts#L474. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_bot_owner_billing_L474; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/bot-owner-billing-L474"; +pub const NAME : str = "bot-owner-billing-L474"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/services/bot-owner-billing.ts:474"; +pub const SUMMARY_EN : str = "Recomputes every bot owner's debt from the payments ledger, warns them at the soft, warning and critical thresholds, and disables a bot that stays critical for three days."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 86400000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "The first run after a restart is skipped on purpose, and an owner whose payments cannot be read is left alone rather than accused."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-check-stuck-trainings.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-check-stuck-trainings.t27 new file mode 100644 index 0000000000..fe39b39f2f --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-check-stuck-trainings.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-check-stuck-trainings.t27 -- cron inngest/999-multibots-telegraf/check-stuck-trainings +; Source of truth for the job card on t27.ai (#/crons?cron=inngest/999-multibots-telegraf/check-stuck-trainings). +; The schedule was read from 999-multibots-telegraf:src/inngest_app/functions/training/checkStuckTrainings.ts#L53. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_check_stuck_trainings; + +pub const KIND : str = "cron"; +pub const ID : str = "inngest/999-multibots-telegraf/check-stuck-trainings"; +pub const NAME : str = "check-stuck-trainings"; +pub const HOST : str = "inngest"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/inngest_app/functions/training/checkStuckTrainings.ts"; +pub const SUMMARY_EN : str = "Finds model trainings still marked pending or processing past the stuck threshold and reconciles them against Replicate."; +pub const SCHEDULE : str = "*/30 * * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Records whose Replicate id is still a placeholder are skipped, because there is nothing on the provider to ask about."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "inngest-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-ci.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-ci.t27 new file mode 100644 index 0000000000..1869ef6d95 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-ci.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-ci.t27 -- cron github-actions/999-multibots-telegraf/ci +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/999-multibots-telegraf/ci). +; The schedule was read from 999-multibots-telegraf:.github/workflows/ci.yml#L10. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_ci; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/999-multibots-telegraf/ci"; +pub const NAME : str = "CI/CD Pipeline"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = ".github/workflows/ci.yml"; +pub const SUMMARY_EN : str = "Runs the whole build, type-check and test pipeline once a day so a dependency that rots without a push still gets caught."; +pub const SCHEDULE : str = "0 2 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "GitHub Actions on this repository have not run since 2026-06-02 (billing); the schedule stands in the file but does not fire."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-crmProactive-L375.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-crmProactive-L375.t27 new file mode 100644 index 0000000000..a2c212bce3 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-crmProactive-L375.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-crmProactive-L375.t27 -- cron timer/999-multibots-telegraf/crmProactive-L375 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/crmProactive-L375). +; The schedule was read from 999-multibots-telegraf:src/services/crmProactive.ts#L375. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_crmProactive_L375; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/crmProactive-L375"; +pub const NAME : str = "crmProactive-L375"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/services/crmProactive.ts:375"; +pub const SUMMARY_EN : str = "Runs one proactive seller turn for the owner: walks the CRM queue and puts the next draft card in front of them without being asked."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 1800000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "The period is CRM_PROACTIVE_MINUTES from src/index.ts, thirty minutes by default; the first sweep waits two minutes after start."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-daily-sales-advisor.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-daily-sales-advisor.t27 new file mode 100644 index 0000000000..17748b4368 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-daily-sales-advisor.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-daily-sales-advisor.t27 -- cron inngest/999-multibots-telegraf/daily-sales-advisor +; Source of truth for the job card on t27.ai (#/crons?cron=inngest/999-multibots-telegraf/daily-sales-advisor). +; The schedule was read from 999-multibots-telegraf:src/inngest_app/functions/analytics/dailySalesAdvisor.ts#L56. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_daily_sales_advisor; + +pub const KIND : str = "cron"; +pub const ID : str = "inngest/999-multibots-telegraf/daily-sales-advisor"; +pub const NAME : str = "daily-sales-advisor"; +pub const HOST : str = "inngest"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/inngest_app/functions/analytics/dailySalesAdvisor.ts"; +pub const SUMMARY_EN : str = "Builds every bot owner a morning report of yesterday's revenue, the week's costs and which services their users actually bought."; +pub const SCHEDULE : str = "0 9 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "One report per owner across all of their bots, assembled from the payments ledger rather than from a balance column."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "inngest-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-generate-jobs-L164.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-generate-jobs-L164.t27 new file mode 100644 index 0000000000..89cfd70d3c --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-generate-jobs-L164.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-generate-jobs-L164.t27 -- cron timer/999-multibots-telegraf/generate-jobs-L164 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/generate-jobs-L164). +; The schedule was read from 999-multibots-telegraf:apps/vibee-editor/render/generate-jobs.ts#L164. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_generate_jobs_L164; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/generate-jobs-L164"; +pub const NAME : str = "generate-jobs-L164"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "apps/vibee-editor/render/generate-jobs.ts:164"; +pub const SUMMARY_EN : str = "Sweeps finished generation jobs out of memory an hour after they end, once a caller who lost the connection has had time to collect the result."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 300000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-health-check.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-health-check.t27 new file mode 100644 index 0000000000..ba1dd0f4be --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-health-check.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-health-check.t27 -- cron inngest/999-multibots-telegraf/health-check +; Source of truth for the job card on t27.ai (#/crons?cron=inngest/999-multibots-telegraf/health-check). +; The schedule was read from 999-multibots-telegraf:src/inngest_app/functions/monitoring/criticalErrorMonitor.ts#L243. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_health_check; + +pub const KIND : str = "cron"; +pub const ID : str = "inngest/999-multibots-telegraf/health-check"; +pub const NAME : str = "health-check"; +pub const HOST : str = "inngest"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/inngest_app/functions/monitoring/criticalErrorMonitor.ts"; +pub const SUMMARY_EN : str = "Probes the main API and the Inngest service every half hour and reports what answered."; +pub const SCHEDULE : str = "*/30 * * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "inngest-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-log-monitor.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-log-monitor.t27 new file mode 100644 index 0000000000..5829d6f96d --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-log-monitor.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-log-monitor.t27 -- cron inngest/999-multibots-telegraf/log-monitor +; Source of truth for the job card on t27.ai (#/crons?cron=inngest/999-multibots-telegraf/log-monitor). +; The schedule was read from 999-multibots-telegraf:src/inngest_app/functions/monitoring/logMonitor.ts#L360. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_log_monitor; + +pub const KIND : str = "cron"; +pub const ID : str = "inngest/999-multibots-telegraf/log-monitor"; +pub const NAME : str = "log-monitor"; +pub const HOST : str = "inngest"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/inngest_app/functions/monitoring/logMonitor.ts"; +pub const SUMMARY_EN : str = "Reads the application logs once a day, has a model summarise the errors and warnings, and sends the digest to Telegram."; +pub const SCHEDULE : str = "0 10 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "inngest-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-notificationHandler-L318.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-notificationHandler-L318.t27 new file mode 100644 index 0000000000..5072329f65 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-notificationHandler-L318.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-notificationHandler-L318.t27 -- cron timer/999-multibots-telegraf/notificationHandler-L318 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/notificationHandler-L318). +; The schedule was read from 999-multibots-telegraf:src/handlers/notificationHandler.ts#L318. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_notificationHandler_L318; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/notificationHandler-L318"; +pub const NAME : str = "notificationHandler-L318"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/handlers/notificationHandler.ts:318"; +pub const SUMMARY_EN : str = "Drains the notification queue and delivers the Telegram messages waiting in it."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 60000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Wrapped in an exclusive tick, so two bot processes cannot deliver the same notification twice."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-notificationHandler-L324.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-notificationHandler-L324.t27 new file mode 100644 index 0000000000..1fc9d93dc0 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-notificationHandler-L324.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-notificationHandler-L324.t27 -- cron timer/999-multibots-telegraf/notificationHandler-L324 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/notificationHandler-L324). +; The schedule was read from 999-multibots-telegraf:src/handlers/notificationHandler.ts#L324. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_notificationHandler_L324; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/notificationHandler-L324"; +pub const NAME : str = "notificationHandler-L324"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/handlers/notificationHandler.ts:324"; +pub const SUMMARY_EN : str = "Deletes notification messages old enough that nobody is going to read them."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 3600000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-periodic-webhook-health-check.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-periodic-webhook-health-check.t27 new file mode 100644 index 0000000000..15f2a98abe --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-periodic-webhook-health-check.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-periodic-webhook-health-check.t27 -- cron inngest/999-multibots-telegraf/periodic-webhook-health-check +; Source of truth for the job card on t27.ai (#/crons?cron=inngest/999-multibots-telegraf/periodic-webhook-health-check). +; The schedule was read from 999-multibots-telegraf:src/inngest_app/functions/webhookHealthGuard.ts#L218. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_periodic_webhook_health_check; + +pub const KIND : str = "cron"; +pub const ID : str = "inngest/999-multibots-telegraf/periodic-webhook-health-check"; +pub const NAME : str = "periodic-webhook-health-check"; +pub const HOST : str = "inngest"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/inngest_app/functions/webhookHealthGuard.ts"; +pub const SUMMARY_EN : str = "Tests both Telegram webhook URLs every hour and sends the admin a critical alarm when neither of them answers."; +pub const SCHEDULE : str = "0 * * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "inngest-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-production-monitor-L80.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-production-monitor-L80.t27 new file mode 100644 index 0000000000..e2703a3fca --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-production-monitor-L80.t27 @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-production-monitor-L80.t27 -- cron timer/999-multibots-telegraf/production-monitor-L80 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/production-monitor-L80). +; The schedule was read from 999-multibots-telegraf:src/utils/production-monitor.ts#L80. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_production_monitor_L80; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/production-monitor-L80"; +pub const NAME : str = "production-monitor-L80"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/utils/production-monitor.ts:80"; +pub const SUMMARY_EN : str = "Re-runs the container, nginx, port, domain, webhook and system-resource checks, and applies the fixes that are enabled."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 300000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +; Started only by hand (`monitor` argument); the bot's entry point never starts it. +pub const ENABLED : bool = false; +pub const NOTE : str = "Started only by running this module from the command line with the `monitor` argument; the bot's own entry point never starts it."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-provider-health-monitor-L344.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-provider-health-monitor-L344.t27 new file mode 100644 index 0000000000..25f0b5092c --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-provider-health-monitor-L344.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-provider-health-monitor-L344.t27 -- cron timer/999-multibots-telegraf/provider-health-monitor-L344 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/provider-health-monitor-L344). +; The schedule was read from 999-multibots-telegraf:src/services/provider-health-monitor.ts#L344. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_provider_health_monitor_L344; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/provider-health-monitor-L344"; +pub const NAME : str = "provider-health-monitor-L344"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/services/provider-health-monitor.ts:344"; +pub const SUMMARY_EN : str = "Checks every AI provider that is not parked and records whether it is currently answering."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 300000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Providers listed in HEALTH_SKIP_PROVIDERS are deliberately not watched and do not raise the alarm."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L10845.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L10845.t27 new file mode 100644 index 0000000000..7d75c44613 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L10845.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-render-server-L10845.t27 -- cron timer/999-multibots-telegraf/render-server-L10845 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/render-server-L10845). +; The schedule was read from 999-multibots-telegraf:apps/vibee-editor/render/render-server.ts#L10845. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_render_server_L10845; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/render-server-L10845"; +pub const NAME : str = "render-server-L10845"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "apps/vibee-editor/render/render-server.ts:10845"; +pub const SUMMARY_EN : str = "Pings every websocket peer and drops the ones that did not answer the previous round."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 30000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L2301.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L2301.t27 new file mode 100644 index 0000000000..3ad171f190 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L2301.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-render-server-L2301.t27 -- cron timer/999-multibots-telegraf/render-server-L2301 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/render-server-L2301). +; The schedule was read from 999-multibots-telegraf:apps/vibee-editor/render/render-server.ts#L2301. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_render_server_L2301; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/render-server-L2301"; +pub const NAME : str = "render-server-L2301"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "apps/vibee-editor/render/render-server.ts:2301"; +pub const SUMMARY_EN : str = "Removes render jobs older than an hour from the in-memory job map."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 60000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L2325.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L2325.t27 new file mode 100644 index 0000000000..dd18521e7d --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L2325.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-render-server-L2325.t27 -- cron timer/999-multibots-telegraf/render-server-L2325 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/render-server-L2325). +; The schedule was read from 999-multibots-telegraf:apps/vibee-editor/render/render-server.ts#L2325. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_render_server_L2325; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/render-server-L2325"; +pub const NAME : str = "render-server-L2325"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "apps/vibee-editor/render/render-server.ts:2325"; +pub const SUMMARY_EN : str = "Reads the hive journal and delivers the queen's report to the keepers, passing alarms straight through and batching ordinary events."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 600000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Checks often and writes rarely: the cursor lives in the database, because this Railway project has no volume and a deploy wipes local state."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L919.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L919.t27 new file mode 100644 index 0000000000..14e7f0192e --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-render-server-L919.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-render-server-L919.t27 -- cron timer/999-multibots-telegraf/render-server-L919 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/render-server-L919). +; The schedule was read from 999-multibots-telegraf:apps/vibee-editor/render/render-server.ts#L919. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_render_server_L919; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/render-server-L919"; +pub const NAME : str = "render-server-L919"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "apps/vibee-editor/render/render-server.ts:919"; +pub const SUMMARY_EN : str = "Re-reads the session revocation list from Postgres so a revoked browser session stops being accepted."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 5000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Only starts when a session signing key is configured; a deployed render server refuses to boot without one."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-security.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-security.t27 new file mode 100644 index 0000000000..551c091fb8 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-security.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-security.t27 -- cron github-actions/999-multibots-telegraf/security +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/999-multibots-telegraf/security). +; The schedule was read from 999-multibots-telegraf:.github/workflows/security.yml#L10. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_security; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/999-multibots-telegraf/security"; +pub const NAME : str = "Security Checks"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = ".github/workflows/security.yml"; +pub const SUMMARY_EN : str = "Runs the weekly security sweep: secret detection over the full history, dependency audit and code scanning."; +pub const SCHEDULE : str = "0 3 * * 0"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "GitHub Actions on this repository have not run since 2026-06-02 (billing); the schedule stands in the file but does not fire."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-skill-detector.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-skill-detector.t27 new file mode 100644 index 0000000000..38f28c5933 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-skill-detector.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-skill-detector.t27 -- cron inngest/999-multibots-telegraf/skill-detector +; Source of truth for the job card on t27.ai (#/crons?cron=inngest/999-multibots-telegraf/skill-detector). +; The schedule was read from 999-multibots-telegraf:src/inngest_app/functions/analytics/skillDetector.ts#L32. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_skill_detector; + +pub const KIND : str = "cron"; +pub const ID : str = "inngest/999-multibots-telegraf/skill-detector"; +pub const NAME : str = "skill-detector"; +pub const HOST : str = "inngest"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/inngest_app/functions/analytics/skillDetector.ts"; +pub const SUMMARY_EN : str = "Mines successful generations for repeated model and prompt patterns once a day and saves the new ones as reusable skills."; +pub const SCHEDULE : str = "0 10 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "the \"skills\" it saves are bot prompt patterns, not Claude Code skills; no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "inngest-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-taskCache-L27.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-taskCache-L27.t27 new file mode 100644 index 0000000000..9151598133 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-taskCache-L27.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-taskCache-L27.t27 -- cron timer/999-multibots-telegraf/taskCache-L27 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/taskCache-L27). +; The schedule was read from 999-multibots-telegraf:src/modules/videoGenerator/taskCache.ts#L27. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_taskCache_L27; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/taskCache-L27"; +pub const NAME : str = "taskCache-L27"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/modules/videoGenerator/taskCache.ts:27"; +pub const SUMMARY_EN : str = "Drops video-generation cache entries past their fifteen-minute lifetime so a finished task cannot block the next request."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 300000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-video-task-store-L156.t27 b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-video-task-store-L156.t27 new file mode 100644 index 0000000000..f98f5ced27 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/999-multibots-telegraf-video-task-store-L156.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/999-multibots-telegraf-video-task-store-L156.t27 -- cron timer/999-multibots-telegraf/video-task-store-L156 +; Source of truth for the job card on t27.ai (#/crons?cron=timer/999-multibots-telegraf/video-task-store-L156). +; The schedule was read from 999-multibots-telegraf:src/services/video-task-store.ts#L156. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_999_multibots_telegraf_video_task_store_L156; + +pub const KIND : str = "cron"; +pub const ID : str = "timer/999-multibots-telegraf/video-task-store-L156"; +pub const NAME : str = "video-task-store-L156"; +pub const HOST : str = "timer"; +pub const REPO : str = "999-multibots-telegraf"; +pub const SERVICE : str = "src/services/video-task-store.ts:156"; +pub const SUMMARY_EN : str = "Forgets video tasks older than an hour and rewrites the store to disk when anything was removed."; +; A setInterval inside a long-lived process: a period, not a cron expression. +pub const INTERVAL_MS : u32 = 600000; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; diff --git a/apps/website/public/t27/files/specs/crons/README.md b/apps/website/public/t27/files/specs/crons/README.md new file mode 100644 index 0000000000..478251695e --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/README.md @@ -0,0 +1,54 @@ +# specs/crons — scheduled jobs as first-class `.t27` specs + +One file per scheduled job the site publishes (`apps/website/public/crons/manifest.json`): +GitHub Actions schedules, Inngest functions, Railway cron services and in-process +`setInterval` timers. The `.t27` file is the source of truth for the job card on +t27.ai; `public/crons/spec-crons.json` is generated from it by the real compiler. + +File name: `-.t27`; module name: `cron_`. + +## Schema (every constant is `pub const`) + +| constant | type | meaning | +|-----------------|----------|-------------------------------------------------------------------------------------------| +| `KIND` | `str` | always `"cron"` | +| `ID` | `str` | `//`; must equal the id in the code manifest | +| `NAME` | `str` | display name | +| `HOST` | `str` | `github-actions` \| `inngest` \| `railway-cron` \| `timer` | +| `REPO` | `str` | short repo name | +| `SERVICE` | `str` | workflow file / Inngest function file / Railway service / `file:line` of the timer | +| `SUMMARY_EN` | `str` | what the job does (English) | +| `SCHEDULE` | `str` | cron expression (absent for timers; `""` when only a dashboard holds it, see `SCHEDULE_NOTE`) | +| `INTERVAL_MS` | `u32` | period of an in-process timer (timers only, instead of `SCHEDULE`) | +| `TZ` | `str` | timezone of the schedule | +| `RUNS` | `[N]str` | skill IDs this job launches — **the control link**; only when the source evidently invokes one | +| `RUNS_NOTE` | `str` | why `RUNS` is what it is (e.g. `"no skill invocation found in source"`) | +| `ENABLED` | `bool` | whether the job is meant to fire | +| `NOTE` | `str` | optional caveat carried from the code catalog | +| `ON_FAILURE` | `str` | `issue` \| `log` \| `unknown` | +| `CONTROL` | `str` | `github-actions-dispatch` \| `railway-dashboard` \| `inngest-dashboard` \| `code-only` | + +## Language + +Specs are English-only (t27 `LANG-EN`: `bootstrap/build.rs` fails the build on any +Cyrillic under `specs/`). There is no `SUMMARY_RU`. Translations are connected +through a spec: `specs/i18n/agents-.t27` (see `specs/i18n/README.md`) +declares the locale, the spec directories it covers (`SCOPE`), the fields a bundle +may translate (`FIELDS`: `SUMMARY`, `NAME`) and the bundle file that carries the +text (`BUNDLE_REPO`/`BUNDLE_PATH`, keyed by `ID`). The Russian layer is +`specs/i18n/agents-ru.t27` -> `trinity:apps/website/i18n/agents.ru.json`. The site's +generator discovers every `specs/i18n/*.t27`, loads each bundle, fails on an entry +whose `ID` matches no spec (`ORPHANS_ALLOWED = false`) and emits `summary`/`name` +as `{en, ...}` plus an `i18n` list with the coverage per catalog. A spec +with no translation falls back to English (`FALLBACK = "en"`). + +## Rules the generator enforces + +- duplicate `ID` → build fails; +- a `RUNS` entry that names no skill spec → the job is marked `health: "fail"`; +- an `ID` absent from the code manifest → `witness: "spec-only"` (warn); +- every file must pass `typecheck.ok === true` with nothing discarded by the compiler. + +As of the first commit every `RUNS` is empty: none of the 33 jobs was found to +invoke a Claude Code skill by name (checked in the workflow files and the code +catalog annotations). The link exists so that the day a job does, the spec says so. diff --git a/apps/website/public/t27/files/specs/crons/railway-jobsearch-cron.t27 b/apps/website/public/t27/files/specs/crons/railway-jobsearch-cron.t27 new file mode 100644 index 0000000000..42f08bada9 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/railway-jobsearch-cron.t27 @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/railway-jobsearch-cron.t27 -- cron railway-cron/999/jobsearch-cron +; Source of truth for the job card on t27.ai (#/crons?cron=railway-cron/999/jobsearch-cron). +; The schedule lives in the Railway dashboard of project 999 and is not in any checkout. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_railway_jobsearch_cron; + +pub const KIND : str = "cron"; +pub const ID : str = "railway-cron/999/jobsearch-cron"; +pub const NAME : str = "jobsearch-cron"; +pub const HOST : str = "railway-cron"; +pub const REPO : str = "railway"; +pub const SERVICE : str = "jobsearch-cron"; +pub const SUMMARY_EN : str = "The scheduled job-search service of the Railway project, reachable at cron.t27.ai."; +; No expression is readable from a checkout; the dashboard holds it. +pub const SCHEDULE : str = ""; +pub const SCHEDULE_NOTE : str = "configured in the Railway dashboard (project 999); not readable from the checkout"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "The schedule is configured in the Railway dashboard for project 999 and is not readable from the checkout; the probe below is what this catalog can verify."; +pub const ON_FAILURE : str = "unknown"; +pub const CONTROL : str = "railway-dashboard"; diff --git a/apps/website/public/t27/files/specs/crons/t27-formal-mutation.t27 b/apps/website/public/t27/files/specs/crons/t27-formal-mutation.t27 new file mode 100644 index 0000000000..a97d5018b2 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/t27-formal-mutation.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/t27-formal-mutation.t27 -- cron github-actions/t27/formal-mutation +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/t27/formal-mutation). +; The schedule was read from t27:.github/workflows/formal-mutation.yml#L25. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_t27_formal_mutation; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/t27/formal-mutation"; +pub const NAME : str = "formal-mutation"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "t27"; +pub const SERVICE : str = ".github/workflows/formal-mutation.yml"; +pub const SUMMARY_EN : str = "Proves the formal gates actually bite by mutating the generated RTL once a week and requiring each gate to go red for its own mutation."; +pub const SCHEDULE : str = "0 5 * * 1"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "Weekly rather than per-push because a full run costs about thirty yosys invocations."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/t27-pr-dashboard.t27 b/apps/website/public/t27/files/specs/crons/t27-pr-dashboard.t27 new file mode 100644 index 0000000000..b77f546b1e --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/t27-pr-dashboard.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/t27-pr-dashboard.t27 -- cron github-actions/t27/pr-dashboard +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/t27/pr-dashboard). +; The schedule was read from t27:.github/workflows/pr-dashboard.yml#L6. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_t27_pr_dashboard; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/t27/pr-dashboard"; +pub const NAME : str = "PR Dashboard"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "t27"; +pub const SERVICE : str = ".github/workflows/pr-dashboard.yml"; +pub const SUMMARY_EN : str = "Rebuilds the pull-request dashboard every hour from the current state of the open pull requests."; +pub const SCHEDULE : str = "0 * * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-agent-cron-cleanup.t27 b/apps/website/public/t27/files/specs/crons/trinity-agent-cron-cleanup.t27 new file mode 100644 index 0000000000..075df9141c --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-agent-cron-cleanup.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-agent-cron-cleanup.t27 -- cron github-actions/trinity/agent-cron-cleanup +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/agent-cron-cleanup). +; The schedule was read from trinity:.github/workflows/agent-cron-cleanup.yml#L4. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_agent_cron_cleanup; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/agent-cron-cleanup"; +pub const NAME : str = "Trinity Agent Cron Cleanup"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/agent-cron-cleanup.yml"; +pub const SUMMARY_EN : str = "Finds agent deployments that got stuck and kills them, every two hours."; +pub const SCHEDULE : str = "0 */2 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-agent-queue-drain.t27 b/apps/website/public/t27/files/specs/crons/trinity-agent-queue-drain.t27 new file mode 100644 index 0000000000..8d4722f785 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-agent-queue-drain.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-agent-queue-drain.t27 -- cron github-actions/trinity/agent-queue-drain +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/agent-queue-drain). +; The schedule was read from trinity:.github/workflows/agent-queue-drain.yml#L4. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_agent_queue_drain; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/agent-queue-drain"; +pub const NAME : str = "Trinity Agent Queue Drain"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/agent-queue-drain.yml"; +pub const SUMMARY_EN : str = "Looks for queued issues and free agent slots every five minutes and starts the next agent when there is room."; +pub const SCHEDULE : str = "*/5 * * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "dispatches agent-spawn-pool.yml for a queued issue; no named skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-brain-ci.t27 b/apps/website/public/t27/files/specs/crons/trinity-brain-ci.t27 new file mode 100644 index 0000000000..b7fb20d23a --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-brain-ci.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-brain-ci.t27 -- cron github-actions/trinity/brain-ci +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/brain-ci). +; The schedule was read from trinity:.github/workflows/brain-ci.yml#L11. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_brain_ci; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/brain-ci"; +pub const NAME : str = "S³AI Brain CI — \"Functional MRI\" Gate"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/brain-ci.yml"; +pub const SUMMARY_EN : str = "Runs the weekly brain stress test, the long version of the health gate that every commit already gets."; +pub const SCHEDULE : str = "0 0 * * 0"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-codegen.t27 b/apps/website/public/t27/files/specs/crons/trinity-codegen.t27 new file mode 100644 index 0000000000..aca6d2a285 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-codegen.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-codegen.t27 -- cron github-actions/trinity/codegen +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/codegen). +; The schedule was read from trinity:.github/workflows/codegen.yml#L10. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_codegen; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/codegen"; +pub const NAME : str = "Codegen Validation"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/codegen.yml"; +pub const SUMMARY_EN : str = "Validates the VIBEE code generator once a day, independently of whether anyone pushed."; +pub const SCHEDULE : str = "0 0 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-discover-callers.t27 b/apps/website/public/t27/files/specs/crons/trinity-discover-callers.t27 new file mode 100644 index 0000000000..400baeb61e --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-discover-callers.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-discover-callers.t27 -- cron github-actions/trinity/discover-callers +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/discover-callers). +; The schedule was read from trinity:.github/workflows/discover-callers.yml#L16. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_discover_callers; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/discover-callers"; +pub const NAME : str = "Discover RTL check callers"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/discover-callers.yml"; +pub const SUMMARY_EN : str = "Reads GitHub for repositories that run the RTL check, so the gallery counts its own users instead of being hand-written."; +pub const SCHEDULE : str = "41 5 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-pages-health-check.t27 b/apps/website/public/t27/files/specs/crons/trinity-pages-health-check.t27 new file mode 100644 index 0000000000..be5bb40b1a --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-pages-health-check.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-pages-health-check.t27 -- cron github-actions/trinity/pages-health-check +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/pages-health-check). +; The schedule was read from trinity:.github/workflows/pages-health-check.yml#L17. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_pages_health_check; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/pages-health-check"; +pub const NAME : str = "Pages Health Check"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/pages-health-check.yml"; +pub const SUMMARY_EN : str = "Fetches the published site every fifteen minutes, follows the redirect, and redeploys only when the evidence says the deploy is what broke."; +pub const SCHEDULE : str = "*/15 * * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const NOTE : str = "An earlier version judged a bare status code, failed ninety-six times a day on a site that was up, and dispatched a redeploy each time."; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-signal-health-self.t27 b/apps/website/public/t27/files/specs/crons/trinity-signal-health-self.t27 new file mode 100644 index 0000000000..9bbc99e610 --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-signal-health-self.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-signal-health-self.t27 -- cron github-actions/trinity/signal-health-self +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/signal-health-self). +; The schedule was read from trinity:.github/workflows/signal-health-self.yml#L17. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_signal_health_self; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/signal-health-self"; +pub const NAME : str = "Signal health (self)"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/signal-health-self.yml"; +pub const SUMMARY_EN : str = "Applies the signal-health check to the repository that sells it and writes the numbers into the website data."; +pub const SCHEDULE : str = "23 4 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/crons/trinity-site-live-gate.t27 b/apps/website/public/t27/files/specs/crons/trinity-site-live-gate.t27 new file mode 100644 index 0000000000..28f5e73e5e --- /dev/null +++ b/apps/website/public/t27/files/specs/crons/trinity-site-live-gate.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/crons/trinity-site-live-gate.t27 -- cron github-actions/trinity/site-live-gate +; Source of truth for the job card on t27.ai (#/crons?cron=github-actions/trinity/site-live-gate). +; The schedule was read from trinity:.github/workflows/site-live-gate.yml#L33. +; A cron CONTROLS skills: RUNS names the skill IDs it launches. It is empty +; unless the source evidently invokes one -- nothing here is inferred from a name. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module cron_trinity_site_live_gate; + +pub const KIND : str = "cron"; +pub const ID : str = "github-actions/trinity/site-live-gate"; +pub const NAME : str = "Site live gate"; +pub const HOST : str = "github-actions"; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/site-live-gate.yml"; +pub const SUMMARY_EN : str = "Compares the content-hashed entry bundle served at the apex against a build of main, so that 'deployed' means a reader can actually see it."; +pub const SCHEDULE : str = "17 6 * * *"; +pub const TZ : str = "UTC"; +pub const RUNS : [0]str = []; +pub const RUNS_NOTE : str = "no skill invocation found in source"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "github-actions-dispatch"; diff --git a/apps/website/public/t27/files/specs/i18n/README.md b/apps/website/public/t27/files/specs/i18n/README.md new file mode 100644 index 0000000000..970a37d069 --- /dev/null +++ b/apps/website/public/t27/files/specs/i18n/README.md @@ -0,0 +1,67 @@ +# specs/i18n -- translation contracts for the agent specs + +Specs are English-only (t27 `LANG-EN`: `bootstrap/build.rs` fails the build on any +Cyrillic under `specs/`). A translation is therefore never inside a `.t27`. It is +**connected through** a `.t27`: one module per locale under this directory declares +the contract, and a bundle file outside the spec corpus carries the text. + +| File | Module | Locale | Bundle | +| --- | --- | --- | --- | +| `agents-ru.t27` | `i18n_agents_ru` | `ru` | `trinity:apps/website/i18n/agents.ru.json` | + +Add a locale by copying `agents-ru.t27` to `agents-.t27`, renaming the module +to `i18n_agents_` and changing `LOCALE` and `BUNDLE_PATH`. Nothing else knows +the locale list: the site's generator (`apps/website/scripts/agents-from-specs.mjs` +in gHashTag/trinity) discovers every `specs/i18n/*.t27`, so `de`, `es`, `zh` (locales +the site already speaks) are one file each. + +## Schema + +Every constant is required and `pub`. + +| Constant | Type | Meaning | +| --- | --- | --- | +| `KIND` | `str` | Always `"i18n"`. | +| `LOCALE` | `str` | The locale this bundle provides (BCP-47-shaped, e.g. `ru`). | +| `SOURCE_LOCALE` | `str` | Always `"en"`: the specs are the English source. | +| `SCOPE` | `[N]str` | Spec directories whose modules may be translated, e.g. `["specs/skills", "specs/crons"]`. An entry `ID` must resolve to a spec in one of them. | +| `FIELDS` | `[N]str` | Spec fields a bundle entry may carry. `SUMMARY` maps to the spec's `SUMMARY_EN`, `NAME` to `NAME`. Any other key in a bundle entry is a build failure. | +| `BUNDLE_REPO` | `str` | Repository that holds the bundle (`"trinity"`). Only a bundle in the building repository is loaded. | +| `BUNDLE_PATH` | `str` | Repo-relative path of the bundle. | +| `BUNDLE_FORMAT` | `str` | `"json"`. | +| `KEY` | `str` | `"ID"`: bundle entries are keyed by the spec's `ID` constant. | +| `FALLBACK` | `str` | `"en"`: what the site shows for a spec (or a field) with no translation. | +| `COVERAGE_REQUIRED` | `bool` | `true`: every spec in `SCOPE` must have an entry, or the build fails. `false`: missing entries are reported as coverage `n/total`, not failed. | +| `ORPHANS_ALLOWED` | `bool` | `false`: an entry whose `ID` matches no spec in `SCOPE` fails the build. | +| `ENABLED` | `bool` | `false` lists the contract but loads nothing. | + +## Bundle shape + +```json +{ + "$spec": "specs/i18n/agents-ru.t27", + "locale": "ru", + "entries": { + "": { "SUMMARY": "...", "NAME": "..." } + } +} +``` + +`$spec` must name the contract, `locale` must equal its `LOCALE`, and every key of an +entry must be in `FIELDS`. An entry may carry a subset of `FIELDS`; the rest fall +back to English. + +## What the site emits + +`public/skills/spec-skills.json` and `public/crons/spec-crons.json` carry, per entry, +`summary: {en, ...}` and `name: {en, ...}`, and per catalog +`i18n: [{locale, spec, sha256, bundle, enabled, fields, scope, coverage: {n, total}, missing}]`. +The Skill and Cron Explorers read the current locale from `summary`/`name` and the +SPEC panel prints `translations: ru via specs/i18n/agents-ru.t27 (n/total)`. +`npm run check:agents` (`qa/agents-spec-contract.mjs`) checks the contract: the +bundle exists at the declared path, names its contract, matches the locale, has no +orphan entry and no key outside `FIELDS`; coverage is printed. + +Validity: the file must pass `typecheck.ok === true` with empty `discarded` / +`lexerDiscarded` / `swallowed` under the vendored wasm compiler, like every other +spec here. diff --git a/apps/website/public/t27/files/specs/i18n/agents-ru.t27 b/apps/website/public/t27/files/specs/i18n/agents-ru.t27 new file mode 100644 index 0000000000..319ac45615 --- /dev/null +++ b/apps/website/public/t27/files/specs/i18n/agents-ru.t27 @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/i18n/agents-ru.t27 -- translation contract, Russian, for the agent specs +; Specs are English-only (LANG-EN). This module does not carry a single translated +; word: it declares WHICH fields of WHICH specs a bundle may translate, WHERE that +; bundle lives and HOW it is keyed, and what the site must do when a translation +; is missing. The Cyrillic payload is in the bundle file named below, never here. +; One module per locale: copy this file to agents-.t27 and change LOCALE +; and BUNDLE_PATH; the generator discovers every specs/i18n/*.t27 and hardcodes no +; locale list. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module i18n_agents_ru; + +pub const KIND : str = "i18n"; +pub const LOCALE : str = "ru"; +pub const SOURCE_LOCALE : str = "en"; +; Spec directories whose modules this bundle may translate; an entry ID must +; resolve to a spec in one of them. +pub const SCOPE : [2]str = ["specs/skills", "specs/crons"]; +; Spec fields a bundle entry may carry. SUMMARY maps to SUMMARY_EN, NAME to NAME. +pub const FIELDS : [2]str = ["SUMMARY", "NAME"]; +pub const BUNDLE_REPO : str = "trinity"; +pub const BUNDLE_PATH : str = "apps/website/i18n/agents.ru.json"; +pub const BUNDLE_FORMAT : str = "json"; +; Bundle entries are keyed by the spec's ID constant. +pub const KEY : str = "ID"; +; What the site shows when an entry or a field has no translation. +pub const FALLBACK : str = "en"; +; false: a spec with no translation is a warning, not a build failure. +pub const COVERAGE_REQUIRED : bool = false; +; false: a bundle entry whose ID matches no spec in SCOPE fails the build. +pub const ORPHANS_ALLOWED : bool = false; +pub const ENABLED : bool = true; diff --git a/apps/website/public/t27/files/specs/skills/README.md b/apps/website/public/t27/files/specs/skills/README.md new file mode 100644 index 0000000000..f894f1cc5a --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/README.md @@ -0,0 +1,51 @@ +# specs/skills — skills as first-class `.t27` specs + +One file per Claude Code skill the site publishes (`apps/website/public/skills/manifest.json`). +The `.t27` file is the source of truth for the skill card on t27.ai; the JSON the site +serves (`public/skills/spec-skills.json`) is generated from these files by the real +compiler (`t27_compiler.wasm`), never by a regex. + +File name: `-.t27`; module name: `skill_`. + +## Schema (every constant is `pub const`) + +| constant | type | meaning | +|---------------|-----------|-------------------------------------------------------------------------| +| `KIND` | `str` | always `"skill"` | +| `ID` | `str` | `/`; must equal the id in the code manifest | +| `NAME` | `str` | display name (frontmatter `name`, else the directory) | +| `REPO` | `str` | short repo name (`t27`, `trinity`, …) | +| `SOURCE` | `str` | the skill file inside its directory (`SKILL.md` / `skill.md`) | +| `SUMMARY_EN` | `str` | one-paragraph summary (English) | +| `COMMAND` | `str` | how it is invoked (`/name argument-hint`); `""` when nothing is declared | +| `SPECS` | `[N]str` | `.t27` paths the skill stands on; `[0]str = []` when none is declared | +| `TAGS` | `[N]str` | short topical tags | +| `ENABLED` | `bool` | whether the skill is offered | +| `TIMEOUT_MIN` | `u16` | budget in minutes (30 = default, nothing declared in SKILL.md) | + +## Witness labels the site derives + +- `spec+code` — a spec here **and** the skill exists in the code manifest. +- `spec-only` — a spec here with no matching skill in code (warn). +- `code-only` — a skill in code with no spec here (listed, not invented). + +A cron spec (`../crons/`) names the skills it launches in its `RUNS`; the site +shows that link in both directions (`runBy` on the skill card). + +## Language + +Specs are English-only (t27 `LANG-EN`: `bootstrap/build.rs` fails the build on any +Cyrillic under `specs/`). There is no `SUMMARY_RU`. Translations are connected +through a spec: `specs/i18n/agents-.t27` (see `specs/i18n/README.md`) +declares the locale, the spec directories it covers (`SCOPE`), the fields a bundle +may translate (`FIELDS`: `SUMMARY`, `NAME`) and the bundle file that carries the +text (`BUNDLE_REPO`/`BUNDLE_PATH`, keyed by `ID`). The Russian layer is +`specs/i18n/agents-ru.t27` -> `trinity:apps/website/i18n/agents.ru.json`. The site's +generator discovers every `specs/i18n/*.t27`, loads each bundle, fails on an entry +whose `ID` matches no spec (`ORPHANS_ALLOWED = false`) and emits `summary`/`name` +as `{en, ...}` plus an `i18n` list with the coverage per catalog. A spec +with no translation falls back to English (`FALLBACK = "en"`). + +Validity: every file must pass `typecheck.ok === true` with empty +`discarded` / `lexerDiscarded` / `swallowed` under the vendored wasm compiler — +the site's `scripts/agents-from-specs.mjs` fails the build otherwise. diff --git a/apps/website/public/t27/files/specs/skills/t27-measure-corpus.t27 b/apps/website/public/t27/files/specs/skills/t27-measure-corpus.t27 new file mode 100644 index 0000000000..8a943e757d --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/t27-measure-corpus.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-measure-corpus.t27 -- skill t27/measure-corpus +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/measure-corpus). +; The skill body lives in gHashTag/t27 at .claude/skills/measure-corpus/SKILL.md +; (vendored copy: apps/website/public/skills/files/t27/measure-corpus/SKILL.md, sha256 b8fb7503f660...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_measure_corpus; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/measure-corpus"; +pub const NAME : str = "MEASURE THE CORPUS"; +pub const REPO : str = "t27"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Pick the right instrument for a claim about the t27 spec corpus, and avoid the specific traps that have produced wrong published numbers. Use before quoting any figure about specs, errors, or gates."; +; The frontmatter name is a display title, not a slash command, so no invocation is declared. +pub const COMMAND : str = ""; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["corpus", "measurement", "honesty"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/t27-phi-loop.t27 b/apps/website/public/t27/files/specs/skills/t27-phi-loop.t27 new file mode 100644 index 0000000000..5483ca7a60 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/t27-phi-loop.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-phi-loop.t27 -- skill t27/phi-loop +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/phi-loop). +; The skill body lives in gHashTag/t27 at .claude/skills/phi-loop/SKILL.md +; (vendored copy: apps/website/public/skills/files/t27/phi-loop/SKILL.md, sha256 d8af7f6a9cbc...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_phi_loop; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/phi-loop"; +pub const NAME : str = "PHI LOOP"; +pub const REPO : str = "t27"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Execute 9-phase spec-first development workflow (Issue → Spec → TDD → Code → Gen → Seal → Verify → Land → Learn)"; +; The frontmatter name is a display title, not a slash command, so no invocation is declared. +pub const COMMAND : str = ""; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["workflow", "spec-first", "tdd"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/t27-tri-pipeline.t27 b/apps/website/public/t27/files/specs/skills/t27-tri-pipeline.t27 new file mode 100644 index 0000000000..0f7324bcd1 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/t27-tri-pipeline.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-tri-pipeline.t27 -- skill t27/tri-pipeline +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/tri-pipeline). +; The skill body lives in gHashTag/t27 at .claude/skills/tri-pipeline/SKILL.md +; (vendored copy: apps/website/public/skills/files/t27/tri-pipeline/SKILL.md, sha256 b4bfc290b86a...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_tri_pipeline; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/tri-pipeline"; +pub const NAME : str = "TRI Pipeline"; +pub const REPO : str = "t27"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Execute tri commands (gen, test, verify, seal, verdict) for spec-first development"; +; The frontmatter name is a display title, not a slash command, so no invocation is declared. +pub const COMMAND : str = ""; +; Specs this skill stands on, as declared (frontmatter or the site's bindings.json). +pub const SPECS : [1]str = ["specs/pipeline/e2e_test.t27"]; +pub const TAGS : [3]str = ["tri", "pipeline", "verify"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/t27-tri.t27 b/apps/website/public/t27/files/specs/skills/t27-tri.t27 new file mode 100644 index 0000000000..8443fc51b5 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/t27-tri.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-tri.t27 -- skill t27/tri +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/tri). +; The skill body lives in gHashTag/t27 at .claude/skills/tri/skill.md +; (vendored copy: apps/website/public/skills/files/t27/tri/skill.md, sha256 8fb4d4796c2d...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_tri; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/tri"; +pub const NAME : str = "tri"; +pub const REPO : str = "t27"; +pub const SOURCE : str = "skill.md"; +pub const SUMMARY_EN : str = "This skill should be used when user asks to \"tri skill begin\", \"PHI LOOP\", \"edit .t27 spec\", \"seal hash\", \"tri gen\", \"tri test\", \"tri verdict\", \"tri experience save\", \"tri notebook query\", \"tri notebook wrapup\", or any task requiring spe..."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/tri"; +; Specs this skill stands on, as declared (frontmatter or the site's bindings.json). +pub const SPECS : [1]str = ["compiler/skill/registry.t27"]; +pub const TAGS : [3]str = ["tri", "workflow", "seal"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/t27-wave-audit.t27 b/apps/website/public/t27/files/specs/skills/t27-wave-audit.t27 new file mode 100644 index 0000000000..200ac93ffb --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/t27-wave-audit.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-wave-audit.t27 -- skill t27/wave-audit +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/wave-audit). +; The skill body lives in gHashTag/t27 at .claude/skills/wave-audit/SKILL.md +; (vendored copy: apps/website/public/skills/files/t27/wave-audit/SKILL.md, sha256 4cd0dd22105a...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_wave_audit; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/wave-audit"; +pub const NAME : str = "Wave Audit — Comprehensive Project Analysis"; +pub const REPO : str = "t27"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Full-spectrum audit of t27 project: GitHub issues analysis, weakness identification, SOTA research review, decomposed plan, and implementation of critical fixes."; +; The skill file has no frontmatter, so it declares no invocation. +pub const COMMAND : str = ""; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["audit", "issues", "plan"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/t27-wrap-up.t27 b/apps/website/public/t27/files/specs/skills/t27-wrap-up.t27 new file mode 100644 index 0000000000..8fed732fd1 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/t27-wrap-up.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-wrap-up.t27 -- skill t27/wrap-up +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/wrap-up). +; The skill body lives in gHashTag/t27 at .claude/skills/wrap-up/skill.md +; (vendored copy: apps/website/public/skills/files/t27/wrap-up/skill.md, sha256 a4a9b4fd9d02...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_t27_wrap_up; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/wrap-up"; +pub const NAME : str = "wrap-up"; +pub const REPO : str = "t27"; +pub const SOURCE : str = "skill.md"; +pub const SUMMARY_EN : str = "Format and upload session wrap-up to NotebookLM for persistent semantic memory"; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/wrap-up"; +; Specs this skill stands on, as declared (frontmatter or the site's bindings.json). +pub const SPECS : [1]str = ["specs/automation/wrapup-auto.t27"]; +pub const TAGS : [3]str = ["memory", "notebooklm", "session"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-blog-post.t27 b/apps/website/public/t27/files/specs/skills/trinity-blog-post.t27 new file mode 100644 index 0000000000..a51e59eb18 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-blog-post.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-blog-post.t27 -- skill trinity/blog-post +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/blog-post). +; The skill body lives in gHashTag/trinity at .claude/skills/blog-post/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/blog-post/SKILL.md, sha256 2cb755fd4a60...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_blog_post; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/blog-post"; +pub const NAME : str = "blog-post"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Write and publish a post to the t27.ai blog. Covers where the blog actually lives (not where it looks like it lives), the Post schema with its required receipts and openQuestions, the honesty rules the data file itself imposes, and the b..."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/blog-post"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["blog", "publish", "website"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-board-sync.t27 b/apps/website/public/t27/files/specs/skills/trinity-board-sync.t27 new file mode 100644 index 0000000000..471f0039f2 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-board-sync.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-board-sync.t27 -- skill trinity/board-sync +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/board-sync). +; The skill body lives in gHashTag/trinity at .claude/skills/board-sync/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/board-sync/SKILL.md, sha256 f4bfa63737cf...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_board_sync; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/board-sync"; +pub const NAME : str = "board"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "GitHub Project Board auto-sync — fill empty fields, sync labels with board status, audit field completeness. Keeps board as realtime source of truth."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/board [sync|audit|fix]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["github", "board", "labels"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-cloud.t27 b/apps/website/public/t27/files/specs/skills/trinity-cloud.t27 new file mode 100644 index 0000000000..37299089b4 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-cloud.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-cloud.t27 -- skill trinity/cloud +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/cloud). +; The skill body lives in gHashTag/trinity at .claude/skills/cloud/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/cloud/SKILL.md, sha256 53aa338dddc4...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_cloud; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/cloud"; +pub const NAME : str = "cloud"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Show Trinity Cloud Dev dashboard — containers, training farm, issues, PRs, costs. Use when checking agent swarm status, cloud containers, or CI/CD pipeline."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/cloud [status|agents|events|full]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["dashboard", "railway", "agents"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-doctor.t27 b/apps/website/public/t27/files/specs/skills/trinity-doctor.t27 new file mode 100644 index 0000000000..96bdf3a12c --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-doctor.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-doctor.t27 -- skill trinity/doctor +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/doctor). +; The skill body lives in gHashTag/trinity at .claude/skills/doctor/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/doctor/SKILL.md, sha256 68efdf793010...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_doctor; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/doctor"; +pub const NAME : str = "doctor"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "HEALER — diagnose, heal, commit dirty files, monitor junk & docs & duplicates, report honestly. Every loop = action + proof."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/doctor [quick|full|scan|junk|docs|dupes] [lang:ru|en]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["health", "repo", "report"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-farm-garden.t27 b/apps/website/public/t27/files/specs/skills/trinity-farm-garden.t27 new file mode 100644 index 0000000000..514077e888 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-farm-garden.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-farm-garden.t27 -- skill trinity/farm-garden +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/farm-garden). +; The skill body lives in gHashTag/trinity at .claude/skills/farm-garden/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/farm-garden/SKILL.md, sha256 672148988575...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_farm_garden; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/farm-garden"; +pub const NAME : str = "Garden — Quantum Gardener for Trinity Farm"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Autonomous farm population manager. Monitors, evolves, and recycles HSLM training workers."; +; The skill file has no frontmatter, so it declares no invocation. +pub const COMMAND : str = ""; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["farm", "hslm", "workers"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-fpga-synth.t27 b/apps/website/public/t27/files/specs/skills/trinity-fpga-synth.t27 new file mode 100644 index 0000000000..d7d39594f4 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-fpga-synth.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-fpga-synth.t27 -- skill trinity/fpga-synth +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/fpga-synth). +; The skill body lives in gHashTag/trinity at .claude/skills/fpga-synth/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/fpga-synth/SKILL.md, sha256 de3915e66cda...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_fpga_synth; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/fpga-synth"; +pub const NAME : str = "fpga-synth"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Run FPGA synthesis pipeline from .tri spec to bitstream. Use when working with Verilog, FPGA, or hardware synthesis."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/fpga-synth "; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["fpga", "verilog", "synthesis"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-god-mode.t27 b/apps/website/public/t27/files/specs/skills/trinity-god-mode.t27 new file mode 100644 index 0000000000..fa7198870e --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-god-mode.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-god-mode.t27 -- skill trinity/god-mode +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/god-mode). +; The skill body lives in gHashTag/trinity at .claude/skills/god-mode/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/god-mode/SKILL.md, sha256 0c48e328ae4c...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_god_mode; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/god-mode"; +pub const NAME : str = "god-mode"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "GOD MODE — Agent monitoring dashboard. Shows swarm status, task queue, pipeline compliance, circuit breakers, git activity, and rule violations."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/god-mode [status|agents|tasks|violations]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["dashboard", "agents", "monitoring"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-implement-issue.t27 b/apps/website/public/t27/files/specs/skills/trinity-implement-issue.t27 new file mode 100644 index 0000000000..5d0838f2d4 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-implement-issue.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-implement-issue.t27 -- skill trinity/implement-issue +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/implement-issue). +; The skill body lives in gHashTag/trinity at .claude/skills/implement-issue/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/implement-issue/SKILL.md, sha256 88ccafaece20...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_implement_issue; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/implement-issue"; +pub const NAME : str = "implement-issue"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Read a GitHub issue, create a branch, implement the solution, and open a PR. Use when given an issue number to implement."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/implement-issue [issue-number]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["github", "issue", "pr"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-queen-hive-visuals.t27 b/apps/website/public/t27/files/specs/skills/trinity-queen-hive-visuals.t27 new file mode 100644 index 0000000000..3f7bff50dd --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-queen-hive-visuals.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-queen-hive-visuals.t27 -- skill trinity/queen-hive-visuals +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/queen-hive-visuals). +; The skill body lives in gHashTag/trinity at .claude/skills/queen-hive-visuals/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/queen-hive-visuals/SKILL.md, sha256 c6d914dc81c8...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_queen_hive_visuals; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/queen-hive-visuals"; +pub const NAME : str = "queen-hive-visuals"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Maintain QUEEN's floating hive, original point-down TRINITY logo, sharp issue displays and matching camera interactions."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/queen-hive-visuals"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["queen", "ui", "3d"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-review-code.t27 b/apps/website/public/t27/files/specs/skills/trinity-review-code.t27 new file mode 100644 index 0000000000..b4e9b7659f --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-review-code.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-review-code.t27 -- skill trinity/review-code +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/review-code). +; The skill body lives in gHashTag/trinity at .claude/skills/review-code/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/review-code/SKILL.md, sha256 e5712744615c...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_review_code; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/review-code"; +pub const NAME : str = "review-code"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Review recent code changes for bugs, style issues, and improvements. Use after writing code or before committing."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/review-code [file-or-branch] (optional)"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [2]str = ["review", "quality"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-run-tests.t27 b/apps/website/public/t27/files/specs/skills/trinity-run-tests.t27 new file mode 100644 index 0000000000..3042b92744 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-run-tests.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-run-tests.t27 -- skill trinity/run-tests +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/run-tests). +; The skill body lives in gHashTag/trinity at .claude/skills/run-tests/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/run-tests/SKILL.md, sha256 7ead75ab36b8...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_run_tests; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/run-tests"; +pub const NAME : str = "run-tests"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Build the project, check for warnings, run all tests, and report results. Quick health check for the codebase."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/run-tests [module] (optional - all, vsa, vm, bsd, hslm, tri-api)"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["tests", "build", "zig"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-scholar.t27 b/apps/website/public/t27/files/specs/skills/trinity-scholar.t27 new file mode 100644 index 0000000000..75d440fef6 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-scholar.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-scholar.t27 -- skill trinity/scholar +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/scholar). +; The skill body lives in gHashTag/trinity at .claude/skills/scholar/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/scholar/SKILL.md, sha256 5c35431008a4...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_scholar; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/scholar"; +pub const NAME : str = "scholar"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Self-Evolving Research Agent — scans web for relevant tech, evaluates findings, proposes improvements. Uses Perplexity Sonar API via MCP."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/scholar [scan|eval|apply|full|report|topic:\"query\"]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["research", "perplexity", "mcp"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-status.t27 b/apps/website/public/t27/files/specs/skills/trinity-status.t27 new file mode 100644 index 0000000000..26e1b22f03 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-status.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-status.t27 -- skill trinity/status +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/status). +; The skill body lives in gHashTag/trinity at .claude/skills/status/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/status/SKILL.md, sha256 e4bc75ff45cd...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_status; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/status"; +pub const NAME : str = "status"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Live system dashboard — 12-dimension ouroboros score, verdict v3, agents, build health, recommendations"; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/status [quick|full] [lang:ru|en]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["dashboard", "ouroboros", "verdict"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-tech-tree.t27 b/apps/website/public/t27/files/specs/skills/trinity-tech-tree.t27 new file mode 100644 index 0000000000..dc1e5cddf5 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-tech-tree.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-tech-tree.t27 -- skill trinity/tech-tree +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/tech-tree). +; The skill body lives in gHashTag/trinity at .claude/skills/tech-tree/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/tech-tree/SKILL.md, sha256 f84c8ea67ab6...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_tech_tree; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/tech-tree"; +pub const NAME : str = "tree"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Technology dependency tree — shows issue DAG, prereqs, critical path, blocked/ready tasks. Single source of truth for work order."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/tree [next|path|blocked|update]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["issues", "dag", "planning"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-tri.t27 b/apps/website/public/t27/files/specs/skills/trinity-tri.t27 new file mode 100644 index 0000000000..50942355c5 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-tri.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-tri.t27 -- skill trinity/tri +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/tri). +; The skill body lives in gHashTag/trinity at .claude/skills/tri/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/tri/SKILL.md, sha256 4c38d1005219...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_tri; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/tri"; +pub const NAME : str = "tri"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "TRI status dashboard — agent thought mode by default, /tri full for visual dashboard"; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/tri [short] [full] [audit] [coverage] [lang:ru|en] [humor|prof|toxic|zen]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["tri", "dashboard", "status"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-trinity-test.t27 b/apps/website/public/t27/files/specs/skills/trinity-trinity-test.t27 new file mode 100644 index 0000000000..6cf8fba212 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-trinity-test.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-trinity-test.t27 -- skill trinity/trinity-test +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/trinity-test). +; The skill body lives in gHashTag/trinity at .claude/skills/trinity-test/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/trinity-test/SKILL.md, sha256 648c7f095f0b...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_trinity_test; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/trinity-test"; +pub const NAME : str = "trinity-test"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Run Trinity test suites and analyze results. Use for testing VSA, VM, Firebird, WASM, or full test suite."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/trinity-test [module] (vsa, vm, firebird, all)"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["tests", "vsa", "vm"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-ux-wave.t27 b/apps/website/public/t27/files/specs/skills/trinity-ux-wave.t27 new file mode 100644 index 0000000000..24a3d51309 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-ux-wave.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-ux-wave.t27 -- skill trinity/ux-wave +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/ux-wave). +; The skill body lives in gHashTag/trinity at .claude/skills/ux-wave/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/ux-wave/SKILL.md, sha256 9690999e8d1e...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_ux_wave; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/ux-wave"; +pub const NAME : str = "ux-wave"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Autonomous UX improvement waves — launch 4 parallel agents per wave, audit every 5 waves, commit fixes. For Queen UI chat & network polish."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/ux-wave [wave-number] [audit]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["ux", "agents", "queen"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-vibee-gen.t27 b/apps/website/public/t27/files/specs/skills/trinity-vibee-gen.t27 new file mode 100644 index 0000000000..9877c04083 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-vibee-gen.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-vibee-gen.t27 -- skill trinity/vibee-gen +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/vibee-gen). +; The skill body lives in gHashTag/trinity at .claude/skills/vibee-gen/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/vibee-gen/SKILL.md, sha256 fc01e69f1395...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_vibee_gen; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/vibee-gen"; +pub const NAME : str = "vibee-gen"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Generate Zig or Verilog code from VIBEE specifications. Use when creating or updating .tri specs and regenerating code."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/vibee-gen "; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["codegen", "zig", "verilog"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-vsa-verify.t27 b/apps/website/public/t27/files/specs/skills/trinity-vsa-verify.t27 new file mode 100644 index 0000000000..cb0783e43e --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-vsa-verify.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-vsa-verify.t27 -- skill trinity/vsa-verify +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/vsa-verify). +; The skill body lives in gHashTag/trinity at .claude/skills/vsa-verify/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/vsa-verify/SKILL.md, sha256 8b9c4518b1bc...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_vsa_verify; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/vsa-verify"; +pub const NAME : str = "vsa-verify"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Verify VSA (Vector Symbolic Architecture) mathematical proofs and operations. Use for math verification, bind/unbind/bundle testing, phi identity checks."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/vsa-verify [operation or proof to verify]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["vsa", "math", "verify"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/public/t27/files/specs/skills/trinity-wave.t27 b/apps/website/public/t27/files/specs/skills/trinity-wave.t27 new file mode 100644 index 0000000000..489e76ba12 --- /dev/null +++ b/apps/website/public/t27/files/specs/skills/trinity-wave.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/trinity-wave.t27 -- skill trinity/wave +; Source of truth for the skill card on t27.ai (#/skills?skill=trinity/wave). +; The skill body lives in gHashTag/trinity at .claude/skills/wave/SKILL.md +; (vendored copy: apps/website/public/skills/files/trinity/wave/SKILL.md, sha256 ecf06fdfa467...). +; The JSON the site serves is generated from this file by the real compiler; +; nothing below is parsed with a regex. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_trinity_wave; + +pub const KIND : str = "skill"; +pub const ID : str = "trinity/wave"; +pub const NAME : str = "wave"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "Farm wave management — create Railway accounts, spawn workers, deploy configs, track wave progress. Use for scaling training farm."; +; Invocation, from the skill's own frontmatter (name + argument-hint). +pub const COMMAND : str = "/wave [status|spawn |deploy|progress]"; +; No spec is declared for this skill yet; the Skill Explorer lists it as unbound. +pub const SPECS : [0]str = []; +pub const TAGS : [3]str = ["farm", "railway", "scaling"]; +pub const ENABLED : bool = true; +; Default budget; SKILL.md declares no timeout of its own. +pub const TIMEOUT_MIN : u16 = 30; diff --git a/apps/website/qa/agents-spec-contract.mjs b/apps/website/qa/agents-spec-contract.mjs new file mode 100644 index 0000000000..13f0c67a9c --- /dev/null +++ b/apps/website/qa/agents-spec-contract.mjs @@ -0,0 +1,232 @@ +// The spec-first skills and crons catalogs, checked against what they claim. +// +// public/skills/spec-skills.json and public/crons/spec-crons.json are written +// by scripts/agents-from-specs.mjs from the .t27 files under +// public/t27/files/specs/{skills,crons}/ -- through the real compiler, not a +// regex. This gate asks the questions a generated file can still fail: is the +// committed JSON what the specs produce today (no stale output, no hand edit); +// does every entry point at a file whose bytes hash to what it says; does every +// witness label agree with the code catalog it names; does every RUNS link +// resolve both ways; and does the public page carry nothing shaped like a +// secret. It also pins the management-strip facts the page derives -- the +// canonical edit URL and the "run now" target per host -- so the buttons in the +// Explorer cannot silently point somewhere the spec does not. +// +// node --experimental-strip-types qa/agents-spec-contract.mjs + +import assert from 'node:assert/strict' +import { createHash } from 'node:crypto' +import { existsSync, readFileSync, readdirSync } from 'node:fs' +import { join } from 'node:path' +import { generate, SKILLS_OUT, CRONS_OUT, HOSTS, CONTROLS, ON_FAILURE, I18N_SPEC_DIR, I18N_FIELD_SOURCE, REPO_ROOT } from '../scripts/agents-from-specs.mjs' +import { canonicalSpecEditUrl, vendoredSpecUrl, specSlug } from '../src/lib/agentSpecs.ts' +import { MODULES } from '../src/lib/queenModules.ts' +import { HUD_VIEWS } from '../src/components/queenHud.ts' + +const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex') +const skills = JSON.parse(readFileSync(SKILLS_OUT, 'utf8')) +const crons = JSON.parse(readFileSync(CRONS_OUT, 'utf8')) +const skillsCode = JSON.parse(readFileSync('public/skills/manifest.json', 'utf8')) +const cronsCode = JSON.parse(readFileSync('public/crons/manifest.json', 'utf8')) +const t27Manifest = JSON.parse(readFileSync('public/t27/manifest.json', 'utf8')) +const corpusPaths = new Set(t27Manifest.specs.map((s) => s.path)) + +// 1. The committed output is what the specs produce today. +const fresh = await generate({ generatedAt: skills.generatedAt }) +assert.deepEqual(fresh.problems, [], 'the generator reports problems:\n' + fresh.problems.join('\n')) +assert.equal(skills.contentSha256, fresh.skills.contentSha256, `${SKILLS_OUT} is stale or hand-edited; run node scripts/agents-from-specs.mjs`) +assert.equal(crons.contentSha256, fresh.crons.contentSha256, `${CRONS_OUT} is stale or hand-edited; run node scripts/agents-from-specs.mjs`) +assert.equal(skills.compilerWasmSha256, sha256(readFileSync('public/t27/t27_compiler.wasm')), 'the catalog names a compiler other than the vendored one') +assert.equal(crons.compilerWasmSha256, skills.compilerWasmSha256) + +// 2. Nothing shaped like a secret on a public page. +const FORBIDDEN = [ + { name: 'telegram id', re: /\b\d{9,10}\b/ }, + { name: 'IPv4 address', re: /\b(?:\d{1,3}\.){3}\d{1,3}\b/ }, + { name: 'bearer token', re: /Bearer\s+[A-Za-z0-9._-]{10,}/ }, + { name: 'bot token', re: /\b\d{8,10}:[A-Za-z0-9_-]{35}\b/ }, + { name: 'url credential', re: /:\/\/[^\s/:@]+:[^\s@]+@/ }, + { name: 'github token', re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/ }, +] +for (const [name, text] of [['spec-skills', JSON.stringify(skills)], ['spec-crons', JSON.stringify(crons)]]) { + for (const f of FORBIDDEN) { + const hit = f.re.exec(text) + assert.ok(!hit, `${name} carries something shaped like a ${f.name} (${hit ? hit[0].slice(0, 4) : ''}…)`) + } +} + +// 3. Every entry: a real file, the right bytes, an honest witness. +const WITNESS = new Set(['spec+code', 'spec-only']) +const HEALTH = new Set(['ok', 'warn', 'fail']) +const CYRILLIC = /[\u0400-\u04ff]/ + +// Language policy: a .t27 spec is English-only (t27 LANG-EN; bootstrap/build.rs +// on gHashTag/t27 fails on any Cyrillic under specs/). Every other locale is +// connected through a contract spec in specs/i18n/ (checked in section 6); the +// entry itself carries `en` from the spec plus whatever the loaded bundles gave. +function assertSummary(e, locales) { + const file = join('public/t27/files', e.specPath) + assert.ok(!CYRILLIC.test(readFileSync(file, 'utf8')), `${e.specPath}: Cyrillic in a .t27 spec (LANG-EN)`) + assert.ok(!('SUMMARY_RU' in e.fields), `${e.id}: SUMMARY_RU must not be a spec constant`) + assert.ok(!('ruSource' in e), `${e.id}: ruSource is gone; locales come from specs/i18n/*.t27`) + assert.equal(e.summary.en, e.fields.SUMMARY_EN) + assert.equal(e.name.en, e.fields.NAME) + assert.ok(e.summary.en.length > 10, `${e.id}: SUMMARY_EN is missing`) + for (const field of ['summary', 'name']) { + for (const k of Object.keys(e[field])) assert.ok(k === 'en' || locales.has(k), `${e.id}: ${field}.${k} has no contract spec in ${I18N_SPEC_DIR}`) + } + if (e.summary.ru) assert.ok(e.summary.ru.length > 10 && CYRILLIC.test(e.summary.ru), `${e.id}: summary.ru is not Russian`) +} +const localesOf = (cat) => new Set(cat.i18n.map((l) => l.locale)) +const skillIds = new Set(skills.skills.map((s) => s.id)) +const cronIds = new Set(crons.crons.map((c) => c.id)) +assert.equal(skillIds.size, skills.skills.length, 'duplicate skill ids') +assert.equal(cronIds.size, crons.crons.length, 'duplicate cron ids') +const codeSkillIds = new Set(skillsCode.skills.map((s) => s.id)) +const codeCronIds = new Set(cronsCode.crons.map((c) => c.id)) + +for (const s of skills.skills) { + const file = join('public/t27/files', s.specPath) + assert.ok(existsSync(file), `${s.id}: ${s.specPath} is not in the vendored corpus`) + assert.equal(sha256(readFileSync(file)), s.sha256, `${s.id}: the spec bytes changed under the catalog`) + assert.equal(s.typecheckOk, true) + assert.equal(s.discarded, 0) + assert.ok(WITNESS.has(s.witness)) + assert.equal(s.witness, codeSkillIds.has(s.id) ? 'spec+code' : 'spec-only', `${s.id}: witness disagrees with the code catalog`) + assert.ok(HEALTH.has(s.health)) + if (s.witness === 'spec-only') assert.notEqual(s.health, 'ok', `${s.id}: spec-only must not read ok`) + assert.equal(s.inSpecCorpus, corpusPaths.has(s.specPath), `${s.id}: inSpecCorpus disagrees with public/t27/manifest.json`) + assert.equal(s.fields.ID, s.id) + assert.equal(s.fields.KIND, 'skill') + assertSummary(s, localesOf(skills)) + for (const id of s.runBy) assert.ok(cronIds.has(id), `${s.id}: runBy names ${id}, which has no cron spec`) + for (const id of s.runBy) { + const cron = crons.crons.find((c) => c.id === id) + assert.ok(cron.runs.includes(s.id), `${s.id}: runBy ${id} but that cron's RUNS does not name it`) + } + // Management strip facts. + assert.equal(canonicalSpecEditUrl(s.specPath), `https://github.com/gHashTag/t27/edit/master/${s.specPath}`) + assert.equal(vendoredSpecUrl(s.specPath), `https://github.com/gHashTag/trinity/blob/main/apps/website/public/t27/files/${s.specPath}`) + assert.equal(specSlug(s.specPath), s.specPath.replace(/^specs\/skills\//, '').replace(/\.t27$/, '')) +} +for (const id of skills.codeOnly) assert.ok(codeSkillIds.has(id) && !skillIds.has(id), `codeOnly ${id} is wrong`) +for (const id of codeSkillIds) assert.ok(skillIds.has(id) || skills.codeOnly.includes(id), `${id} is in code but neither spec nor codeOnly`) + +for (const c of crons.crons) { + const file = join('public/t27/files', c.specPath) + assert.ok(existsSync(file), `${c.id}: ${c.specPath} is not in the vendored corpus`) + assert.equal(sha256(readFileSync(file)), c.sha256, `${c.id}: the spec bytes changed under the catalog`) + assert.equal(c.typecheckOk, true) + assert.equal(c.discarded, 0) + assert.ok(WITNESS.has(c.witness)) + assert.equal(c.witness, codeCronIds.has(c.id) ? 'spec+code' : 'spec-only', `${c.id}: witness disagrees with the code catalog`) + assert.ok(HEALTH.has(c.health)) + assert.equal(c.fields.ID, c.id) + assert.equal(c.fields.KIND, 'cron') + assert.ok(HOSTS.includes(c.fields.HOST)) + assert.ok(CONTROLS.includes(c.control)) + assert.equal(c.control, c.fields.CONTROL) + assert.ok(ON_FAILURE.includes(c.fields.ON_FAILURE)) + assertSummary(c, localesOf(crons)) + assert.deepEqual(c.runs, c.fields.RUNS) + assert.equal(c.runsResolved.length, c.runs.length) + for (const r of c.runsResolved) { + assert.equal(r.ok, skillIds.has(r.id), `${c.id}: RUNS ${r.id} resolution is wrong`) + if (!r.ok) assert.equal(c.health, 'fail', `${c.id}: an unresolved RUNS must read fail`) + if (r.ok) assert.ok(skills.skills.find((s) => s.id === r.id).runBy.includes(c.id), `${c.id}: RUNS ${r.id} has no reverse runBy`) + } + if (c.fields.HOST === 'timer') { + assert.ok(Number.isInteger(c.fields.INTERVAL_MS) && c.fields.INTERVAL_MS > 0) + assert.equal(c.fields.SCHEDULE, undefined) + assert.equal(c.control, 'code-only', `${c.id}: a timer cannot be controlled from outside its process`) + } else { + assert.equal(typeof c.fields.SCHEDULE, 'string') + if (c.fields.SCHEDULE === '') assert.ok(c.fields.SCHEDULE_NOTE, `${c.id}: an empty SCHEDULE needs a note`) + } + // A cron that runs no skill must say so in its own words, not stay silent. + if (c.runs.length === 0) assert.ok(c.fields.RUNS_NOTE.length > 0, `${c.id}: empty RUNS without RUNS_NOTE`) + // Management strip facts: where "run now" goes, per host, never invented. + const target = c.runNow + if (c.fields.HOST === 'github-actions') { + assert.equal(target.kind, 'link') + assert.equal(target.url, `https://github.com/gHashTag/${c.fields.REPO}/actions/workflows/${c.fields.SERVICE.split('/').pop()}`) + } else if (c.fields.HOST === 'railway-cron') { + assert.equal(target.kind, 'link') + assert.match(target.url, /^https:\/\/railway\.com\/project\/[0-9a-f-]{36}\/service\/[0-9a-f-]{36}$/) + } else if (c.fields.HOST === 'inngest') { + assert.equal(target.kind, 'link') + assert.equal(target.url, 'https://inngestinngest-production-6a21.up.railway.app') + } else { + assert.equal(target.kind, 'disabled') + } + assert.equal(canonicalSpecEditUrl(c.specPath), `https://github.com/gHashTag/t27/edit/master/${c.specPath}`) +} +for (const id of crons.codeOnly) assert.ok(codeCronIds.has(id) && !cronIds.has(id), `codeOnly ${id} is wrong`) +for (const id of codeCronIds) assert.ok(cronIds.has(id) || crons.codeOnly.includes(id), `${id} is in code but neither spec nor codeOnly`) + +// 4. The counts on the page are sums of the list. +assert.equal(skills.counts.specs, skills.skills.length) +assert.equal(skills.counts.specPlusCode, skills.skills.filter((s) => s.witness === 'spec+code').length) +assert.equal(skills.counts.codeOnly, skills.codeOnly.length) +assert.equal(skills.counts.runBy, skills.skills.filter((s) => s.runBy.length).length) +assert.equal(crons.counts.specs, crons.crons.length) +assert.equal(crons.counts.withRuns, crons.crons.filter((c) => c.runs.length).length) +for (const h of HOSTS) assert.equal(crons.counts.byHost[h], crons.crons.filter((c) => c.fields.HOST === h).length) + +// 5. The Queen knows both views, at the keys the modules list promises. +for (const tab of ['skills', 'crons']) { + const m = MODULES.find((x) => x.tab === tab) + assert.ok(m, `queenModules has no ${tab} entry`) + assert.ok(HUD_VIEWS.includes(tab), `HUD_VIEWS does not include ${tab}`) + assert.equal(String(HUD_VIEWS.indexOf(tab) + 1), m.key, `${tab}: the key on the card (${m.key}) is not the keyboard digit`) + for (const lang of ['en', 'ru']) assert.ok(m[lang].name && m[lang].hint && m[lang].body.length > 40, `${tab}: ${lang} copy missing`) +} +assert.equal(MODULES.length, HUD_VIEWS.length, 'every module is a view and every view a module') +for (const [i, m] of MODULES.entries()) assert.equal(m.key, String(i + 1), `module ${m.tab} carries key ${m.key} at position ${i + 1}`) + +// 6. Translations are connected through .t27 contract specs, never hardcoded. +// Every specs/i18n/*.t27 the corpus carries is in both catalogs' i18n lists +// (the vendored copy is English-only too); each contract's bundle exists at +// the declared path, names the contract back, matches the locale, and every +// entry resolves to a spec whose keys are within FIELDS (ORPHANS_ALLOWED is +// false in the shipped contract). Coverage is printed, not asserted (the +// contract says COVERAGE_REQUIRED false), unless the spec says otherwise. +const i18nDir = join('public/t27/files', I18N_SPEC_DIR) +const i18nSpecFiles = existsSync(i18nDir) ? readdirSync(i18nDir).filter((f) => f.endsWith('.t27')).sort() : [] +assert.ok(i18nSpecFiles.length >= 1, `${I18N_SPEC_DIR}: at least the Russian contract (agents-ru.t27) must exist`) +assert.ok(i18nSpecFiles.includes('agents-ru.t27'), `${I18N_SPEC_DIR}/agents-ru.t27 is the Russian contract`) +for (const f of i18nSpecFiles) assert.ok(!CYRILLIC.test(readFileSync(join(i18nDir, f), 'utf8')), `${I18N_SPEC_DIR}/${f}: Cyrillic in a .t27 spec (LANG-EN)`) +assert.deepEqual(skills.i18n.map((l) => l.spec), i18nSpecFiles.map((f) => `${I18N_SPEC_DIR}/${f}`).sort((a, b) => a.localeCompare(b)), 'skills.i18n lists exactly the contract specs') +assert.deepEqual(crons.i18n.map((l) => l.spec).sort(), skills.i18n.map((l) => l.spec).sort(), 'both catalogs see the same contracts') +const coverageLine = [] +for (const l of skills.i18n) { + const c = crons.i18n.find((x) => x.locale === l.locale) + assert.ok(c, `${l.locale}: contract missing from crons.i18n`) + assert.equal(l.sha256, sha256(readFileSync(join('public/t27/files', l.spec))), `${l.spec}: sha256 in the catalog is not the vendored file`) + assert.ok(existsSync(join(REPO_ROOT, l.bundle)), `${l.spec}: bundle ${l.bundle} does not exist`) + const bundle = JSON.parse(readFileSync(join(REPO_ROOT, l.bundle), 'utf8')) + assert.equal(bundle.$spec, l.spec, `${l.bundle}: $spec must name its contract`) + assert.equal(bundle.locale, l.locale, `${l.bundle}: locale must match the contract`) + for (const field of l.fields) assert.ok(field in I18N_FIELD_SOURCE, `${l.spec}: FIELDS ${field} backed by no spec constant`) + const ids = new Set([...skillIds, ...cronIds]) + for (const [id, entry] of Object.entries(bundle.entries)) { + assert.ok(ids.has(id), `${l.bundle}: orphan entry ${id} (ORPHANS_ALLOWED is false)`) + for (const k of Object.keys(entry)) assert.ok(l.fields.includes(k), `${l.bundle}: ${id}.${k} not in FIELDS ${l.fields.join(',')}`) + } + // Coverage in the catalog equals what the bundle actually gives each entry. + const has = (list) => list.filter((e) => e.summary[l.locale] || e.name[l.locale]).length + assert.equal(l.coverage.n, has(skills.skills), `${l.locale}: skills coverage n mismatch`) + assert.equal(l.coverage.total, skills.skills.length) + assert.equal(c.coverage.n, has(crons.crons), `${l.locale}: crons coverage n mismatch`) + assert.equal(c.coverage.total, crons.crons.length) + assert.equal(l.missing.length, l.coverage.total - l.coverage.n) + coverageLine.push(`${l.locale} via ${l.spec}${l.enabled ? '' : ' (off)'}: skills ${l.coverage.n}/${l.coverage.total}, crons ${c.coverage.n}/${c.coverage.total}, orphans 0`) +} + +console.log( + `agents-spec-contract: skills ${skills.skills.length} (spec+code ${skills.counts.specPlusCode}, spec-only ${skills.counts.specOnly}, code-only ${skills.counts.codeOnly}, run by a cron ${skills.counts.runBy}); ` + + `crons ${crons.crons.length} (spec+code ${crons.counts.specPlusCode}, spec-only ${crons.counts.specOnly}, code-only ${crons.counts.codeOnly}, with RUNS ${crons.counts.withRuns}); ` + + `in vendored corpus manifest: ${skills.skills.filter((s) => s.inSpecCorpus).length + crons.crons.filter((c) => c.inSpecCorpus).length}/${skills.skills.length + crons.crons.length}; ` + + `Queen views ${HUD_VIEWS.length}, modules ${MODULES.length}; ` + + `i18n contracts ${skills.i18n.length} [${coverageLine.join('; ')}]`, +) diff --git a/apps/website/qa/queen-viewport-contract.mjs b/apps/website/qa/queen-viewport-contract.mjs index dad105c988..535815c801 100644 --- a/apps/website/qa/queen-viewport-contract.mjs +++ b/apps/website/qa/queen-viewport-contract.mjs @@ -322,7 +322,7 @@ for (const [w, h] of (DEAD ? SIZES.filter(([w]) => w === 1440 || w === 390) : SI const dead = !!document.querySelector('#stat-status.is-cold'); const rows = document.querySelectorAll('.queen27-sectors-row').length; const data = ${DEAD} ? dead : (live || rows === 6); - return round && data && document.querySelectorAll('.queen27-hud-cmd').length === 5; + return round && data && document.querySelectorAll('.queen27-hud-cmd').length === 8; })()`); if (ready) break; await wait(250); @@ -348,8 +348,9 @@ for (const [w, h] of (DEAD ? SIZES.filter(([w]) => w === 1440 || w === 390) : SI const { fail, counts } = result; const zero = []; if (counts.shell !== 1) zero.push('shell'); - // six command views since the SPECS view joined the panel (origin/main a308aa8bc); the gate follows the panel it counts - if (counts.commands !== 6) zero.push(`commands=${counts.commands}`); + // eight command views: SPECS joined at origin/main a308aa8bc, SKILLS and CRONS with the spec-first agents catalog; + // the gate follows the panel it counts (src/components/queenHud.ts HUD_VIEWS) + if (counts.commands !== 8) zero.push(`commands=${counts.commands}`); if (counts.resources < 7) zero.push(`resources=${counts.resources}`); if (!DEAD && !phone && w > 1100 && counts.sectors !== 6) zero.push(`sectors=${counts.sectors}`); if (DEAD && counts.sectors !== 0) fail.push(`sectors rendered without a board: ${counts.sectors}`); diff --git a/apps/website/scripts/agents-from-specs.mjs b/apps/website/scripts/agents-from-specs.mjs new file mode 100644 index 0000000000..f982843fe4 --- /dev/null +++ b/apps/website/scripts/agents-from-specs.mjs @@ -0,0 +1,583 @@ +#!/usr/bin/env node +// Skills and crons, generated FROM their .t27 specs by the real compiler. +// +// The spec is the source of truth: public/t27/files/specs/skills/*.t27 and +// public/t27/files/specs/crons/*.t27 are read here, analysed with the vendored +// t27_compiler.wasm (the same bytes the browser runs), and the constants are +// taken from the AST it returns. Nothing in a .t27 file is parsed with a regex. +// The code catalogs (public/skills/manifest.json, public/crons/manifest.json) +// are the WITNESS side: a spec whose ID they also list is `spec+code`; a spec +// they do not list is `spec-only` (warn); a catalog entry with no spec is +// `code-only` and is listed, not invented. +// +// node scripts/agents-from-specs.mjs write both JSON files +// node scripts/agents-from-specs.mjs --check regenerate in memory and diff +// +// Outputs: public/skills/spec-skills.json, public/crons/spec-crons.json. +// +// Language: the specs are English-only (t27 LANG-EN; bootstrap/build.rs on +// gHashTag/t27 fails the build on any Cyrillic in specs/). A Cyrillic character +// in a spec is a problem here too, so the vendored copy can never drift from +// what the canonical repository accepts. Translations are CONNECTED THROUGH A +// SPEC: every public/t27/files/specs/i18n/*.t27 (KIND "i18n", one per locale) +// declares the locale, the spec directories in SCOPE, the FIELDS a bundle may +// translate, and the BUNDLE_PATH (repo-relative, in BUNDLE_REPO) of the JSON +// that carries the translated text. This file discovers those specs, loads each +// bundle, checks it against its contract (locale matches, entry keys within +// FIELDS, every entry ID resolves to a spec in SCOPE unless ORPHANS_ALLOWED, +// full coverage if COVERAGE_REQUIRED) and emits summary/name as {en, } +// plus an `i18n` list with the coverage per catalog. No locale is hardcoded. +// +// Compiler quirks this file depends on (verified against the vendored wasm): +// * a string literal comes back as ExprLiteral{value:'"..."'} -- the value +// keeps its quotes, so it is JSON-parsed, not trimmed; +// * an array literal comes back as ExprIdentifier{name:'["a","b"]'} -- the +// whole literal in the identifier's name, again JSON-parseable; +// * the JSON the wasm returns carries every non-ASCII byte as a separate +// char code < 256 (UTF-8 bytes read as Latin-1). `decodeBytes` undoes that +// only when every char code fits a byte, so already-correct text is left +// alone. Round-trip is exact for Cyrillic and the ✓ ⟲ ◷ glyphs; +// * `typecheck.ok` from this wasm is NECESSARY, NOT SUFFICIENT: it stays true +// for `pub const X : str = 5;`, for `[2]str = ["a"]` and for a u16 of +// 70000 (measured, see the test file). So the gate that actually holds is +// `checkSchema` below -- every constant's annotation, value shape, array +// length and integer range are checked here, from the AST, and a spec is +// only "ok" when both the compiler and the schema accept it. + +import { createHash } from 'node:crypto' +import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +export const SITE = resolve(dirname(fileURLToPath(import.meta.url)), '..') +export const SKILL_SPEC_DIR = 'specs/skills' +export const CRON_SPEC_DIR = 'specs/crons' +const CORPUS = 'public/t27/files' +const WASM = 'public/t27/t27_compiler.wasm' +export const SKILLS_OUT = 'public/skills/spec-skills.json' +export const CRONS_OUT = 'public/crons/spec-crons.json' +export const I18N_SPEC_DIR = 'specs/i18n' +// Repo root of gHashTag/trinity (BUNDLE_PATH is repo-relative). +export const REPO_ROOT = resolve(SITE, '..', '..') +export const VERSION = 1 + +export const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex') + +// --------------------------------------------------------------------------- +// The compiler. +// --------------------------------------------------------------------------- +export async function loadCompiler(wasmBytes) { + const { instance } = await WebAssembly.instantiate(wasmBytes, {}) + const w = instance.exports + return function analyze(source) { + const bytes = new TextEncoder().encode(source) + const inPtr = w.t27_alloc(bytes.length) + new Uint8Array(w.memory.buffer, inPtr, bytes.length).set(bytes) + const outPtr = w.t27_analyze(inPtr, bytes.length) + const len = new DataView(w.memory.buffer).getUint32(outPtr, true) + const json = new TextDecoder().decode(new Uint8Array(w.memory.buffer, outPtr + 4, len)) + w.t27_free(outPtr, 4 + len) + return JSON.parse(json) + } +} + +export function decodeBytes(text) { + if (typeof text !== 'string') return text + let allBytes = true + for (const ch of text) { + if (ch.charCodeAt(0) > 255) { allBytes = false; break } + } + if (!allBytes) return text + try { + return new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(text, (c) => c.charCodeAt(0))) + } catch { + return text + } +} + +/** Every `pub const` of the module, from the AST, as `{ name: { type, value } }`. */ +export function constsOf(analysis) { + const out = {} + const decls = (analysis.ast?.children ?? []).filter((n) => n.kind === 'ConstDecl') + for (const d of decls) { + const expr = d.children?.[0] + if (!expr) continue + let value + if (expr.kind === 'ExprLiteral') { + const raw = decodeBytes(expr.value) + if (raw === 'true') value = true + else if (raw === 'false') value = false + else if (/^-?\d+$/.test(raw)) value = Number(raw) + else value = JSON.parse(raw) + } else if (expr.kind === 'ExprIdentifier') { + value = JSON.parse(decodeBytes(expr.name)) + } else { + throw new Error(`${d.name}: unsupported expression kind ${expr.kind}`) + } + if (d.name in out) throw new Error(`duplicate constant ${d.name}`) + out[d.name] = { type: d.type, value, pub: d.pub === true } + } + return out +} + +export function verdictOf(analysis) { + const discarded = (analysis.discarded?.length ?? 0) + (analysis.lexerDiscarded?.length ?? 0) + (analysis.swallowed?.length ?? 0) + return { + typecheckOk: analysis.typecheck?.ok === true, + errors: analysis.typecheck?.errorCount ?? (analysis.typecheck?.errors?.length ?? 0), + discarded, + hirOk: analysis.hir?.ok !== false, + } +} + +// --------------------------------------------------------------------------- +// Schema. +// --------------------------------------------------------------------------- +// Shapes: 'str' | 'bool' | 'arr' (of str) | 'u16' | 'u32'. +const SKILL_REQUIRED = { + KIND: 'str', ID: 'str', NAME: 'str', REPO: 'str', SOURCE: 'str', SUMMARY_EN: 'str', + COMMAND: 'str', SPECS: 'arr', TAGS: 'arr', ENABLED: 'bool', TIMEOUT_MIN: 'u16', +} +const CRON_REQUIRED = { + KIND: 'str', ID: 'str', NAME: 'str', HOST: 'str', REPO: 'str', SERVICE: 'str', SUMMARY_EN: 'str', + TZ: 'str', RUNS: 'arr', RUNS_NOTE: 'str', ENABLED: 'bool', ON_FAILURE: 'str', CONTROL: 'str', +} +const CRON_OPTIONAL = { SCHEDULE: 'str', SCHEDULE_NOTE: 'str', INTERVAL_MS: 'u32', NOTE: 'str' } +const INT_MAX = { u16: 0xffff, u32: 0xffffffff } +export const CYRILLIC = /[\u0400-\u04ff]/ + +// Translation contracts (specs/i18n/agents-.t27). +const I18N_REQUIRED = { + KIND: 'str', LOCALE: 'str', SOURCE_LOCALE: 'str', SCOPE: 'arr', FIELDS: 'arr', BUNDLE_REPO: 'str', BUNDLE_PATH: 'str', + BUNDLE_FORMAT: 'str', KEY: 'str', FALLBACK: 'str', COVERAGE_REQUIRED: 'bool', ORPHANS_ALLOWED: 'bool', ENABLED: 'bool', +} +// Bundle field -> spec constant it translates. +export const I18N_FIELD_SOURCE = { SUMMARY: 'SUMMARY_EN', NAME: 'NAME' } +export const LOCALE_RE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/ + +/** + * Check one i18n spec + its bundle against the contract the spec declares. + * `specsById` maps a spec ID to `{dir, fields}` for every skill and cron spec. + * Returns `{entry, translations, problems}` where `translations` is + * `Map` (empty when disabled or broken) and `entry` is what + * goes into the catalog's `i18n` list. + */ +export function checkI18n(spec, bundle, specsById) { + const problems = [] + const file = spec.path + const f = spec.fields + if (!spec.verdict.typecheckOk || spec.verdict.discarded > 0 || !spec.verdict.hirOk) problems.push(`${file}: compiler verdict not clean (${JSON.stringify(spec.verdict)})`) + problems.push(...checkSchema(spec.consts, I18N_REQUIRED, {}, file)) + if (f.KIND !== 'i18n') problems.push(`${file}: KIND must be "i18n"`) + if (typeof f.LOCALE === 'string' && !LOCALE_RE.test(f.LOCALE)) problems.push(`${file}: LOCALE ${JSON.stringify(f.LOCALE)} is not a BCP-47-shaped tag`) + if (f.SOURCE_LOCALE !== 'en') problems.push(`${file}: SOURCE_LOCALE must be "en" (the specs are English-only)`) + if (f.FALLBACK !== 'en') problems.push(`${file}: FALLBACK must be "en"`) + if (f.KEY !== 'ID') problems.push(`${file}: KEY must be "ID" (bundles are keyed by the spec ID)`) + if (f.BUNDLE_FORMAT !== 'json') problems.push(`${file}: BUNDLE_FORMAT ${JSON.stringify(f.BUNDLE_FORMAT)} is not supported (json)`) + const expectModule = `i18n_${file.replace(/^specs\/i18n\//, '').replace(/\.t27$/, '').replace(/[^A-Za-z0-9]+/g, '_')}` + if (spec.moduleName && spec.moduleName !== expectModule) problems.push(`${file}: module must be ${expectModule}, is ${spec.moduleName}`) + const fields = Array.isArray(f.FIELDS) ? f.FIELDS : [] + for (const name of fields) if (!(name in I18N_FIELD_SOURCE)) problems.push(`${file}: FIELDS names ${JSON.stringify(name)}, which no spec constant backs (${Object.keys(I18N_FIELD_SOURCE).join(', ')})`) + const scope = new Set(Array.isArray(f.SCOPE) ? f.SCOPE : []) + const inScope = [...specsById.entries()].filter(([, v]) => scope.has(v.dir)) + const entry = { + locale: f.LOCALE, spec: file, sha256: spec.sha256, bundle: f.BUNDLE_REPO === 'trinity' ? f.BUNDLE_PATH : `${f.BUNDLE_REPO}:${f.BUNDLE_PATH}`, + enabled: f.ENABLED === true, fields, scope: [...scope], coverage: { n: 0, total: inScope.length }, missing: [], + } + const translations = new Map() + if (f.ENABLED !== true) return { entry, translations, problems } + if (f.BUNDLE_REPO !== 'trinity') { problems.push(`${file}: BUNDLE_REPO ${JSON.stringify(f.BUNDLE_REPO)} -- only a bundle in this repository can be loaded at build time`); return { entry, translations, problems } } + if (!bundle) { problems.push(`${file}: bundle ${f.BUNDLE_PATH} is missing or not JSON`); return { entry, translations, problems } } + if (bundle.$spec !== file) problems.push(`${f.BUNDLE_PATH}: $spec is ${JSON.stringify(bundle.$spec)}, the contract is ${file}`) + if (bundle.locale !== f.LOCALE) problems.push(`${f.BUNDLE_PATH}: locale ${JSON.stringify(bundle.locale)} does not match the spec's LOCALE ${JSON.stringify(f.LOCALE)}`) + const entries = bundle.entries && typeof bundle.entries === 'object' && !Array.isArray(bundle.entries) ? bundle.entries : null + if (!entries) { problems.push(`${f.BUNDLE_PATH}: entries must be an object keyed by spec ID`); return { entry, translations, problems } } + for (const [id, value] of Object.entries(entries)) { + const target = specsById.get(id) + if (!target || !scope.has(target.dir)) { + if (f.ORPHANS_ALLOWED !== true) problems.push(`${f.BUNDLE_PATH}: entry ${JSON.stringify(id)} matches no spec in SCOPE (${[...scope].join(', ')}) and ORPHANS_ALLOWED is false`) + continue + } + if (!value || typeof value !== 'object' || Array.isArray(value)) { problems.push(`${f.BUNDLE_PATH}: entry ${JSON.stringify(id)} must be an object of FIELDS`); continue } + const clean = {} + for (const [k, v] of Object.entries(value)) { + if (!fields.includes(k)) { problems.push(`${f.BUNDLE_PATH}: entry ${JSON.stringify(id)} carries ${JSON.stringify(k)}, which is not in FIELDS (${fields.join(', ')})`); continue } + if (typeof v !== 'string' || !v.trim()) { problems.push(`${f.BUNDLE_PATH}: entry ${JSON.stringify(id)}.${k} must be a non-empty string`); continue } + clean[k] = v + } + if (Object.keys(clean).length) translations.set(id, clean) + } + for (const [id] of inScope) if (!translations.has(id)) entry.missing.push(id) + entry.missing.sort() + entry.coverage.n = inScope.length - entry.missing.length + if (f.COVERAGE_REQUIRED === true && entry.missing.length) problems.push(`${file}: COVERAGE_REQUIRED and ${entry.missing.length} spec(s) have no ${f.LOCALE} entry in ${f.BUNDLE_PATH}: ${entry.missing.slice(0, 5).join(', ')}${entry.missing.length > 5 ? ', ...' : ''}`) + return { entry, translations, problems } +} + +/** `{en, ...}` for one field of one spec from every loaded locale. */ +export function localized(en, id, field, locales) { + const out = { en } + for (const { locale, translations } of locales) { + const t = translations.get(id)?.[field] + if (t) out[locale] = t + } + return out +} +export const HOSTS = ['github-actions', 'inngest', 'railway-cron', 'timer'] +export const CONTROLS = ['github-actions-dispatch', 'railway-dashboard', 'inngest-dashboard', 'code-only'] +export const ON_FAILURE = ['issue', 'log', 'unknown'] + +/** Inngest's own dashboard on Railway; the only outside handle an Inngest function has. */ +export const INNGEST_DASHBOARD_URL = 'https://inngestinngest-production-6a21.up.railway.app' + +/** + * Where "run now" goes for a job, per host. Resolved at build time so the page + * never has to know Railway's ids: they live in scripts/railway-services.json, + * beside the service list the crons manifest is already built from. A timer + * inside a process has no outside handle, and the strip says so. + */ +export function runNowTarget(fields, railway) { + switch (fields.HOST) { + case 'github-actions': { + const file = fields.SERVICE.split('/').pop() ?? fields.SERVICE + return { kind: 'link', via: 'github-actions', url: `https://github.com/gHashTag/${fields.REPO}/actions/workflows/${file}` } + } + case 'railway-cron': { + const svc = (railway?.services ?? []).find((s) => s.name === fields.SERVICE) + if (!railway?.projectId || !svc?.serviceId) return { kind: 'disabled', reason: 'unknown-service' } + return { kind: 'link', via: 'railway', url: `https://railway.com/project/${railway.projectId}/service/${svc.serviceId}` } + } + case 'inngest': + return { kind: 'link', via: 'inngest', url: INNGEST_DASHBOARD_URL } + default: + return { kind: 'disabled', reason: 'timer' } + } +} + +/** Annotation + value agree with the shape; null when they do, a reason when they do not. */ +export function shapeProblem(decl, shape) { + const { type, value } = decl + switch (shape) { + case 'str': + if (type !== 'str') return `annotated ${type}, must be str` + if (typeof value !== 'string') return 'value is not a string literal' + return null + case 'bool': + if (type !== 'bool') return `annotated ${type}, must be bool` + if (typeof value !== 'boolean') return 'value is not true/false' + return null + case 'u16': + case 'u32': + if (type !== shape) return `annotated ${type}, must be ${shape}` + if (!Number.isInteger(value) || value < 0 || value > INT_MAX[shape]) return `value ${value} is not in ${shape} range` + return null + case 'arr': { + if (!Array.isArray(value) || !value.every((x) => typeof x === 'string')) return 'value is not an array of strings' + if (type !== `[${value.length}]str`) return `annotated ${type}, holds ${value.length} string(s)` + return null + } + default: + return `unknown shape ${shape}` + } +} + +export function checkSchema(consts, required, optional, file) { + const problems = [] + for (const [name, shape] of Object.entries(required)) { + if (!(name in consts)) { problems.push(`${file}: missing ${name}`); continue } + const bad = shapeProblem(consts[name], shape) + if (bad) problems.push(`${file}: ${name}: ${bad}`) + if (!consts[name].pub) problems.push(`${file}: ${name} must be pub`) + } + for (const [name, shape] of Object.entries(optional)) { + if (!(name in consts)) continue + const bad = shapeProblem(consts[name], shape) + if (bad) problems.push(`${file}: ${name}: ${bad}`) + if (!consts[name].pub) problems.push(`${file}: ${name} must be pub`) + } + for (const name of Object.keys(consts)) { + if (!(name in required) && !(name in optional)) problems.push(`${file}: unknown constant ${name}`) + } + return problems +} + +const plain = (consts) => Object.fromEntries(Object.entries(consts).map(([k, v]) => [k, v.value])) + +// --------------------------------------------------------------------------- +// Build. `files` = [{ path (corpus-relative), text }], `code` = the two manifests. +// --------------------------------------------------------------------------- +export function analyzeSpecFiles(analyze, files) { + return files.map((f) => { + const analysis = analyze(f.text) + return { path: f.path, text: f.text, sha256: sha256(Buffer.from(f.text, 'utf8')), consts: constsOf(analysis), verdict: verdictOf(analysis), moduleName: analysis.ast?.name ?? null } + }) +} + +export function buildSpecCatalogs({ skillSpecs, cronSpecs, i18nSpecs = [], bundles = new Map(), skillsManifest, cronsManifest, t27Manifest, railway, compilerWasmSha256, generatedAt }) { + const problems = [] + for (const s of [...skillSpecs, ...cronSpecs, ...i18nSpecs]) { + if (s.text !== undefined && CYRILLIC.test(s.text)) problems.push(`${s.path}: Cyrillic in a .t27 spec (t27 LANG-EN; translated text belongs in the bundle a specs/i18n/*.t27 contract points to)`) + } + const corpusPaths = new Set((t27Manifest?.specs ?? []).map((s) => s.path)) + const codeSkills = new Map((skillsManifest?.skills ?? []).map((s) => [s.id, s])) + const codeCrons = new Map((cronsManifest?.crons ?? []).map((c) => [c.id, c])) + + const skills = [] + const seenSkill = new Map() + for (const s of skillSpecs) { + const file = s.path + if (!s.verdict.typecheckOk || s.verdict.discarded > 0 || !s.verdict.hirOk) problems.push(`${file}: compiler verdict not clean (${JSON.stringify(s.verdict)})`) + problems.push(...checkSchema(s.consts, SKILL_REQUIRED, {}, file)) + const f = plain(s.consts) + if (f.KIND !== 'skill') problems.push(`${file}: KIND must be "skill"`) + if (typeof f.ID === 'string') { + if (seenSkill.has(f.ID)) problems.push(`${file}: duplicate skill ID ${f.ID} (also ${seenSkill.get(f.ID)})`) + seenSkill.set(f.ID, file) + } + const expectModule = `skill_${file.replace(/^specs\/skills\//, '').replace(/\.t27$/, '').replace(/[^A-Za-z0-9]+/g, '_')}` + if (s.moduleName && s.moduleName !== expectModule) problems.push(`${file}: module must be ${expectModule}, is ${s.moduleName}`) + const code = codeSkills.get(f.ID) + const specsMissing = (Array.isArray(f.SPECS) ? f.SPECS : []).filter((p) => !corpusPaths.has(p)) + skills.push({ + id: f.ID, + specPath: file, + summary: { en: f.SUMMARY_EN }, + name: { en: f.NAME }, + sha256: s.sha256, + typecheckOk: s.verdict.typecheckOk, + discarded: s.verdict.discarded, + moduleName: s.moduleName, + inSpecCorpus: corpusPaths.has(file), + fields: f, + code: code ? { path: code.path, sha256: code.sha256, health: code.health, link: code.link } : null, + witness: code ? 'spec+code' : 'spec-only', + runBy: [], + health: code ? (specsMissing.length ? 'warn' : 'ok') : 'warn', + messages: [ + ...(code ? [] : [`no skill ${f.ID} in public/skills/manifest.json`]), + ...specsMissing.map((p) => `SPECS names ${p}, which is not in the vendored corpus manifest`), + ], + }) + } + + const crons = [] + const seenCron = new Map() + for (const c of cronSpecs) { + const file = c.path + if (!c.verdict.typecheckOk || c.verdict.discarded > 0 || !c.verdict.hirOk) problems.push(`${file}: compiler verdict not clean (${JSON.stringify(c.verdict)})`) + problems.push(...checkSchema(c.consts, CRON_REQUIRED, CRON_OPTIONAL, file)) + const f = plain(c.consts) + if (f.KIND !== 'cron') problems.push(`${file}: KIND must be "cron"`) + if (typeof f.ID === 'string') { + if (seenCron.has(f.ID)) problems.push(`${file}: duplicate cron ID ${f.ID} (also ${seenCron.get(f.ID)})`) + seenCron.set(f.ID, file) + } + const expectModule = `cron_${file.replace(/^specs\/crons\//, '').replace(/\.t27$/, '').replace(/[^A-Za-z0-9]+/g, '_')}` + if (c.moduleName && c.moduleName !== expectModule) problems.push(`${file}: module must be ${expectModule}, is ${c.moduleName}`) + if (f.HOST && !HOSTS.includes(f.HOST)) problems.push(`${file}: HOST ${f.HOST} is not one of ${HOSTS.join('|')}`) + if (f.CONTROL && !CONTROLS.includes(f.CONTROL)) problems.push(`${file}: CONTROL ${f.CONTROL} is not one of ${CONTROLS.join('|')}`) + if (f.ON_FAILURE && !ON_FAILURE.includes(f.ON_FAILURE)) problems.push(`${file}: ON_FAILURE ${f.ON_FAILURE} is not one of ${ON_FAILURE.join('|')}`) + const isTimer = f.HOST === 'timer' + if (isTimer && !('INTERVAL_MS' in f)) problems.push(`${file}: a timer needs INTERVAL_MS`) + if (isTimer && 'SCHEDULE' in f) problems.push(`${file}: a timer has INTERVAL_MS, not SCHEDULE`) + if (!isTimer && !('SCHEDULE' in f)) problems.push(`${file}: a scheduled job needs SCHEDULE`) + if (!isTimer && 'INTERVAL_MS' in f) problems.push(`${file}: only a timer has INTERVAL_MS`) + if (f.SCHEDULE === '' && !f.SCHEDULE_NOTE) problems.push(`${file}: an empty SCHEDULE needs SCHEDULE_NOTE`) + const code = codeCrons.get(f.ID) + const runs = Array.isArray(f.RUNS) ? f.RUNS : [] + const runsResolved = runs.map((id) => ({ id, ok: seenSkill.has(id) })) + const unresolved = runsResolved.filter((r) => !r.ok) + const messages = [ + ...(code ? [] : [`no job ${f.ID} in public/crons/manifest.json`]), + ...unresolved.map((r) => `RUNS names ${r.id}, which has no skill spec`), + ] + let health = 'ok' + if (!code) health = 'warn' + if (unresolved.length) health = 'fail' + // The code catalog knows the schedule it read from the file; when the spec + // disagrees, that is a fact the reader must see, not a build failure. + if (code) { + const codeExpr = code.schedule?.expr ?? null + const codeMs = code.schedule?.everyMs ?? null + if (codeExpr && f.SCHEDULE !== undefined && f.SCHEDULE !== codeExpr) { messages.push(`SCHEDULE ${JSON.stringify(f.SCHEDULE)} differs from the code catalog (${codeExpr})`); if (health === 'ok') health = 'warn' } + if (codeMs && f.INTERVAL_MS !== undefined && f.INTERVAL_MS !== codeMs) { messages.push(`INTERVAL_MS ${f.INTERVAL_MS} differs from the code catalog (${codeMs})`); if (health === 'ok') health = 'warn' } + if (code.kind !== f.HOST) { messages.push(`HOST ${f.HOST} differs from the code catalog (${code.kind})`); if (health === 'ok') health = 'warn' } + } + crons.push({ + id: f.ID, + specPath: file, + summary: { en: f.SUMMARY_EN }, + name: { en: f.NAME }, + sha256: c.sha256, + typecheckOk: c.verdict.typecheckOk, + discarded: c.verdict.discarded, + moduleName: c.moduleName, + inSpecCorpus: corpusPaths.has(file), + fields: f, + code: code ? { where: code.where, sourceUrl: code.sourceUrl ?? null, health: code.health, kind: code.kind, repoPrivate: code.repoPrivate === true } : null, + witness: code ? 'spec+code' : 'spec-only', + runs, + runsResolved, + control: f.CONTROL, + runNow: runNowTarget(f, railway), + health, + messages, + }) + } + + // The reverse link, from the crons' RUNS. + for (const cr of crons) for (const r of cr.runsResolved) { + if (!r.ok) continue + const sk = skills.find((s) => s.id === r.id) + if (sk && !sk.runBy.includes(cr.id)) sk.runBy.push(cr.id) + } + for (const sk of skills) sk.runBy.sort() + + const byId = (a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0) + skills.sort(byId) + crons.sort(byId) + const codeOnlySkills = [...codeSkills.keys()].filter((id) => !seenSkill.has(id)).sort() + const codeOnlyCrons = [...codeCrons.keys()].filter((id) => !seenCron.has(id)).sort() + + // Translations, connected through their contract specs. Every locale comes + // from a specs/i18n/*.t27; nothing here knows which locales exist. + const specsById = new Map() + for (const s of skills) specsById.set(s.id, { dir: SKILL_SPEC_DIR, fields: s.fields }) + for (const c of crons) specsById.set(c.id, { dir: CRON_SPEC_DIR, fields: c.fields }) + const locales = [] + const seenLocale = new Map() + for (const spec of [...i18nSpecs].sort((a, b) => a.path.localeCompare(b.path))) { + const fields = plain(spec.consts) + const r = checkI18n({ ...spec, fields }, bundles.get(fields.BUNDLE_PATH) ?? null, specsById) + problems.push(...r.problems) + if (typeof fields.LOCALE === 'string') { + if (seenLocale.has(fields.LOCALE)) problems.push(`${spec.path}: duplicate LOCALE ${fields.LOCALE} (also ${seenLocale.get(fields.LOCALE)})`) + seenLocale.set(fields.LOCALE, spec.path) + } + locales.push({ locale: fields.LOCALE, entry: r.entry, translations: r.translations, scope: new Set(r.entry.scope) }) + } + const i18nFor = (dir, list) => [...locales].sort((a, b) => String(a.locale).localeCompare(String(b.locale))).filter((l) => l.scope.has(dir)).map((l) => { + const ids = new Set(list.map((e) => e.id)) + const missing = l.entry.missing.filter((id) => ids.has(id)) + const { missing: _all, ...rest } = l.entry + return { ...rest, coverage: { n: list.length - missing.length, total: list.length }, missing } + }) + for (const e of [...skills, ...crons]) { + e.summary = localized(e.fields.SUMMARY_EN, e.id, 'SUMMARY', locales) + e.name = localized(e.fields.NAME, e.id, 'NAME', locales) + } + const base = { version: VERSION, compilerWasmSha256 } + const skillsOut = sortKeys({ + ...base, + generatedAt, + counts: { specs: skills.length, specPlusCode: skills.filter((s) => s.witness === 'spec+code').length, specOnly: skills.filter((s) => s.witness === 'spec-only').length, codeOnly: codeOnlySkills.length, typecheckOk: skills.filter((s) => s.typecheckOk).length, runBy: skills.filter((s) => s.runBy.length).length }, + skills, + codeOnly: codeOnlySkills, + i18n: i18nFor(SKILL_SPEC_DIR, skills), + }) + const cronsOut = sortKeys({ + ...base, + generatedAt, + counts: { specs: crons.length, specPlusCode: crons.filter((c) => c.witness === 'spec+code').length, specOnly: crons.filter((c) => c.witness === 'spec-only').length, codeOnly: codeOnlyCrons.length, typecheckOk: crons.filter((c) => c.typecheckOk).length, withRuns: crons.filter((c) => c.runs.length).length, byHost: Object.fromEntries(HOSTS.map((h) => [h, crons.filter((c) => c.fields.HOST === h).length])) }, + crons, + codeOnly: codeOnlyCrons, + i18n: i18nFor(CRON_SPEC_DIR, crons), + }) + // A hash of everything but the clock, so a checker can compare committed and + // regenerated output without the timestamp getting in the way. + skillsOut.contentSha256 = sha256(JSON.stringify({ ...skillsOut, generatedAt: null })) + cronsOut.contentSha256 = sha256(JSON.stringify({ ...cronsOut, generatedAt: null })) + return { skills: sortKeys(skillsOut), crons: sortKeys(cronsOut), problems } +} + +export function sortKeys(value) { + if (Array.isArray(value)) return value.map(sortKeys) + if (value && typeof value === 'object') { + return Object.fromEntries(Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])) + } + return value +} + +// --------------------------------------------------------------------------- +// The site. +// --------------------------------------------------------------------------- +function readSpecDir(dir) { + const abs = join(SITE, CORPUS, dir) + if (!existsSync(abs)) return [] + return readdirSync(abs) + .filter((f) => f.endsWith('.t27')) + .sort() + .map((f) => ({ path: `${dir}/${f}`, text: readFileSync(join(abs, f), 'utf8') })) +} + +const readJson = (rel) => (existsSync(join(SITE, rel)) ? JSON.parse(readFileSync(join(SITE, rel), 'utf8')) : null) + +export async function generate({ generatedAt } = {}) { + const wasmBytes = readFileSync(join(SITE, WASM)) + const analyze = await loadCompiler(wasmBytes) + const skillSpecs = analyzeSpecFiles(analyze, readSpecDir(SKILL_SPEC_DIR)) + const cronSpecs = analyzeSpecFiles(analyze, readSpecDir(CRON_SPEC_DIR)) + const i18nSpecs = analyzeSpecFiles(analyze, readSpecDir(I18N_SPEC_DIR)) + // Each contract names its bundle; load exactly those, repo-relative. + const bundles = new Map() + for (const s of i18nSpecs) { + const path = plain(s.consts).BUNDLE_PATH + if (typeof path !== 'string' || path.includes('..')) continue + const abs = join(REPO_ROOT, path) + if (existsSync(abs)) { try { bundles.set(path, JSON.parse(readFileSync(abs, 'utf8'))) } catch { bundles.set(path, null) } } + } + const epoch = process.env.SOURCE_DATE_EPOCH + const stamp = generatedAt ?? (epoch ? new Date(Number(epoch) * 1000).toISOString() : new Date().toISOString()) + return buildSpecCatalogs({ + skillSpecs, + cronSpecs, + skillsManifest: readJson('public/skills/manifest.json'), + cronsManifest: readJson('public/crons/manifest.json'), + t27Manifest: readJson('public/t27/manifest.json'), + railway: readJson('scripts/railway-services.json'), + i18nSpecs, + bundles, + compilerWasmSha256: sha256(wasmBytes), + generatedAt: stamp, + }) +} + +function writeAtomic(rel, data) { + const dest = join(SITE, rel) + mkdirSync(dirname(dest), { recursive: true }) + const temp = `${dest}.${process.pid}.tmp` + writeFileSync(temp, `${JSON.stringify(data)}\n`, { flag: 'wx' }) + renameSync(temp, dest) +} + +async function main(argv) { + const check = argv.includes('--check') + const { skills, crons, problems } = await generate() + if (problems.length) { + console.error(`agents-from-specs: ${problems.length} problem(s)`) + for (const p of problems) console.error(' ' + p) + process.exit(1) + } + if (check) { + const prior = { skills: readJson(SKILLS_OUT), crons: readJson(CRONS_OUT) } + const drift = [] + if (prior.skills?.contentSha256 !== skills.contentSha256) drift.push(SKILLS_OUT) + if (prior.crons?.contentSha256 !== crons.contentSha256) drift.push(CRONS_OUT) + if (drift.length) { + console.error(`agents-from-specs: committed output is stale: ${drift.join(', ')} -- run node scripts/agents-from-specs.mjs`) + process.exit(1) + } + console.log(`agents-from-specs: ${SKILLS_OUT} and ${CRONS_OUT} match the specs`) + return + } + writeAtomic(SKILLS_OUT, skills) + writeAtomic(CRONS_OUT, crons) + const s = skills.counts, c = crons.counts + console.log(`agents-from-specs: skills ${s.specs} specs (typecheck ok ${s.typecheckOk}/${s.specs}; spec+code ${s.specPlusCode}, spec-only ${s.specOnly}, code-only ${s.codeOnly}) -> ${SKILLS_OUT}`) + console.log(`agents-from-specs: crons ${c.specs} specs (typecheck ok ${c.typecheckOk}/${c.specs}; spec+code ${c.specPlusCode}, spec-only ${c.specOnly}, code-only ${c.codeOnly}; with RUNS ${c.withRuns}) -> ${CRONS_OUT}`) + for (const l of skills.i18n) console.log(`agents-from-specs: i18n ${l.locale} via ${l.spec} -> ${l.bundle}: skills ${l.coverage.n}/${l.coverage.total}, crons ${crons.i18n.find((x) => x.locale === l.locale)?.coverage.n ?? 0}/${crons.counts.specs}${l.enabled ? '' : ' (disabled)'}`) +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + main(process.argv.slice(2)).catch((e) => { console.error(e); process.exit(1) }) +} diff --git a/apps/website/scripts/agents-from-specs.test.mjs b/apps/website/scripts/agents-from-specs.test.mjs new file mode 100644 index 0000000000..bae10d5c86 --- /dev/null +++ b/apps/website/scripts/agents-from-specs.test.mjs @@ -0,0 +1,339 @@ +// What the spec→catalog generator refuses, and what it lets through. +// +// The interesting cases are the failures: two specs claiming one ID, a cron +// whose RUNS names a skill that has no spec, a spec with no code behind it, a +// spec the compiler does not accept. And the two quirks the generator leans +// on -- array literals arriving as identifiers and UTF-8 arriving as bytes -- +// are pinned here against the REAL wasm so a compiler update that changes +// either shape fails loudly instead of shipping mojibake. +// +// node --test scripts/agents-from-specs.test.mjs + +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { + I18N_SPEC_DIR, SITE, analyzeSpecFiles, checkI18n, buildSpecCatalogs, constsOf, decodeBytes, loadCompiler, runNowTarget, sortKeys, verdictOf, +} from './agents-from-specs.mjs' + +const analyze = await loadCompiler(readFileSync(join(SITE, 'public/t27/t27_compiler.wasm'))) + +const q = JSON.stringify +const skillSrc = (id, { module = 'skill_x', specs = [], summary = 'en', extra = '' } = {}) => `module ${module}; +pub const KIND : str = "skill"; +pub const ID : str = ${q(id)}; +pub const NAME : str = "x"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = ${q(summary)}; +pub const COMMAND : str = "/x"; +pub const SPECS : [${specs.length}]str = ${q(specs)}; +pub const TAGS : [1]str = ["t"]; +pub const ENABLED : bool = true; +pub const TIMEOUT_MIN : u16 = 30; +${extra}` +const cronSrc = (id, { module = 'cron_x', runs = [], host = 'github-actions', schedule = '"0 0 * * *"', extra = '' } = {}) => `module ${module}; +pub const KIND : str = "cron"; +pub const ID : str = ${q(id)}; +pub const NAME : str = "x"; +pub const HOST : str = ${q(host)}; +pub const REPO : str = "trinity"; +pub const SERVICE : str = ".github/workflows/x.yml"; +pub const SUMMARY_EN : str = "en"; +${host === 'timer' ? 'pub const INTERVAL_MS : u32 = 5000;' : `pub const SCHEDULE : str = ${schedule};`} +pub const TZ : str = "UTC"; +pub const RUNS : [${runs.length}]str = ${q(runs)}; +pub const RUNS_NOTE : str = "test"; +pub const ENABLED : bool = true; +pub const ON_FAILURE : str = "log"; +pub const CONTROL : str = "code-only"; +${extra}` + +const files = (dir, ...srcs) => srcs.map((text, i) => ({ path: `${dir}/x${i}.t27`, text })) +const skillsManifest = { skills: [{ id: 'trinity/x', path: 'trinity/x/SKILL.md', sha256: 'a'.repeat(64), health: 'ok', link: 'unbound' }] } +const cronsManifest = { crons: [{ id: 'github-actions/trinity/x', kind: 'github-actions', schedule: { kind: 'cron', expr: '0 0 * * *' }, where: { file: 'w.yml', line: 1 }, health: 'ok' }] } +const build = (skillSpecs, cronSpecs, over = {}) => buildSpecCatalogs({ + skillSpecs: analyzeSpecFiles(analyze, skillSpecs), + cronSpecs: analyzeSpecFiles(analyze, cronSpecs), + skillsManifest, cronsManifest, t27Manifest: { specs: [{ path: 'specs/demos/hello_world.t27' }] }, + compilerWasmSha256: 'w', generatedAt: '2026-01-01T00:00:00.000Z', ...over, +}) + +test('the compiler accepts the schema, including an empty array, and constants come back typed', () => { + const a = analyze(skillSrc('trinity/x', { module: 'skill_x0' })) + const v = verdictOf(a) + assert.equal(v.typecheckOk, true) + assert.equal(v.discarded, 0) + const c = constsOf(a) + assert.equal(c.ID.value, 'trinity/x') + assert.deepEqual(c.SPECS.value, []) + assert.equal(c.ENABLED.value, true) + assert.equal(c.TIMEOUT_MIN.value, 30) + assert.equal(c.TIMEOUT_MIN.type, 'u16') +}) + +test('array literals arrive as identifiers and UTF-8 arrives as bytes; both are undone exactly', () => { + // Specs are English-only (LANG-EN), but English specs still carry non-ASCII + // glyphs -- arrows, check marks, φ -- and those must round-trip exactly. + const a = analyze(skillSrc('trinity/x', { module: 'skill_x0', specs: ['specs/a.t27', 'specs/φ.t27'], summary: 'Issue → Spec ✓ ⟲ ◷ φ' })) + const arr = a.ast.children.find((n) => n.name === 'SPECS').children[0] + assert.equal(arr.kind, 'ExprIdentifier', 'the quirk this generator documents: array literal in an identifier name') + const c = constsOf(a) + assert.deepEqual(c.SPECS.value, ['specs/a.t27', 'specs/φ.t27']) + assert.equal(c.SUMMARY_EN.value, 'Issue → Spec ✓ ⟲ ◷ φ') + assert.equal(decodeBytes('plain ascii'), 'plain ascii') + assert.equal(decodeBytes('уже верно'), 'уже верно', 'text that is already decoded is left alone') +}) + +test('a clean pair is spec+code, healthy, with the reverse link filled in', () => { + const r = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' })), files('specs/crons', cronSrc('github-actions/trinity/x', { module: 'cron_x0', runs: ['trinity/x'] }))) + assert.deepEqual(r.problems, []) + assert.equal(r.skills.skills[0].witness, 'spec+code') + assert.equal(r.crons.crons[0].witness, 'spec+code') + assert.equal(r.crons.crons[0].health, 'ok') + assert.deepEqual(r.crons.crons[0].runsResolved, [{ id: 'trinity/x', ok: true }]) + assert.deepEqual(r.skills.skills[0].runBy, ['github-actions/trinity/x']) + assert.deepEqual(r.skills.codeOnly, []) + assert.equal(r.skills.counts.runBy, 1) +}) + +test('duplicate IDs fail the build', () => { + const r = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' }), skillSrc('trinity/x', { module: 'skill_x1' })), []) + assert.ok(r.problems.some((p) => p.includes('duplicate skill ID trinity/x')), r.problems.join('\n')) +}) + +test('a RUNS entry with no skill spec marks the cron fail, with a message', () => { + const r = build([], files('specs/crons', cronSrc('github-actions/trinity/x', { module: 'cron_x0', runs: ['trinity/ghost'] }))) + assert.deepEqual(r.problems, []) + const c = r.crons.crons[0] + assert.equal(c.health, 'fail') + assert.deepEqual(c.runsResolved, [{ id: 'trinity/ghost', ok: false }]) + assert.ok(c.messages.some((m) => m.includes('trinity/ghost'))) +}) + +test('a spec the code catalog does not know is spec-only and warns; a code entry with no spec is code-only', () => { + const r = build(files('specs/skills', skillSrc('trinity/only-in-spec', { module: 'skill_x0' })), []) + assert.equal(r.skills.skills[0].witness, 'spec-only') + assert.equal(r.skills.skills[0].health, 'warn') + assert.deepEqual(r.skills.codeOnly, ['trinity/x']) + assert.deepEqual(r.crons.codeOnly, ['github-actions/trinity/x']) + assert.equal(r.crons.counts.codeOnly, 1) +}) + +test('the module name must follow the file name', () => { + const r = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_wrong' })), []) + assert.ok(r.problems.some((p) => p.includes('module must be skill_x0'))) +}) + +test('a timer carries INTERVAL_MS, a schedule carries SCHEDULE, never both', () => { + const r = build([], files('specs/crons', + cronSrc('timer/trinity/t', { module: 'cron_x0', host: 'timer', extra: 'pub const SCHEDULE : str = "* * * * *";' }), + cronSrc('github-actions/trinity/x', { module: 'cron_x1', extra: 'pub const INTERVAL_MS : u32 = 1;' }), + )) + assert.ok(r.problems.some((p) => p.includes('x0.t27: a timer has INTERVAL_MS, not SCHEDULE')), r.problems.join('\n')) + assert.ok(r.problems.some((p) => p.includes('x1.t27: only a timer has INTERVAL_MS')), r.problems.join('\n')) +}) + +test('a spec that disagrees with the schedule the code catalog read is shown, as warn', () => { + const r = build([], files('specs/crons', cronSrc('github-actions/trinity/x', { module: 'cron_x0', schedule: '"5 5 * * *"' }))) + assert.deepEqual(r.problems, []) + assert.equal(r.crons.crons[0].health, 'warn') + assert.ok(r.crons.crons[0].messages.some((m) => m.includes('differs from the code catalog'))) +}) + +test('unknown constants, wrong shapes and bad enums are problems, not silently dropped', () => { + const r = build( + files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0', extra: 'pub const EXTRA : str = "x";' })), + files('specs/crons', cronSrc('github-actions/trinity/x', { module: 'cron_x0', extra: '' }).replace('"code-only"', '"telepathy"')), + ) + assert.ok(r.problems.some((p) => p.includes('unknown constant EXTRA'))) + assert.ok(r.problems.some((p) => p.includes('CONTROL telepathy'))) +}) + +test('the wasm typecheck is lenient, so the schema is the gate: wrong annotation, shape, length and range are all problems', () => { + // Measured against the vendored wasm: each of these still gets typecheck.ok === true. + const src = `module skill_x0; +pub const KIND : str = 5; +pub const ID : str = "trinity/x"; +pub const NAME : str = "x"; +pub const REPO : str = "trinity"; +pub const SOURCE : str = "SKILL.md"; +pub const SUMMARY_EN : str = "en"; +pub const COMMAND : str = ; +pub const SPECS : [2]str = ["a"]; +pub const TAGS : [1]str = ["t"]; +pub const ENABLED : bool = "yes"; +pub const TIMEOUT_MIN : u16 = 70000; +` + const a = analyze(src) + assert.equal(a.typecheck.ok, true, 'if this starts failing the compiler got stricter; the schema gate below stays') + const r = build(files('specs/skills', src), []) + const text = r.problems.join('\n') + assert.match(text, /KIND: value is not a string literal/) + assert.match(text, /missing COMMAND/) + assert.match(text, /SPECS: annotated \[2\]str, holds 1 string/) + assert.match(text, /ENABLED: value is not true\/false/) + assert.match(text, /TIMEOUT_MIN: value 70000 is not in u16 range/) +}) + +test('output is deterministic: sorted keys, sorted ids, content hash independent of the clock', () => { + const a = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' })), []) + const b = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' })), [], { generatedAt: '2030-01-01T00:00:00.000Z' }) + assert.equal(a.skills.contentSha256, b.skills.contentSha256) + assert.deepEqual(Object.keys(a.skills), Object.keys(a.skills).slice().sort()) + assert.deepEqual(sortKeys({ b: 1, a: [{ d: 1, c: 2 }] }), { a: [{ c: 2, d: 1 }], b: 1 }) +}) + +test('runNowTarget: every host resolves to the panel that owns it, and a timer to nothing', () => { + const railway = { projectId: 'P', services: [{ name: 'jobsearch-cron', serviceId: 'S' }] } + assert.deepEqual(runNowTarget({ HOST: 'github-actions', REPO: 'trinity', SERVICE: '.github/workflows/site-live-gate.yml' }, railway), { + kind: 'link', via: 'github-actions', url: 'https://github.com/gHashTag/trinity/actions/workflows/site-live-gate.yml', + }) + assert.deepEqual(runNowTarget({ HOST: 'railway-cron', REPO: 'x', SERVICE: 'jobsearch-cron' }, railway), { + kind: 'link', via: 'railway', url: 'https://railway.com/project/P/service/S', + }) + assert.deepEqual(runNowTarget({ HOST: 'railway-cron', REPO: 'x', SERVICE: 'nobody' }, railway), { kind: 'disabled', reason: 'unknown-service' }) + assert.deepEqual(runNowTarget({ HOST: 'railway-cron', REPO: 'x', SERVICE: 'jobsearch-cron' }, null), { kind: 'disabled', reason: 'unknown-service' }) + assert.equal(runNowTarget({ HOST: 'inngest', REPO: 'x', SERVICE: 'y' }, railway).via, 'inngest') + assert.deepEqual(runNowTarget({ HOST: 'timer', REPO: 'x', SERVICE: 'y' }, railway), { kind: 'disabled', reason: 'timer' }) +}) + +test('Cyrillic in a spec is a problem (t27 LANG-EN), even when the compiler accepts the file', () => { + const r = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0', summary: 'Проверка' })), []) + assert.ok(r.problems.some((p) => p.includes('Cyrillic in a .t27 spec')), r.problems.join('\n')) + const clean = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' })), []) + assert.ok(!clean.problems.some((p) => p.includes('Cyrillic'))) +}) + +const i18nSrc = ({ locale = 'ru', module = 'i18n_agents_ru', fields = ['SUMMARY', 'NAME'], scope = ['specs/skills', 'specs/crons'], bundle = 'apps/website/i18n/agents.ru.json', coverageRequired = false, orphans = false, enabled = true, repo = 'trinity', extra = '' } = {}) => `module ${module}; +pub const KIND : str = "i18n"; +pub const LOCALE : str = ${q(locale)}; +pub const SOURCE_LOCALE : str = "en"; +pub const SCOPE : [${scope.length}]str = ${q(scope)}; +pub const FIELDS : [${fields.length}]str = ${q(fields)}; +pub const BUNDLE_REPO : str = ${q(repo)}; +pub const BUNDLE_PATH : str = ${q(bundle)}; +pub const BUNDLE_FORMAT : str = "json"; +pub const KEY : str = "ID"; +pub const FALLBACK : str = "en"; +pub const COVERAGE_REQUIRED : bool = ${coverageRequired}; +pub const ORPHANS_ALLOWED : bool = ${orphans}; +pub const ENABLED : bool = ${enabled}; +${extra}` +const i18nFiles = (...srcs) => srcs.map((text, i) => ({ path: i === 0 ? `${I18N_SPEC_DIR}/agents-ru.t27` : `${I18N_SPEC_DIR}/agents-x${i}.t27`, text })) +const ruBundle = (entries, over = {}) => ({ $spec: 'specs/i18n/agents-ru.t27', locale: 'ru', entries, ...over }) +const withI18n = (i18nSpecs, bundles, over = {}) => build( + files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' })), + files('specs/crons', cronSrc('github-actions/trinity/x', { module: 'cron_x0' })), + { i18nSpecs: analyzeSpecFiles(analyze, i18nSpecs), bundles: new Map(Object.entries(bundles)), ...over }, +) + +test('the i18n contract spec typechecks and comes back typed', () => { + const a = analyze(i18nSrc()) + assert.equal(verdictOf(a).typecheckOk, true) + assert.equal(verdictOf(a).discarded, 0) + const c = constsOf(a) + assert.equal(c.KIND.value, 'i18n') + assert.deepEqual(c.SCOPE.value, ['specs/skills', 'specs/crons']) + assert.equal(c.COVERAGE_REQUIRED.value, false) +}) + +test('a translation reaches the catalog only through its contract spec, as {en, }, with coverage per catalog', () => { + const r = withI18n(i18nFiles(i18nSrc()), { 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: 'Проверка ✓', NAME: 'ИКС' } }) }) + assert.deepEqual(r.problems, []) + assert.deepEqual(r.skills.skills[0].summary, { en: 'en', ru: 'Проверка ✓' }) + assert.deepEqual(r.skills.skills[0].name, { en: 'x', ru: 'ИКС' }) + assert.equal('SUMMARY_RU' in r.skills.skills[0].fields, false) + assert.equal(r.skills.i18n.length, 1) + assert.equal(r.skills.i18n[0].locale, 'ru') + assert.equal(r.skills.i18n[0].spec, 'specs/i18n/agents-ru.t27') + assert.deepEqual(r.skills.i18n[0].coverage, { n: 1, total: 1 }) + // the cron has no entry: en only, coverage 0/1 and its id listed, but no problem (COVERAGE_REQUIRED false) + assert.deepEqual(r.crons.crons[0].summary, { en: 'en' }) + assert.deepEqual(r.crons.i18n[0].coverage, { n: 0, total: 1 }) + assert.deepEqual(r.crons.i18n[0].missing, ['github-actions/trinity/x']) +}) + +test('no i18n spec: en only, empty i18n list -- the site never invents a locale', () => { + const r = build(files('specs/skills', skillSrc('trinity/x', { module: 'skill_x0' })), []) + assert.deepEqual(r.skills.skills[0].summary, { en: 'en' }) + assert.deepEqual(r.skills.i18n, []) +}) + +test('an orphan bundle entry fails when ORPHANS_ALLOWED is false, and is skipped when true', () => { + const bundle = ruBundle({ 'trinity/x': { SUMMARY: 'ок' }, 'trinity/gone': { SUMMARY: 'нет' } }) + const strict = withI18n(i18nFiles(i18nSrc()), { 'apps/website/i18n/agents.ru.json': bundle }) + assert.equal(strict.problems.filter((p) => p.includes('matches no spec in SCOPE')).length, 1, strict.problems.join('\n')) + const loose = withI18n(i18nFiles(i18nSrc({ orphans: true })), { 'apps/website/i18n/agents.ru.json': bundle }) + assert.deepEqual(loose.problems, []) + assert.deepEqual(loose.skills.skills[0].summary, { en: 'en', ru: 'ок' }) +}) + +test('COVERAGE_REQUIRED turns a missing translation from a warning into a problem', () => { + const r = withI18n(i18nFiles(i18nSrc({ coverageRequired: true })), { 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: 'ок' } }) }) + assert.ok(r.problems.some((p) => p.includes('COVERAGE_REQUIRED') && p.includes('github-actions/trinity/x')), r.problems.join('\n')) +}) + +test('the bundle is checked against the contract: locale, $spec, FIELDS, shapes, missing file, foreign repo, disabled', () => { + const p = (r) => r.problems.join('\n') + let r = withI18n(i18nFiles(i18nSrc()), { 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: 'ок' } }, { locale: 'de' }) }) + assert.ok(p(r).includes('does not match the spec\'s LOCALE'), p(r)) + r = withI18n(i18nFiles(i18nSrc()), { 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: 'ок' } }, { $spec: 'specs/i18n/other.t27' }) }) + assert.ok(p(r).includes('$spec is'), p(r)) + r = withI18n(i18nFiles(i18nSrc()), { 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: 'ок', COMMAND: '/х' } }) }) + assert.ok(p(r).includes('not in FIELDS'), p(r)) + assert.deepEqual(r.skills.skills[0].summary, { en: 'en', ru: 'ок' }, 'the good field still lands') + r = withI18n(i18nFiles(i18nSrc()), { 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: '' } }) }) + assert.ok(p(r).includes('non-empty string'), p(r)) + r = withI18n(i18nFiles(i18nSrc()), {}) + assert.ok(p(r).includes('is missing or not JSON'), p(r)) + r = withI18n(i18nFiles(i18nSrc({ repo: 't27' })), {}) + assert.ok(p(r).includes('only a bundle in this repository'), p(r)) + r = withI18n(i18nFiles(i18nSrc({ enabled: false })), {}) + assert.deepEqual(r.problems, []) + assert.equal(r.skills.i18n[0].enabled, false) + assert.deepEqual(r.skills.skills[0].summary, { en: 'en' }) + r = withI18n(i18nFiles(i18nSrc({ fields: ['SUMMARY', 'COMMAND'] })), { 'apps/website/i18n/agents.ru.json': ruBundle({}) }) + assert.ok(p(r).includes('which no spec constant backs'), p(r)) +}) + +test('locales are discovered, not enumerated: a second contract adds a second key; a duplicate LOCALE is a problem', () => { + const de = i18nSrc({ locale: 'de', module: 'i18n_agents_x1', bundle: 'apps/website/i18n/agents.de.json' }) + const r = withI18n(i18nFiles(i18nSrc(), de), { + 'apps/website/i18n/agents.ru.json': ruBundle({ 'trinity/x': { SUMMARY: 'ок' } }), + 'apps/website/i18n/agents.de.json': { $spec: 'specs/i18n/agents-x1.t27', locale: 'de', entries: { 'trinity/x': { SUMMARY: 'gut' } } }, + }) + assert.deepEqual(r.problems, []) + assert.deepEqual(r.skills.skills[0].summary, { en: 'en', ru: 'ок', de: 'gut' }) + assert.deepEqual(r.skills.i18n.map((l) => l.locale), ['de', 'ru']) + const dup = withI18n(i18nFiles(i18nSrc(), i18nSrc({ module: 'i18n_agents_x1' })), { 'apps/website/i18n/agents.ru.json': ruBundle({}) }) + assert.ok(dup.problems.some((p) => p.includes('duplicate LOCALE ru')), dup.problems.join('\n')) +}) + +test('Cyrillic in an i18n contract spec is a problem like in any other spec', () => { + const r = withI18n(i18nFiles(i18nSrc({ extra: '; Русский комментарий' })), { 'apps/website/i18n/agents.ru.json': ruBundle({}) }) + assert.ok(r.problems.some((p) => p.includes('Cyrillic in a .t27 spec')), r.problems.join('\n')) +}) + +test('checkI18n is usable on its own', () => { + const spec = analyzeSpecFiles(analyze, i18nFiles(i18nSrc()))[0] + const fields = Object.fromEntries(Object.entries(spec.consts).map(([k, v]) => [k, v.value])) + const r = checkI18n({ ...spec, fields }, ruBundle({ 'a/b': { SUMMARY: 'ок' } }), new Map([['a/b', { dir: 'specs/skills' }]])) + assert.deepEqual(r.problems, []) + assert.deepEqual(r.entry.coverage, { n: 1, total: 1 }) + assert.deepEqual(r.translations.get('a/b'), { SUMMARY: 'ок' }) +}) + +test('the committed contract and bundle: no problems, every vendored spec English-only, ru coverage reported', async () => { + const { generate } = await import('./agents-from-specs.mjs') + const r = await generate({ generatedAt: '2026-01-01T00:00:00.000Z' }) + assert.deepEqual(r.problems, []) + const ru = r.skills.i18n.find((l) => l.locale === 'ru') + assert.ok(ru, 'specs/i18n/agents-ru.t27 is the contract for the Russian layer') + assert.equal(ru.spec, 'specs/i18n/agents-ru.t27') + for (const e of [...r.skills.skills, ...r.crons.crons]) { + assert.ok(!/[\u0400-\u04ff]/.test(JSON.stringify(e.fields)), `${e.id}: Cyrillic in spec constants`) + if (e.summary.ru) assert.ok(/[\u0400-\u04ff]/.test(e.summary.ru), `${e.id}: summary.ru is not Russian`) + } +}) diff --git a/apps/website/scripts/railway-services.json b/apps/website/scripts/railway-services.json index c05056fc0f..aa770632b2 100644 --- a/apps/website/scripts/railway-services.json +++ b/apps/website/scripts/railway-services.json @@ -1 +1 @@ -{"project":"999","services":[{"name":"jobsearch-cron","role":"cron","domains":["cron.t27.ai"]},{"name":"skills-site","role":"host","domains":["skills.t27.ai"]},{"name":"trios-agent-server","role":"host","domains":["api.t27.ai","trios-agent-server-production.up.railway.app"]},{"name":"vibee-render","role":"host","domains":["vibee-render-production.up.railway.app"]},{"name":"999-multibots-telegraf","role":"host","domains":[]},{"name":"inngest/inngest","role":"host","domains":[]},{"name":"vibee-editor","role":"host","domains":[]},{"name":"queen-ollama","role":"host","domains":[]},{"name":"queen-proxy","role":"host","domains":[]},{"name":"zep","role":"host","domains":[]},{"name":"zep-nlp","role":"host","domains":[]},{"name":"postgrest","role":"host","domains":[]},{"name":"supabase-gateway","role":"host","domains":[]},{"name":"t27-github-collab","role":"host","domains":[]},{"name":"trios-railway","role":"host","domains":[]},{"name":"Console","role":"host","domains":[]},{"name":"Postgres","role":"store","domains":[]},{"name":"Postgres-NFrq","role":"store","domains":[]},{"name":"pgvector","role":"store","domains":[]},{"name":"Redis","role":"store","domains":[]},{"name":"Bucket","role":"store","domains":[]}]} +{"project":"999","services":[{"name":"jobsearch-cron","role":"cron","domains":["cron.t27.ai"],"serviceId":"3196472e-b885-43e0-b553-0644664e4496"},{"name":"skills-site","role":"host","domains":["skills.t27.ai"],"serviceId":"84197d71-0e3a-4a35-a5ae-05c86b5cf57f"},{"name":"trios-agent-server","role":"host","domains":["api.t27.ai","trios-agent-server-production.up.railway.app"],"serviceId":"40d67e62-08bd-4eea-b709-7201242e9d82"},{"name":"vibee-render","role":"host","domains":["vibee-render-production.up.railway.app"]},{"name":"999-multibots-telegraf","role":"host","domains":[]},{"name":"inngest/inngest","role":"host","domains":[],"serviceId":"3cb0290e-0858-47e6-921d-edd10ed6de3c"},{"name":"vibee-editor","role":"host","domains":[]},{"name":"queen-ollama","role":"host","domains":[]},{"name":"queen-proxy","role":"host","domains":[]},{"name":"zep","role":"host","domains":[]},{"name":"zep-nlp","role":"host","domains":[]},{"name":"postgrest","role":"host","domains":[]},{"name":"supabase-gateway","role":"host","domains":[]},{"name":"t27-github-collab","role":"host","domains":[]},{"name":"trios-railway","role":"host","domains":[]},{"name":"Console","role":"host","domains":[]},{"name":"Postgres","role":"store","domains":[]},{"name":"Postgres-NFrq","role":"store","domains":[]},{"name":"pgvector","role":"store","domains":[]},{"name":"Redis","role":"store","domains":[]},{"name":"Bucket","role":"store","domains":[]}],"projectId":"564d9ebd-7aa8-44fe-93ec-e0b03c87158d"} diff --git a/apps/website/src/components/AgentSpecPanel.tsx b/apps/website/src/components/AgentSpecPanel.tsx new file mode 100644 index 0000000000..f2825a7a1d --- /dev/null +++ b/apps/website/src/components/AgentSpecPanel.tsx @@ -0,0 +1,474 @@ +// The spec side of a skill or cron card, shared by both Explorers. +// +// A skill or a job is a fact only when a .t27 spec states it; the code the +// site's sync scripts scanned is the witness that the fact is also deployed. +// This panel shows the spec itself (its bytes, its hash, the compiler's verdict +// on it), where it can be changed, where the job can be started, and how it is +// linked to its neighbours — without inventing a control it does not have: a +// timer inside a process has no outside handle, and a page with no control +// plane configured says so instead of pretending a button does something. +// +// The copy lives here rather than in each page's UI literal because the two +// pages must say exactly the same thing about the same spec. + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { SpecCodeView } from './SpecCodeView' +import { C, pill, tagChip } from '../lib/explorerTheme' +import { highlightCode, highlightSource, type Span } from '../lib/highlight' +import { analyze } from '../lib/t27Compiler' +import { specExplorerHash } from '../lib/specCatalog' +import { + type I18nContract, + agentControlUrl, + canonicalSpecEditUrl, + loadAgentSpecSource, + requestControl, + specSlug, + vendoredSpecUrl, + WITNESS_LABEL, + type ControlAction, + type CronSpecEntry, + type SkillSpecEntry, +} from '../lib/agentSpecs' + +const COPY = { + en: { + spec: 'SPEC', + control: 'CONTROL', + links: 'LINKS', + verdictOk: 'typecheck ok', + verdictFail: 'typecheck failed', + verdictNote: 'the compiler accepted the module; the field schema is checked by the generator, and both must hold', + discarded: 'discarded', + sha: 'sha256', + shaVerified: 'bytes match the catalog hash', + shaMismatch: 'served bytes differ from the catalog hash — the vendored copy and the generated JSON are out of step', + shaUnchecked: 'hash not verified here (no WebCrypto)', + openSpec: 'Open in Spec Explorer →', + notInCorpus: 'not in the vendored corpus manifest yet (sync-t27-specs has not been re-run)', + loading: 'loading the spec…', + failed: 'could not load the spec:', + codeOnly: 'This card is built from code only: no .t27 spec states it yet. The fields shown come from the sync script’s scan, not from a spec.', + createSpec: 'Write the spec →', + enabled: 'ENABLED', + on: 'true', + off: 'false', + readOnly: 'read from the spec; change it there', + editSpec: 'Edit in the spec →', + vendored: 'vendored copy', + runNow: 'Run now →', + viaActions: 'GitHub Actions workflow page', + viaRailway: 'Railway service', + viaInngest: 'Inngest dashboard', + timerDisabled: 'timer inside the process — controlled by the service’s deployment', + unknownService: 'the Railway service id is not known to the site', + skillRun: 'a skill is launched by an agent with its command; there is no run button without a control plane', + command: 'command', + noPlane: 'No control plane connected — actions go through the spec and the hosts’ panels', + plane: 'control plane', + actRun: 'run', + actEnable: 'enable', + actDisable: 'disable', + sent: 'accepted', + rejected: 'rejected', + runs: 'runs skills', + runBy: 'run by jobs', + noRuns: 'names no skill', + noRunBy: 'no job names this skill', + unresolved: 'unresolved id', + summary: 'summary', + translations: 'translations', + noTranslations: 'none declared', + translationsTitle: 'Specs are English-only (t27 LANG-EN). Each locale is connected through a contract spec under specs/i18n/ that names the bundle carrying the text; n/total is how many entries of this catalog that bundle covers.', + viaSpec: 'via', + copy: 'copy', + copied: 'copied', + messages: 'generator notes', + }, + ru: { + spec: 'СПЕКА', + control: 'УПРАВЛЕНИЕ', + links: 'СВЯЗИ', + verdictOk: 'типизация ок', + verdictFail: 'типизация не прошла', + verdictNote: 'компилятор принял модуль; схему полей проверяет генератор, и нужны оба', + discarded: 'отброшено', + sha: 'sha256', + shaVerified: 'байты совпадают с хешем каталога', + shaMismatch: 'отданные байты не совпадают с хешем каталога — вендорная копия и сгенерированный JSON разошлись', + shaUnchecked: 'хеш здесь не проверен (нет WebCrypto)', + openSpec: 'Открыть в Обозревателе спек →', + notInCorpus: 'ещё не в манифесте вендорного корпуса (sync-t27-specs не перезапускался)', + loading: 'загружаем спеку…', + failed: 'не удалось загрузить спеку:', + codeOnly: 'Эта карточка построена только из кода: спеки .t27 для неё ещё нет. Поля взяты из сканирования скриптом синхронизации, а не из спеки.', + createSpec: 'Написать спеку →', + enabled: 'ENABLED', + on: 'true', + off: 'false', + readOnly: 'читается из спеки; менять там', + editSpec: 'Изменить в спеке →', + vendored: 'вендорная копия', + runNow: 'Запустить сейчас →', + viaActions: 'страница workflow в GitHub Actions', + viaRailway: 'сервис в Railway', + viaInngest: 'панель Inngest', + timerDisabled: 'таймер внутри процесса — управляется деплоем сервиса', + unknownService: 'id сервиса Railway сайту не известен', + skillRun: 'скилл запускает агент по его команде; без контура управления кнопки запуска нет', + command: 'команда', + noPlane: 'Контур управления не подключён — действия идут через спеку и панели хостов', + plane: 'контур управления', + actRun: 'запустить', + actEnable: 'включить', + actDisable: 'выключить', + sent: 'принято', + rejected: 'отклонено', + runs: 'запускает скиллы', + runBy: 'запускается заданиями', + noRuns: 'не называет ни одного скилла', + noRunBy: 'ни одно задание не называет этот скилл', + unresolved: 'неизвестный id', + summary: 'кратко', + translations: 'переводы', + noTranslations: 'не объявлены', + translationsTitle: 'Спеки только на английском (t27 LANG-EN). Каждая локаль подключена через спеку-контракт в specs/i18n/, которая называет бандл с текстом; n/total — сколько записей этого каталога бандл покрывает.', + viaSpec: 'через', + copy: 'копировать', + copied: 'скопировано', + messages: 'заметки генератора', + }, +} as const + +type Copy = Record + +export interface CrossLink { + id: string + href: string + ok: boolean +} + +interface Props { + lang: 'en' | 'ru' + kind: 'skill' | 'cron' + /** The catalog id of the card, for the code-only case where there is no spec. */ + id: string + entry: SkillSpecEntry | CronSpecEntry | null + links: CrossLink[] + /** Whether the reader can jump to the Spec Explorer in this frame. */ + embedded: boolean + /** The catalog's translation contracts, for the `translations:` line. */ + i18n?: I18nContract[] +} + +async function sha256Hex(text: string): Promise { + const subtle = globalThis.crypto?.subtle + if (!subtle) return null + const digest = await subtle.digest('SHA-256', new TextEncoder().encode(text)) + return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('') +} + +function label(kind: Props['kind'], t: Copy): { links: string; empty: string } { + return kind === 'cron' ? { links: t.runs, empty: t.noRuns } : { links: t.runBy, empty: t.noRunBy } +} + +export function AgentSpecPanel({ lang, kind, id, entry, links, embedded, i18n = [] }: Props) { + const t: Copy = COPY[lang] + // Everything fetched for one spec travels together, keyed by its path, so a + // change of card is a change of key rather than a burst of resets. + interface Loaded { path: string; source: string; lines: Span[][]; err: string | null; sha: 'pending' | 'ok' | 'mismatch' | 'unchecked' } + const [loaded, setLoaded] = useState(null) + const [copied, setCopied] = useState(false) + const [planeNote, setPlaneNote] = useState(null) + const plane = agentControlUrl() + + const specPath = entry?.specPath ?? null + const expectedSha = entry?.sha256 ?? null + useEffect(() => { + if (!specPath || !expectedSha) return + let alive = true + const path = specPath + loadAgentSpecSource(path) + .then(async (text) => { + if (!alive) return + const hex = await sha256Hex(text) + const sha: Loaded['sha'] = hex === null ? 'unchecked' : hex === expectedSha ? 'ok' : 'mismatch' + let lines: Span[][] + try { + lines = highlightSource(text, (await analyze(text)).tokens) + } catch { + lines = highlightCode(text, 't27') + } + if (alive) setLoaded({ path, source: text, lines, err: null, sha }) + }) + .catch((e) => alive && setLoaded({ path, source: '', lines: [], err: String(e instanceof Error ? e.message : e), sha: 'unchecked' })) + return () => { + alive = false + } + }, [specPath, expectedSha]) + + const current = loaded && loaded.path === specPath ? loaded : null + const source = current?.source ?? '' + const lines = current?.lines ?? [] + const err = current?.err ?? null + const shaState = current?.sha ?? 'pending' + + const copySha = useCallback(() => { + if (!entry) return + navigator.clipboard?.writeText(entry.sha256).then( + () => { + setCopied(true) + window.setTimeout(() => setCopied(false), 1400) + }, + () => {}, + ) + }, [entry]) + + const act = useCallback( + async (action: ControlAction) => { + if (!plane) return + setPlaneNote('…') + try { + const r = await requestControl(plane, kind, id, action) + setPlaneNote(`${r.ok ? t.sent : t.rejected} · HTTP ${r.status}${r.body ? ` · ${r.body.slice(0, 160)}` : ''}`) + } catch (e) { + setPlaneNote(`${t.rejected} · ${String(e instanceof Error ? e.message : e)}`) + } + }, + [plane, kind, id, t.sent, t.rejected], + ) + + const heading = (text: string) =>
{text}
+ const box: React.CSSProperties = { background: C.panel, border: `1px solid ${C.border}`, borderRadius: 6, padding: 12, display: 'flex', flexDirection: 'column', gap: 8 } + + const runNow = useMemo(() => { + if (!entry || kind !== 'cron') return null + return (entry as CronSpecEntry).runNow + }, [entry, kind]) + + // The spec is English-only (LANG-EN). Any other locale reaches this panel + // through a contract spec under specs/i18n/ and its bundle; when that locale + // has no entry for this spec, the English text from the spec is shown (the + // contract's FALLBACK). + const summary = entry ? (entry.summary[lang] ?? entry.summary.en) : '' + const { links: linksLabel, empty: emptyLabel } = label(kind, t) + + // ---- code-only: no spec to show, but an honest card and a way to write one. + if (!entry) { + const dir = kind === 'cron' ? 'specs/crons' : 'specs/skills' + const filename = `${id.replace(/[^A-Za-z0-9_.-]+/g, '-')}.t27` + return ( +
+
+
+ {heading(t.spec)} + {WITNESS_LABEL['code-only'][lang]} +
+
{t.codeOnly}
+ +
+
+ {heading(t.control)} +
{t.noPlane}
+
+
+ ) + } + + const witnessColor = entry.witness === 'spec+code' ? C.accent : C.warn + const verdictColor = entry.typecheckOk && entry.discarded === 0 ? C.accent : C.bad + + return ( +
+ {/* ---- the spec itself ---- */} +
+
+ {heading(t.spec)} + + {entry.specPath} + + {WITNESS_LABEL[entry.witness][lang]} + + {entry.typecheckOk ? t.verdictOk : t.verdictFail} + {entry.discarded > 0 ? ` · ${t.discarded} ${entry.discarded}` : ''} + +
+ {summary && ( +
+ {summary} +
+ )} +
+ {t.translations}:{' '} + {i18n.length === 0 + ? t.noTranslations + : i18n.map((c, i) => ( + + {i > 0 ? ' · ' : ''} + {c.locale} {t.viaSpec}{' '} + + {c.spec} + {' '} + ({c.coverage.n}/{c.coverage.total}){c.enabled ? '' : ' · off'} + + ))} +
+
+ {t.sha} + + {entry.sha256.slice(0, 16)}… + + + + {shaState === 'ok' ? `✓ ${t.shaVerified}` : shaState === 'mismatch' ? `✕ ${t.shaMismatch}` : shaState === 'unchecked' ? t.shaUnchecked : ''} + +
+
+ {entry.inSpecCorpus ? ( + + {t.openSpec} + + ) : ( + {t.notInCorpus} + )} +
+ {entry.messages.length > 0 && ( +
+ {t.messages}: + {entry.messages.join(' · ')} +
+ )} +
+ {err ? ( +
+ {t.failed} {err} +
+ ) : source ? ( + + ) : ( +
{t.loading}
+ )} +
+
+ + {/* ---- cross links ---- */} +
+
+ {heading(t.links)} + {linksLabel} +
+ {links.length === 0 ? ( +
+ {emptyLabel} + {kind === 'cron' && (entry as CronSpecEntry).fields.RUNS_NOTE ? ( + — {(entry as CronSpecEntry).fields.RUNS_NOTE} + ) : null} +
+ ) : ( +
+ {links.map((l) => + l.ok ? ( + + {kind === 'cron' ? '⟲' : '◷'} {l.id} + + ) : ( + + ✕ {l.id} + + ), + )} +
+ )} +
+ + {/* ---- management strip ---- */} +
+ {heading(t.control)} +
+ + {t.enabled} = {entry.fields.ENABLED ? t.on : t.off} + + + {t.editSpec} + + + {t.vendored} + +
+
+ {kind === 'cron' && runNow ? ( + runNow.kind === 'link' ? ( + <> + + {t.runNow} + + + {runNow.via === 'github-actions' ? t.viaActions : runNow.via === 'railway' ? t.viaRailway : t.viaInngest} + + + ) : ( + <> + + {t.runNow} + + {runNow.reason === 'timer' ? t.timerDisabled : t.unknownService} + + ) + ) : ( + + {t.skillRun} + {(entry as SkillSpecEntry).fields.COMMAND ? ( + <> + {' · '} + {t.command}: {(entry as SkillSpecEntry).fields.COMMAND} + + ) : null} + + )} +
+ {plane ? ( +
+ {t.plane} + + {kind === 'cron' && ( + <> + + + + )} + {planeNote && ( + + {planeNote} + + )} +
+ ) : ( +
{t.noPlane}
+ )} +
+ {specSlug(entry.specPath)} +
+
+
+ ) +} diff --git a/apps/website/src/components/ModulesBlock.tsx b/apps/website/src/components/ModulesBlock.tsx index 1a232b6856..5b47471ce0 100644 --- a/apps/website/src/components/ModulesBlock.tsx +++ b/apps/website/src/components/ModulesBlock.tsx @@ -9,16 +9,16 @@ import './ModulesBlock.css' const COPY = { en: { - eyebrow: 'QUEEN / SIX MODULES', - title: 'One screen, six ways to read it', - lede: 'The Queen is a single-page module: the 3D map is the ground and every panel floats on it. These six are what that ground can be read as — each one opens on the same map, in the same shell, from the number beside it.', + eyebrow: 'QUEEN / EIGHT MODULES', + title: 'One screen, eight ways to read it', + lede: 'The Queen is a single-page module: the 3D map is the ground and every panel floats on it. These eight are what that ground can be read as — each one opens on the same map, in the same shell, from the number beside it.', open: 'Open', all: 'Open the shell →', }, ru: { - eyebrow: 'КОРОЛЕВА / ШЕСТЬ МОДУЛЕЙ', - title: 'Один экран и шесть способов его прочитать', - lede: 'Королева — одностраничный модуль: 3D-карта здесь основание, а каждая панель плавает над ним. Эти шесть — то, чем это основание можно прочитать; любой открывается на той же карте, в том же шелле, по цифре рядом с ним.', + eyebrow: 'КОРОЛЕВА / ВОСЕМЬ МОДУЛЕЙ', + title: 'Один экран и восемь способов его прочитать', + lede: 'Королева — одностраничный модуль: 3D-карта здесь основание, а каждая панель плавает над ним. Эти восемь — то, чем это основание можно прочитать; любой открывается на той же карте, в том же шелле, по цифре рядом с ним.', open: 'Открыть', all: 'Открыть шелл →', }, diff --git a/apps/website/src/components/QueenAgents.tsx b/apps/website/src/components/QueenAgents.tsx new file mode 100644 index 0000000000..68981749cf --- /dev/null +++ b/apps/website/src/components/QueenAgents.tsx @@ -0,0 +1,121 @@ +// The Queen's SKILLS and CRONS views: the real Skill and Cron Explorers, +// inside the game. +// +// Same shape as QueenSpecs, for the same reasons: the Explorers own a +// full-viewport layout and the Skill/Cron pages boot the compiler wasm to +// colour their specs, so each is an iframe of our own page at +// #/skills?embed=1 or #/crons?embed=1 — a layout boundary, not a sandbox one. +// +// The strip above the frame (or beside it, on a wide screen) reads the generated +// spec catalog and states the witness split in numbers: how many specs, how many +// backed by code, how many cards have code and no spec yet, and how many the +// compiler accepted. It counts; it does not grade. + +import { useEffect, useRef, useState } from 'react' +import { useI18n } from '../i18n/context' +import { QueenLoading } from './QueenLoading' +import { loadCronSpecs, loadSkillSpecs } from '../lib/agentSpecs' + +export type AgentsKind = 'skills' | 'crons' + +export interface AgentsCopy { + directive: string + directiveBody: string + open: string + loading: string + specs: string + specPlusCode: string + codeOnly: string + typecheck: string +} + +interface Counts { specs: number; specPlusCode: number; codeOnly: number; typecheckOk: number } + +async function loadCounts(kind: AgentsKind): Promise { + const c = kind === 'skills' ? (await loadSkillSpecs()).counts : (await loadCronSpecs()).counts + return { specs: c.specs, specPlusCode: c.specPlusCode, codeOnly: c.codeOnly, typecheckOk: c.typecheckOk } +} + +/** The directive, the witness counts and the way out to the full page. */ +export function QueenAgentsDirective({ kind, c, collapsible = false }: { kind: AgentsKind; c: AgentsCopy; collapsible?: boolean }) { + const [counts, setCounts] = useState(null) + + useEffect(() => { + let alive = true + loadCounts(kind) + .then((n) => { if (alive) setCounts(n) }) + .catch(() => {}) + return () => { alive = false } + }, [kind]) + + const body = ( + <> +

{c.directiveBody}

+ {counts && ( + + {counts.specs} {c.specs} + {' · '} + {counts.specPlusCode} {c.specPlusCode} + {' · '} + {counts.codeOnly} {c.codeOnly} + {' · '} + + {counts.typecheckOk}/{counts.specs} + {' '} + {c.typecheck} + + )} + + {c.open} + + + ) + + if (collapsible) { + return ( +
+ {c.directive} + {body} +
+ ) + } + + return ( +
+ {c.directive} + {body} +
+ ) +} + +export function QueenAgents({ kind, c, showDirective = true }: { kind: AgentsKind; c: AgentsCopy; showDirective?: boolean }) { + const { lang } = useI18n() + const [ready, setReady] = useState(false) + const frameRef = useRef(null) + + // ?lang= rides in the search, where the i18n provider reads it, so the frame + // follows the shell's language instead of whatever localStorage held. + const src = `${window.location.pathname}?lang=${lang}#/${kind}?embed=1` + + return ( +
+ {showDirective && } + +
+ {!ready && ( +
+ +
+ )} +