From 67e99b85cca46b15c0f15b7f82e40efb68b4a227 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 09:25:14 +0700 Subject: [PATCH] specs/t27mcp: land examples.t27 and spec_brief.t27 on master #10083 and #10092 were merged into their stacked base branches (mcp-t27-check, mcp-t27-examples) after #10080 had already landed, so neither spec reached master. This carries the two specs and the OWNERS row unchanged from mcp-t27-examples. Closes #10081 Closes #10090 Co-Authored-By: Claude Opus 5.5 --- specs/OWNERS.md | 2 +- specs/t27mcp/examples.t27 | 253 ++++++++++++++++++++++++++++++++++++ specs/t27mcp/spec_brief.t27 | 101 ++++++++++++++ 3 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 specs/t27mcp/examples.t27 create mode 100644 specs/t27mcp/spec_brief.t27 diff --git a/specs/OWNERS.md b/specs/OWNERS.md index 75f3f1bb29..8d8e19a44a 100644 --- a/specs/OWNERS.md +++ b/specs/OWNERS.md @@ -29,7 +29,7 @@ | `ui/` | **T-Queen** | The site's viewport contract as a `.t27` spec (`ui/viewport.t27`, `KIND = "viewport"`): tiers by CSS viewport width, panes per tier, the 44 px touch minimum, the six-size QA matrix and the Queen rail capacity read from `Queen.css`; the site (gHashTag/trinity) generates `viewport.generated.ts` / `.css` from a vendored copy and evaluates the spec's own test blocks; `ui/queen_evidence.t27` (`module ui_queen_evidence`): the evidence constants of the Queen's FFN and Ternary Check panel, from which the site generates `queenEvidence.generated.ts`; canonical here since S10, byte-identical to the copy the site carried alone | | `catalog/` | **T-Queen** | How the site's spec catalog and the Queen map find the repositories that write `.t27` (`catalog/discovery.t27`, `KIND = "discovery"`): the GitHub owners scanned, the exclusions, what makes a candidate qualify (a declaration in the compiler's AST, not a file extension) and the limits; the site (gHashTag/trinity) vendors a byte-identical copy and `scripts/discover-t27-worlds.mjs` reads it through the compiler wasm before touching GitHub | | `t27f/` | **T-Queen** | T27F, the visual spec of a .t27 file (gHashTag/trinity#1648, RFC #1649; definition in `.claude/skills/t27f/SKILL.md`): `t27f/spec_graph.t27` (module `t27f_spec_graph`): the core graph IR a spec becomes -- node and edge kinds, inference labels, source spans, `validate_graph`, source mapping both ways and the node -> ui_plan widget binding, with one hand-read fixture; `t27f/spec_graph_view.t27` (module `t27f_spec_graph_view`): the graph view, a deterministic layout of a spec graph -- one column per node kind, style and row order from the inference label, UNKNOWN nodes drawn alone, every edge with a source line; numbers only, a host draws them; `t27f/ui_plan.t27` (module `t27f_ui_plan`): the renderer-neutral UI plan IR and the trusted widget catalog. Applications built on it: the topic-to-value map of gHashTag/trinity#1677 (`t27f/topic_engine.t27`, module `T27fTopicEngine`): the 3 x 9 = 27 lesson shape, intake and gap rules, keyword routing to one domain, the Bee dispatch cap, the evidence and approval gates, and progression that is never money; driven by the Claude Code skill `.claude/skills/t27f/SKILL.md` through the t27 MCP server; `t27f/reel.t27` (module `T27fReel`): the video format of a topic map and the still views of a Claude Code answer -- honeycomb geometry, focus+context camera, scene catalog, per-target limits, gap styling, the posting gate and the channel per host; numbers only, a host draws them; `t27f/deck.t27` (module `T27fDeck`): the default look of a reel, every scene a presentation slide -- layouts per scene, regions, type sizes, glass card, palette kept off the status colours, contrast and card fades | -| `t27mcp/` | **T-Queen** | The decisions of the t27 MCP server (gHashTag/t27-mcp) as `.t27` specs, compiled to wasm that the server runs: `t27mcp/check_hints.t27` (module `T27mcpCheckHints`): which short "write this instead" hint a compiler message gets for foreign syntax (`in` as a name, `as u3`, `x++`, `a ? b : c`, `match`), and the line number a message names; the server's JavaScript is I/O glue and holds no rule | +| `t27mcp/` | **T-Queen** | The decisions of the t27 MCP server (gHashTag/t27-mcp) as `.t27` specs, compiled to wasm that the server runs: `t27mcp/check_hints.t27` (module `T27mcpCheckHints`): which short "write this instead" hint a compiler message gets for foreign syntax (`in` as a name, `as u3`, `x++`, `a ? b : c`, `match`), and the line number a message names; `t27mcp/examples.t27` (module `T27mcpExamples`): which short slice of the published corpus, pinned to one commit, answers a question about clocked or combinational modules, `fn`, `test` blocks and the reset styles; `t27mcp/spec_brief.t27` (module `T27mcpSpecBrief`): which manifest fields a short spec row keeps and why a failing spec fails (a bit set of reasons plus their text templates); the server's JavaScript is I/O glue and holds no rule | | `trinity/` | **T-Queen** + **A-Architect** | The project manifest of gHashTag/trinity as `.t27` specs (`trinity/project.t27`, `KIND = "project"`; `trinity/capabilities/.t27`, `KIND = "capability"`): the pinned consumer revision, profiles, dispositions and evidence tags, dialect and build counts, dependency pins and the work packages of gHashTag/trinity#988; each card names its owner, canonical spec, implementation and generated paths, build targets, acceptance command and evidence; `tools/trinity_manifest.py` derives the inventory from a clean pinned checkout and holds the cards to it; `trinity/compiler_matrix.t27` (`KIND = "compiler-matrix"`) names the pinned compiler, the backends, the stages and the features of the executable subset with their fixtures under `bootstrap/tests/fixtures/trinity_matrix/`, and `tools/trinity_compiler_matrix.py` measures them; `trinity/build_graph.t27` (`KIND = "build-graph"`) declares the consumer's toolchain, pins, profiles, generated-file boundaries and receipt policy, and `tools/trinity_build_receipt.py` derives the graph, writes the receipts and judges drift; the consumer vendors a byte-identical copy and generates its manifest from it (S12). Shared contracts stay in their own subtrees (`numeric/`, `vsa/`, `queen/`, `tools/`, `docs/`, `ui/`) and are cited, never copied | Each subtree with substantial churn should keep a local **`OWNERS.md`** (see below). diff --git a/specs/t27mcp/examples.t27 b/specs/t27mcp/examples.t27 new file mode 100644 index 0000000000..5de96306b6 --- /dev/null +++ b/specs/t27mcp/examples.t27 @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27mcp/examples.t27 -- t27_examples of the t27 MCP server (gHashTag/t27-mcp): which short slice of the +; published corpus answers a question a model asks while it writes .t27. See #10081 (epic #10066). +; -- +; A model that learns the idioms from failed compiles pays one round trip each: a clocked module has no port +; list, combinational logic is `fn on_comb`, the reset is implicit. The corpus already shows each of them in a +; small tested spec, but t27_spec returns the whole file. This spec holds the pointers; the server fetches +; the file at the pinned commit, cuts the lines and prints them. +; -- +; The rules: +; - topic_code() maps the words of a question (already lower case) to one topic, in a fixed order, the first +; rule that matches wins. A question no rule knows gets T_NONE, and the server then lists the topics and +; invents no example. `async` contains `sync`, so the async rule comes first. +; - pick(topic, k) is the row of the k-th example of a topic, or NO_ROW. A topic has one to three rows. +; - A row is path + from..to lines of the pinned file, an optional module line (a slice that starts below the +; `module` line is not a file) and an optional `use` line and footer line (a braced module needs its closing +; brace); the slice is at most MAX_LINES lines. 0 means "no such line". +; - PIN is one commit of this repository. A pointer cannot drift when a file changes on master. +; Every row was run with `t27c test-report` on the lab at PIN: parse and typecheck clean and, where the slice +; has tests, all passing with 0 vacuous; `why` says "no tests" for the one slice that has none. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27mcpExamples; + +/// The commit of gHashTag/t27 every row points into. +pub const PIN : str = "a18d7ee14a9994ef9dc84dac10e1a2944367d4c5"; + +pub const T_NONE : u32 = 0; +pub const T_CLOCKED : u32 = 1; +pub const T_COMB : u32 = 2; +pub const T_FN : u32 = 3; +pub const T_TEST : u32 = 4; +pub const T_RESET_ASYNC : u32 = 5; +pub const T_RESET_SYNC : u32 = 6; +pub const T_RESET_NONE : u32 = 7; +pub const T_COUNT : u32 = 8; + +pub const TOPICS : [8]str = [ + "", + "clocked", + "comb", + "fn", + "test", + "reset_async", + "reset_sync", + "reset_none" +]; + +pub const NO_ROW : u32 = 4294967295; +pub const NO_POS : u32 = 4294967295; +pub const MAX_LINES : u32 = 40; +pub const ROWS : u32 = 10; + +pub const EX_TOPIC : [10]u32 = [1, 1, 2, 2, 3, 4, 4, 5, 6, 7]; +pub const EX_PATH : [10]str = [ + "specs/ternary/clocked_counter.t27", + "specs/fpga/ro_race.t27", + "specs/verified/poc/a/slot.t27", + "specs/ternary/gft_relu.t27", + "specs/ternary/ternary_mac.t27", + "specs/port/trinity/fpga/openxc7-synth/uart_echo.t27", + "specs/verified/poc/a/slot.t27", + "specs/ternary/clocked_counter.t27", + "specs/fpga/reset_sync.t27", + "specs/fpga/ro_race.t27" +]; +pub const EX_FROM : [10]u32 = [25, 16, 8, 10, 1, 6, 8, 1, 21, 16]; +pub const EX_TO : [10]u32 = [37, 46, 18, 24, 26, 35, 18, 37, 58, 46]; +pub const EX_MODULE : [10]u32 = [1, 2, 0, 1, 0, 0, 0, 0, 17, 2]; +pub const EX_USE : [10]u32 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +pub const EX_FOOTER : [10]u32 = [0, 0, 0, 0, 0, 0, 0, 0, 257, 0]; +pub const EX_WHY : [10]str = [ + "Smallest clocked module: a module-level var is the register, fn on_clock() is the clock edge, the test steps it. No port list, no always, no clk.", + "Two registers, a const bound, a branch in on_clock, and tests that set registers by assignment before stepping the clock.", + "Smallest combinational module: pub fn on_comb(x: u8) -> u8 and a test with an assert statement.", + "A real on_comb with an if-chain and four assert_eq tests; the params are the inputs and the return value is the output.", + "Module header, pub fns with typed params and an arrow return, a loop, and four assert_eq tests.", + "Tests inside a braced module, written assert(cond); the assert_eq and assert x == y forms are in the other examples.", + "The test form `test \"name\" { assert x == y; }` with a quoted name.", + "The default reset needs no code. The header says on_clock lowers to an async active-low reset: each var takes its declared init on !rst_n, and en gates the body.", + "ResetKind (sync, async_assert_sync_release, async_assert_async_release) and a shift-register model of the release path. A sync reset is ordinary logic, not a module switch. No tests in this slice.", + "The header states that nothing here is reset: the counters start from their var initialisers." +]; + +/// Byte offset of the first occurrence of `needle` in `hay`, or NO_POS. +pub fn find(hay: string, needle: string) -> u32 { + var hn : u32 = (hay.len as u32); + var nn : u32 = (needle.len as u32); + if (nn > hn) { return NO_POS; } + var i : u32 = 0; + while (i <= hn - nn) { + var j : u32 = 0; + var same : u32 = 1; + while (j < nn) { + if ((hay[i + j] as u32) != (needle[j] as u32)) { same = 0; } + j = j + 1; + } + if (same == 1) { return i; } + i = i + 1; + } + return NO_POS; +} + +/// 1 when `needle` occurs in `hay`. +pub fn has(hay: string, needle: string) -> u32 { + if (find(hay, needle) == NO_POS) { return 0; } + return 1; +} + +/// The topic a lower-case question is about, or T_NONE. +pub fn topic_code(q: string) -> u32 { + if (has(q, "reset") == 1) { + if (has(q, "async") == 1) { return T_RESET_ASYNC; } + if (has(q, "no reset") == 1) { return T_RESET_NONE; } + if (has(q, "without") == 1) { return T_RESET_NONE; } + if (has(q, "none") == 1) { return T_RESET_NONE; } + if (has(q, "sync") == 1) { return T_RESET_SYNC; } + return T_RESET_ASYNC; + } + if (has(q, "comb") == 1) { return T_COMB; } + if (has(q, "test") == 1) { return T_TEST; } + if (has(q, "assert") == 1) { return T_TEST; } + if (has(q, "clock") == 1) { return T_CLOCKED; } + if (has(q, "sequential") == 1) { return T_CLOCKED; } + if (has(q, "register") == 1) { return T_CLOCKED; } + if (has(q, "counter") == 1) { return T_CLOCKED; } + if (has(q, "fn") == 1) { return T_FN; } + if (has(q, "func") == 1) { return T_FN; } + return T_NONE; +} + +/// The row of the k-th example of `topic`, or NO_ROW. +pub fn pick(topic: u32, k: u32) -> u32 { + var i : u32 = 0; + var seen : u32 = 0; + while (i < ROWS) { + if (EX_TOPIC[i] == topic) { + if (seen == k) { return i; } + seen = seen + 1; + } + i = i + 1; + } + return NO_ROW; +} + +/// Lines the rebuilt slice has: from..to plus the module, use and footer lines that are present. +pub fn row_lines(i: u32) -> u32 { + var n : u32 = EX_TO[i] + 1 - EX_FROM[i]; + if (EX_MODULE[i] != 0) { n = n + 1; } + if (EX_USE[i] != 0) { n = n + 1; } + if (EX_FOOTER[i] != 0) { n = n + 1; } + return n; +} + +/// 1 when a row can be rebuilt: a topic of the table, a non-empty range, at most MAX_LINES lines. +pub fn row_ok(topic: u32, from: u32, to: u32, lines: u32) -> u32 { + if (topic == T_NONE) { return 0; } + if (topic >= T_COUNT) { return 0; } + if (from == 0) { return 0; } + if (to < from) { return 0; } + if (lines > MAX_LINES) { return 0; } + return 1; +} + +/// How many rows of the table fail row_ok. +pub fn bad_rows() -> u32 { + var i : u32 = 0; + var bad : u32 = 0; + while (i < ROWS) { + bad = bad + 1 - row_ok(EX_TOPIC[i], EX_FROM[i], EX_TO[i], row_lines(i)); + i = i + 1; + } + return bad; +} + +/// How many rows a topic has. +pub fn rows_of(topic: u32) -> u32 { + var i : u32 = 0; + var n : u32 = 0; + while (i < ROWS) { + if (EX_TOPIC[i] == topic) { n = n + 1; } + i = i + 1; + } + return n; +} + +test "topic_code: the words of a question" { + assert topic_code("clocked counter") == T_CLOCKED; + assert topic_code("how do i write a register") == T_CLOCKED; + assert topic_code("combinational logic") == T_COMB; + assert topic_code("test block") == T_TEST; + assert topic_code("assert") == T_TEST; + assert topic_code("fn") == T_FN; + assert topic_code("function with a loop") == T_FN; +} + +test "topic_code: async is not taken for sync, and a bare reset is the default one" { + assert topic_code("async reset") == T_RESET_ASYNC; + assert topic_code("sync reset") == T_RESET_SYNC; + assert topic_code("reset") == T_RESET_ASYNC; + assert topic_code("no reset") == T_RESET_NONE; + assert topic_code("a module without reset") == T_RESET_NONE; +} + +test "negative control: a question no rule knows gets no topic and no row" { + assert topic_code("") == T_NONE; + assert topic_code("axi4 burst") == T_NONE; + assert pick(T_NONE, 0) == NO_ROW; +} + +test "pick: the k-th row of a topic, in table order, and NO_ROW past the end" { + assert pick(T_CLOCKED, 0) == 0; + assert pick(T_CLOCKED, 1) == 1; + assert pick(T_CLOCKED, 2) == NO_ROW; + assert pick(T_FN, 0) == 4; + assert pick(T_FN, 1) == NO_ROW; + assert pick(T_RESET_SYNC, 0) == 8; + assert pick(T_COUNT, 0) == NO_ROW; +} + +test "every topic has one to three rows" { + assert rows_of(T_CLOCKED) == 2; + assert rows_of(T_COMB) == 2; + assert rows_of(T_FN) == 1; + assert rows_of(T_TEST) == 2; + assert rows_of(T_RESET_ASYNC) == 1; + assert rows_of(T_RESET_SYNC) == 1; + assert rows_of(T_RESET_NONE) == 1; + assert rows_of(T_NONE) == 0; +} + +test "every row of the table can be rebuilt" { + assert bad_rows() == 0; + assert row_lines(0) == 14; + assert row_lines(8) == 40; +} + +test "negative control: row_ok refuses the rows it must" { + assert row_ok(T_CLOCKED, 25, 37, 14) == 1; + assert row_ok(T_NONE, 25, 37, 14) == 0; + assert row_ok(T_COUNT, 25, 37, 14) == 0; + assert row_ok(T_CLOCKED, 0, 37, 14) == 0; + assert row_ok(T_CLOCKED, 37, 25, 14) == 0; + assert row_ok(T_CLOCKED, 1, 41, 41) == 0; + assert row_ok(T_CLOCKED, 1, 40, 40) == 1; +} + +test "the pin and the topic names" { + assert has(PIN, "a18d7ee1") == 1; + assert PIN.len == 40; + assert has(TOPICS[T_RESET_SYNC], "sync") == 1; + assert TOPICS[T_NONE] == ""; +} diff --git a/specs/t27mcp/spec_brief.t27 b/specs/t27mcp/spec_brief.t27 new file mode 100644 index 0000000000..6e7c492d4c --- /dev/null +++ b/specs/t27mcp/spec_brief.t27 @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27mcp/spec_brief.t27 -- the brief spec row and the "why does it fail" decision of the t27 MCP server +; (gHashTag/t27-mcp). See the epic #10066. +; -- +; t27_specs, t27_spec and t27_grep answer with a spec row. The whole manifest record is about 860 bytes (AST node +; kinds, emitted bytes per backend, tags, a summary), and a model that only wants "does it compile, and if not +; why" pays for all of it on every row. This spec holds the two decisions that were JavaScript in the server: +; - BRIEF_FIELDS / ONE_FIELDS: which manifest fields a brief row keeps. The server copies those keys and nothing +; else; `fields: true` on the tool returns the whole record instead. +; - why_mask(): `health: "fail"` covers unrelated events. A spec that never parsed reports loss 0, tcErrors 0 and +; no failed backend, not because it is clean but because nothing downstream of the parser ran. why_mask turns +; the observable counters into a bit set; WHY_TEXT holds one template per bit, `{}` marks the number or list +; the server fills in. A spec that is not "fail" gets no reasons at all. +; The mask adds no claim of its own: it reads the counters the manifest already records. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27mcpSpecBrief; + +pub const BRIEF_FIELDS : [8]str = [ + "path", + "repo", + "name", + "health", + "lines", + "loss", + "tcErrors", + "failedBackends" +]; + +/// t27_spec keeps the brief fields and what a reader asks next about one file. +pub const ONE_FIELDS : [14]str = [ + "path", + "repo", + "name", + "health", + "lines", + "loss", + "tcErrors", + "failedBackends", + "category", + "bytes", + "tokens", + "nodes", + "summary", + "tags" +]; + +pub const W_NOT_PARSED : u32 = 1; +pub const W_BACKEND : u32 = 2; +pub const W_TYPECHECK : u32 = 4; +pub const W_LOSS : u32 = 8; +pub const W_UNKNOWN : u32 = 16; + +/// One template per bit, in bit order. `{}` is the count or the backend list. +pub const WHY_TEXT : [5]str = [ + "not-parsed: the manifest records no AST, so no backend ran", + "backend: {} returned nothing", + "typecheck: {} error(s)", + "round-trip loss: {}", + "unknown: marked fail with no counter to explain it" +]; + +/// The reasons a spec fails, as a bit set. `is_fail` is 1 for health "fail"; the rest are the manifest counters +/// (nodes of the AST, number of failed backends, type-check errors, round-trip loss). +pub fn why_mask(is_fail: u32, nodes: u32, nfailed: u32, tc: u32, loss: u32) -> u32 { + if (is_fail == 0) { return 0; } + var m : u32 = 0; + if (nodes == 0) { m = m + W_NOT_PARSED; } + if (nfailed > 0) { m = m + W_BACKEND; } + if (tc > 0) { m = m + W_TYPECHECK; } + if (loss > 0) { m = m + W_LOSS; } + if (m == 0) { m = W_UNKNOWN; } + return m; +} + +test "why_mask: a spec that is not failing has no reasons" { + assert why_mask(0, 0, 0, 0, 0) == 0; + assert why_mask(0, 120, 3, 2, 1) == 0; +} + +test "why_mask: a spec that never parsed is not-parsed, not unexplained" { + assert why_mask(1, 0, 0, 0, 0) == W_NOT_PARSED; +} + +test "why_mask: each counter sets its own bit, and bits add" { + assert why_mask(1, 50, 2, 0, 0) == W_BACKEND; + assert why_mask(1, 50, 0, 3, 0) == W_TYPECHECK; + assert why_mask(1, 50, 0, 0, 7) == W_LOSS; + assert why_mask(1, 50, 1, 2, 0) == W_BACKEND + W_TYPECHECK; + assert why_mask(1, 0, 1, 0, 1) == W_NOT_PARSED + W_BACKEND + W_LOSS; +} + +test "negative control: a fail with no counter is W_UNKNOWN alone, never silent" { + assert why_mask(1, 50, 0, 0, 0) == W_UNKNOWN; + assert why_mask(1, 50, 0, 0, 0) != 0; +} + +test "why_mask: all four counters at once leave W_UNKNOWN out" { + assert why_mask(1, 0, 2, 3, 4) == W_NOT_PARSED + W_BACKEND + W_TYPECHECK + W_LOSS; + assert why_mask(1, 0, 2, 3, 4) < W_UNKNOWN; +}