From 0f6c643a45ab1658a806c0bb0e730e18e110af7f Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 06:55:23 +0700 Subject: [PATCH 1/4] t27f: deps card -- who imports a spec and what it imports, as a social card (WIP, Refs #10720) Co-Authored-By: Claude Opus 5.5 --- specs/skills/t27-deps-card.t27 | 51 ++++ specs/t27f/deps_card.t27 | 499 +++++++++++++++++++++++++++++++++ 2 files changed, 550 insertions(+) create mode 100644 specs/skills/t27-deps-card.t27 create mode 100644 specs/t27f/deps_card.t27 diff --git a/specs/skills/t27-deps-card.t27 b/specs/skills/t27-deps-card.t27 new file mode 100644 index 0000000000..9fc334abe8 --- /dev/null +++ b/specs/skills/t27-deps-card.t27 @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/skills/t27-deps-card.t27 -- skill t27/deps-card +; Source of truth for the skill card on t27.ai (#/skills?skill=t27/deps-card). +; How TRI CLAW answers "who uses this spec, and what does it use" with a picture: pipe +; `t27c use-edges` into deps_card with DEPS_SPEC and DEPS_FONT set, and render the +; graph it prints with ffmpeg as a 1080x1350 card: the spec's direct importers above +; it, its direct imports below it, `+N more` past three, the transitive counts in the +; footer, and a gold ON AN IMPORT CYCLE line when it is on one. The law lives once, in +; specs/t27f/deps_card.t27 (dcard_* functions and their tests), the walk in +; specs/t27f/spec_deps.t27 (deps_walk) and the drawing helpers in +; specs/t27f/why_card.t27; this card points at them and copies none. Posting a card is +; the owner's call. There is no SKILL.md behind it yet, so the card is spec-only (see +; "Witness labels" in specs/skills/README.md). Issue gHashTag/t27#10720, epic +; gHashTag/trinity#1648. +; 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_deps_card; + +pub const KIND : str = "skill"; +pub const ID : str = "t27/deps-card"; +pub const NAME : str = "Deps Card"; +pub const REPO : str = "t27"; +; No SKILL.md exists for this card. +pub const SOURCE : str = ""; +pub const SUMMARY_EN : str = "Draw who uses a spec and what it uses as a social card: its direct importers and imports t27c resolved, the transitive counts both ways, a cycle flag, and no card at all when the spec is not in the edge list"; +pub const COMMAND : str = "/deps-card"; +; Specs this skill stands on: the card layout and the walk it draws. +pub const SPECS : [2]str = ["specs/t27f/deps_card.t27", "specs/t27f/spec_deps.t27"]; +pub const TAGS : [4]str = ["social-visual", "t27f", "deps", "card"]; +pub const ENABLED : bool = true; +; Default budget; no SKILL.md declares a timeout. +pub const TIMEOUT_MIN : u16 = 30; + +test basic_constants { + assert KIND == "skill"; + assert ID == "t27/deps-card"; + assert NAME == "Deps Card"; + assert REPO == "t27"; + assert SOURCE == ""; + assert COMMAND == "/deps-card"; + assert ENABLED == true; + assert TIMEOUT_MIN == 30; + assert TAGS.len == 4; + assert TAGS[0] == "social-visual"; + assert TAGS[2] == "deps"; + assert SPECS.len == 2; + assert SPECS[0] == "specs/t27f/deps_card.t27"; + assert SPECS[1] == "specs/t27f/spec_deps.t27"; +} diff --git a/specs/t27f/deps_card.t27 b/specs/t27f/deps_card.t27 new file mode 100644 index 0000000000..b3469d2540 --- /dev/null +++ b/specs/t27f/deps_card.t27 @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27f/deps_card.t27 -- a social card for one spec's neighbourhood: who imports it, what it +; imports (gHashTag/t27#10720; epic gHashTag/trinity#1648). T27F is the visual spec of .t27 +; (.claude/skills/t27f/SKILL.md, "What T27F is"). specs/t27f/why_card.t27 draws one chain between two +; specs; this file draws the question asked first about one spec, the one nx graph's focus view, +; madge's image and dependency-cruiser's focus filter answer: what uses it, and what does it use. +; -- +; The card, 1080x1350, top to bottom: the specs that import the focus directly, the focus, the specs +; it imports directly, each band in edge-list order and joined to the focus by a `uses` connector, +; so the card reads downward as "X uses Y". The footer gives the transitive counts both ways. +; -- +; The rules, each with a test below: +; - each band shows its first DCARD_ROWS specs. Its label gives the direct count, and `+N more` +; appears exactly when the band holds more specs than it shows, N the ones not shown; +; - the counts are deps_walk's (specs/t27f/spec_deps.t27): direct is layer 1, transitive every +; layer past 0, so a spec reached two ways is counted once; +; - every box and connector is in the exact style (spec_graph_view's green stroke on its dark +; tint): every edge is a line t27c resolved. The subtitle reads ON AN IMPORT CYCLE, in the +; derived gold, exactly when deps_on_cycle holds for the focus; +; - an empty band draws its label with 0 and no connector; +; - nothing is drawn (dcard_graph returns 0) for a focus on no line of the edge list, a refused +; walk, or a shown name or font path the graph cannot quote or that does not fit. A name in a +; band past DCARD_ROWS is not drawn, so it is not checked. A graph that does not fit the +; caller's room is refused whole. +; -- +; The drawing helpers and the name rules are why_card's, imported, not copied (duplicate-bodies). +; -- +; HOW (main): t27c use-edges --specs-dir specs | DEPS_SPEC=specs/x.t27 DEPS_FONT= +; ./deps_card 2> graph.txt, where deps_card is this file through `t27c gen` and `zig build-exe -lc`. +; Then ffmpeg -filter_complex_script graph.txt -map '[v]' -frames:v 1 card.png. +; -- +; The honest limits: +; - a band shows DCARD_ROWS specs in edge-list order, not the "most important" ones: the card has +; no measure of importance, and does not pretend to; +; - a `use` line t27c does not resolve is in neither band (deps_unknown, #10702, lists those); +; - nothing posts the card. Publishing it is the owner's call. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module t27f_deps_card; + +use policy::text_lines; +use ci::affected; +use claw::t27f_gate; +use t27f::spec_deps; +use t27f::why_card; +use t27f::spec_graph_view::VIEW_LABEL_FILL; +use t27f::spec_graph_view::VIEW_LABEL_STROKE; +use t27f::spec_graph_view::VIEW_BACKDROP; +use t27f::spec_graph_view::VIEW_TEXT; + +pub const DCARD_ROWS : usize = 3; +pub const DCARD_ROW_H : usize = 64; +pub const DCARD_ROW_GAP : usize = 12; +pub const DCARD_ROW_FONT_MAX : usize = 32; +pub const DCARD_BAND_FONT : usize = 28; +pub const DCARD_UP_LABEL_Y : usize = 250; +pub const DCARD_UP_ROW_Y : usize = 280; +pub const DCARD_UP_MORE_Y : usize = 528; +pub const DCARD_FOCUS_Y : usize = 640; +pub const DCARD_FOCUS_H : usize = 96; +pub const DCARD_FOCUS_BORDER : usize = 8; +pub const DCARD_DOWN_LABEL_Y : usize = 790; +pub const DCARD_DOWN_ROW_Y : usize = 820; +pub const DCARD_DOWN_MORE_Y : usize = 1068; + +pub const DCARD_HEAD : str = "WHO USES IT$"; +pub const DCARD_SUB : str = "imports and importers t27c resolved$"; +pub const DCARD_CYCLE : str = "ON AN IMPORT CYCLE$"; +pub const DCARD_UP : str = "IMPORTED BY $"; +pub const DCARD_DOWN : str = "IMPORTS $"; +pub const DCARD_MORE : str = " more$"; +pub const DCARD_REACHES : str = "reaches $"; +pub const DCARD_REACHED : str = " / reached by $"; +pub const DCARD_FOOT : str = " / every edge exact$"; + +// ---- geometry ---- + +/// How far apart two rows of a band are, top to top. +pub fn dcard_row_step() -> usize { + return DCARD_ROW_H + DCARD_ROW_GAP; +} + +/// The bottom of a band whose rows start at base and that shows shown rows. +pub fn dcard_band_bottom(base: usize, shown: usize) -> usize { + if (shown == 0) { return base; } + return base + shown * dcard_row_step() - DCARD_ROW_GAP; +} + +/// How many rows a band of d specs shows. +pub fn dcard_shown(d: usize) -> usize { + if (d > DCARD_ROWS) { return DCARD_ROWS; } + return d; +} + +/// The text size of a band row's name of len bytes: why_card's fit, capped for the lower row. +pub fn dcard_name_font(len: usize) -> usize { + const f : usize = card_font(len); + if (f > DCARD_ROW_FONT_MAX) { return DCARD_ROW_FONT_MAX; } + return f; +} + +// ---- reading the walks ---- + +/// Can the first DCARD_ROWS lines of set[lo..hi] be drawn. +pub fn dcard_band_ok(set: []const u8, lo: usize, hi: usize) -> bool { + var r : usize = 0; + var i : usize = lo; + while (i < hi and r < DCARD_ROWS) { + const e : usize = line_end(set, i, hi); + if (card_name_ok(set, i, e) == false) { return false; } + r += 1; + i = e + 1; + } + return true; +} + +// ---- writing the graph ---- + +/// One band: its label with the direct count d, its first rows of set[lo..hi], and `+N more`. +pub fn dcard_put_band(out: []u8, w: usize, cap: usize, font: []const u8, fnl: usize, set: []const u8, lo: usize, hi: usize, d: usize, label: []const u8, label_y: usize, row_y: usize, more_y: usize) -> usize { + const ok : u32 = VIEW_LABEL_STROKE[0]; + const tint : u32 = VIEW_LABEL_FILL[0]; + const muted : u32 = VIEW_LABEL_STROKE[3]; + var p : usize = card_text_open(out, w, cap, font, fnl, DCARD_BAND_FONT); + p = deps_put_lit(out, p, cap, label); + p = deps_put_dec(out, p, cap, d); + p = card_text_close(out, p, cap, muted, CARD_BOX_X, label_y); + var r : usize = 0; + var i : usize = lo; + while (i < hi and r < DCARD_ROWS) { + const e : usize = line_end(set, i, hi); + const y : usize = row_y + r * dcard_row_step(); + p = card_put_box(out, p, cap, CARD_BOX_X, y, CARD_BOX_W, DCARD_ROW_H, tint, 0); + p = card_put_box(out, p, cap, CARD_BOX_X, y, CARD_BOX_W, DCARD_ROW_H, ok, CARD_BORDER); + const a : usize = card_name_from(set, i, e); + const b : usize = card_name_to(set, a, e); + p = card_text_open(out, p, cap, font, fnl, dcard_name_font(b - a)); + p = deps_put_span(out, p, cap, set, a, b); + p = card_text_close(out, p, cap, VIEW_TEXT, 0, y + DCARD_ROW_H / 2); + r += 1; + i = e + 1; + } + if (d > DCARD_ROWS) { + p = card_text_open(out, p, cap, font, fnl, DCARD_BAND_FONT); + p = put(out, p, cap, '+'); + p = deps_put_dec(out, p, cap, d - DCARD_ROWS); + p = deps_put_lit(out, p, cap, DCARD_MORE); + p = card_text_close(out, p, cap, muted, CARD_BOX_X, more_y); + } + return p; +} + +/// A `uses` connector from y0 down to y1 on the card's middle line. +pub fn dcard_put_uses(out: []u8, w: usize, cap: usize, font: []const u8, fnl: usize, y0: usize, y1: usize) -> usize { + const ok : u32 = VIEW_LABEL_STROKE[0]; + var p : usize = card_put_box(out, w, cap, CARD_W / 2 - CARD_LINE_W / 2, y0, CARD_LINE_W, y1 - y0, ok, 0); + p = card_text_open(out, p, cap, font, fnl, CARD_LABEL_FONT); + p = deps_put_lit(out, p, cap, CARD_USES); + p = card_text_close(out, p, cap, ok, CARD_W / 2 + CARD_LABEL_DX, (y0 + y1) / 2); + return p; +} + +/// The ffmpeg filter graph of the card for focus[ff..ft] over the edge list buf[0..n], into +/// out[0..cap]. imp/ib and rev/rb hold the two walks (DEPS_BOUNDS entries each). Returns its length, +/// 0 when nothing is drawn (the focus is on no line, a walk is refused, a shown name or the font path +/// font[0..fnl] cannot be drawn), or cap + 1 when the graph does not fit. +pub fn dcard_graph(buf: []const u8, n: usize, focus: []const u8, ff: usize, ft: usize, font: []const u8, fnl: usize, imp: []u8, icap: usize, ib: []usize, rev: []u8, rcap: usize, rb: []usize, out: []u8, cap: usize) -> usize { + if (fnl == 0) { return 0; } + var fi : usize = 0; + while (fi < fnl) { + if (card_quotable(font[fi]) == false) { return 0; } + fi += 1; + } + if (card_name_ok(focus, ff, ft) == false) { return 0; } + if (deps_walk(buf, n, focus, ff, ft, DEPS_IMPORTS, DEPS_MAX_DEPTH, imp, icap, ib) != DEPS_OK) { return 0; } + if (deps_walk(buf, n, focus, ff, ft, DEPS_IMPORTERS, DEPS_MAX_DEPTH, rev, rcap, rb) != DEPS_OK) { return 0; } + const isn : usize = ib[deps_depth(ib, DEPS_MAX_DEPTH) + 1]; + const rsn : usize = rb[deps_depth(rb, DEPS_MAX_DEPTH) + 1]; + const ti : usize = deps_count(imp, 0, isn) - 1; + const tr : usize = deps_count(rev, 0, rsn) - 1; + if (ti == 0 and tr == 0) { return 0; } + const di : usize = deps_count(imp, ib[1], ib[2]); + const dr : usize = deps_count(rev, rb[1], rb[2]); + if (dcard_band_ok(imp, ib[1], ib[2]) == false or dcard_band_ok(rev, rb[1], rb[2]) == false) { return 0; } + const ok : u32 = VIEW_LABEL_STROKE[0]; + const gold : u32 = VIEW_LABEL_STROKE[1]; + const tint : u32 = VIEW_LABEL_FILL[0]; + const muted : u32 = VIEW_LABEL_STROKE[3]; + var w : usize = deps_put_lit(out, 0, cap, "color=c=$"); + w = card_put_hex(out, w, cap, VIEW_BACKDROP); + w = deps_put_lit(out, w, cap, ":s=$"); + w = deps_put_dec(out, w, cap, CARD_W); + w = put(out, w, cap, 'x'); + w = deps_put_dec(out, w, cap, CARD_H); + w = deps_put_lit(out, w, cap, ":d=1$"); + w = card_text_open(out, w, cap, font, fnl, CARD_HEAD_FONT); + w = deps_put_lit(out, w, cap, DCARD_HEAD); + w = card_text_close(out, w, cap, ok, 0, CARD_HEAD_Y); + w = card_text_open(out, w, cap, font, fnl, CARD_SUB_FONT); + if (deps_on_cycle(buf, n, focus, ff, ft, imp, isn)) { + w = deps_put_lit(out, w, cap, DCARD_CYCLE); + w = card_text_close(out, w, cap, gold, 0, CARD_SUB_Y); + } else { + w = deps_put_lit(out, w, cap, DCARD_SUB); + w = card_text_close(out, w, cap, muted, 0, CARD_SUB_Y); + } + w = dcard_put_band(out, w, cap, font, fnl, rev, rb[1], rb[2], dr, DCARD_UP, DCARD_UP_LABEL_Y, DCARD_UP_ROW_Y, DCARD_UP_MORE_Y); + if (dr > 0) { + w = dcard_put_uses(out, w, cap, font, fnl, dcard_band_bottom(DCARD_UP_ROW_Y, dcard_shown(dr)), DCARD_FOCUS_Y); + } + w = card_put_box(out, w, cap, CARD_BOX_X, DCARD_FOCUS_Y, CARD_BOX_W, DCARD_FOCUS_H, tint, 0); + w = card_put_box(out, w, cap, CARD_BOX_X, DCARD_FOCUS_Y, CARD_BOX_W, DCARD_FOCUS_H, ok, DCARD_FOCUS_BORDER); + const a : usize = card_name_from(focus, ff, ft); + const b : usize = card_name_to(focus, a, ft); + w = card_text_open(out, w, cap, font, fnl, card_font(b - a)); + w = deps_put_span(out, w, cap, focus, a, b); + w = card_text_close(out, w, cap, VIEW_TEXT, 0, DCARD_FOCUS_Y + DCARD_FOCUS_H / 2); + if (di > 0) { + w = dcard_put_uses(out, w, cap, font, fnl, DCARD_FOCUS_Y + DCARD_FOCUS_H, DCARD_DOWN_ROW_Y); + } + w = dcard_put_band(out, w, cap, font, fnl, imp, ib[1], ib[2], di, DCARD_DOWN, DCARD_DOWN_LABEL_Y, DCARD_DOWN_ROW_Y, DCARD_DOWN_MORE_Y); + w = card_text_open(out, w, cap, font, fnl, CARD_FOOT_FONT); + w = deps_put_lit(out, w, cap, DCARD_REACHES); + w = deps_put_dec(out, w, cap, ti); + w = deps_put_lit(out, w, cap, DCARD_REACHED); + w = deps_put_dec(out, w, cap, tr); + w = deps_put_lit(out, w, cap, DCARD_FOOT); + w = card_text_close(out, w, cap, muted, 0, CARD_FOOT_Y); + w = card_text_open(out, w, cap, font, fnl, CARD_SITE_FONT); + w = deps_put_lit(out, w, cap, CARD_SITE); + w = card_text_close(out, w, cap, ok, 0, CARD_SITE_Y); + w = deps_put_lit(out, w, cap, "[v]\n$"); + if (w > cap) { return cap + 1; } + return w; +} + +// ---- main ---- + +pub fn main() -> void { + var edges : [262144]u8 = undefined; + var n : usize = 0; + var r : isize = 1; + while (r > 0 and n < 262144) { + r = std.c.read(0, edges[n..262144].ptr, 262144 - n); + if (r > 0) { + n += @as(usize, @intCast(r)); + } + } + // The two settings, read in place: main is the only reader of the environment, and the tests, + // which never link libc, do not reach getenv. + var spec : [256]u8 = undefined; + var sn : usize = 0; + const sp = std.c.getenv("DEPS_SPEC"); + if (sp != null) { + const s = sp.?; + while (sn < 256 and s[sn] != 0) { + spec[sn] = s[sn]; + sn += 1; + } + } + var font : [256]u8 = undefined; + var fnl : usize = 0; + const fp = std.c.getenv("DEPS_FONT"); + if (fp != null) { + const f = fp.?; + while (fnl < 256 and f[fnl] != 0) { + font[fnl] = f[fnl]; + fnl += 1; + } + } + var imp : [65536]u8 = undefined; + var rev : [65536]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var g : [32768]u8 = undefined; + if (n >= 262144 or sn == 0 or fnl == 0) { + std.debug.print("deps_card: need the edge list on stdin (under 256 KiB), DEPS_SPEC and DEPS_FONT\n", .{}); + return; + } + const gw : usize = dcard_graph(&edges, n, &spec, 0, sn, &font, fnl, &imp, 65536, &ib, &rev, 65536, &rb, &g, 32768); + if (gw == 0 or gw > 32768) { + std.debug.print("deps_card: not drawn\n", .{}); + return; + } + std.debug.print("{s}", .{g[0..gw]}); +} + +// ---- tests ---- + +pub const DCARD_WIDE : str = "h\tm1\nh\tm2\nh\tm3\nh\tm4\nh\tm5\nu1\th\nu2\th\nu3\th\nu4\th\n$"; +pub const DCARD_THREE : str = "h\tm1\nh\tm2\nh\tm3\nu1\th\nu2\th\nu3\th\n$"; + +test dcard_draws_both_bands_and_the_focus { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + const w : usize = dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w > 0; + assert w <= 8192; + assert card_count(&out, w, ",drawbox=$") == 10; + assert card_count(&out, w, ",drawtext=$") == 12; + assert card_count(&out, w, "text='a'$") == 1; + assert card_count(&out, w, "text='e'$") == 1; + assert card_count(&out, w, "text='b'$") == 1; + assert card_count(&out, w, "text='c'$") == 1; + assert card_count(&out, w, "text='d'$") == 0; + assert card_count(&out, w, "text='f'$") == 0; + assert card_count(&out, w, "text='uses'$") == 2; + assert card_count(&out, w, "text='IMPORTED BY 1'$") == 1; + assert card_count(&out, w, "text='IMPORTS 2'$") == 1; + assert card_count(&out, w, "text='reaches 3 / reached by 2 / every edge exact'$") == 1; + assert card_count(&out, w, "text='imports and importers t27c resolved'$") == 1; + assert card_count(&out, w, "CYCLE$") == 0; + assert card_count(&out, w, " more'$") == 0; + assert card_count(&out, w, ":color=0x00FF88:t=8$") == 1; + assert card_count(&out, w, ":color=0x00FF88:t=4$") == 3; + assert card_count(&out, w, ":color=0x0C2A1B:t=fill$") == 4; + assert card_count(&out, w, "color=c=0x0B0D0C:s=1080x1350:d=1$") == 1; + assert card_count(&out, w, "[v]\n$") == 1; +} + +test dcard_bands_follow_the_edge_list_order { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + const w : usize = dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w > 0; + const yb : str = "text='b':fontcolor=0xFFFFFF:x=(w-text_w)/2:y=852-text_h/2$"; + const yc : str = "text='c':fontcolor=0xFFFFFF:x=(w-text_w)/2:y=928-text_h/2$"; + const ye : str = "text='e':fontcolor=0xFFFFFF:x=(w-text_w)/2:y=312-text_h/2$"; + assert card_count(&out, w, yb) == 1; + assert card_count(&out, w, yc) == 1; + assert card_count(&out, w, ye) == 1; +} + +test dcard_a_band_past_its_rows_says_how_many_more { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DCARD_WIDE; + const w : usize = dcard_graph(g, lit_len(g), "h$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w > 0; + assert card_count(&out, w, "text='m1'$") == 1; + assert card_count(&out, w, "text='m3'$") == 1; + assert card_count(&out, w, "text='m4'$") == 0; + assert card_count(&out, w, "text='m5'$") == 0; + assert card_count(&out, w, "text='u3'$") == 1; + assert card_count(&out, w, "text='u4'$") == 0; + assert card_count(&out, w, "text='+2 more'$") == 1; + assert card_count(&out, w, "text='+1 more'$") == 1; + assert card_count(&out, w, "text='IMPORTED BY 4'$") == 1; + assert card_count(&out, w, "text='IMPORTS 5'$") == 1; + assert card_count(&out, w, "text='reaches 5 / reached by 4 / every edge exact'$") == 1; + assert card_count(&out, w, ":color=0x00FF88:t=4$") == 6; + const t : str = DCARD_THREE; + const w3 : usize = dcard_graph(t, lit_len(t), "h$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w3 > 0; + assert card_count(&out, w3, " more'$") == 0; + assert card_count(&out, w3, ":color=0x00FF88:t=4$") == 6; +} + +test dcard_counts_a_spec_reached_two_ways_once { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + const w : usize = dcard_graph(g, lit_len(g), "d$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w > 0; + assert card_count(&out, w, "text='IMPORTED BY 2'$") == 1; + assert card_count(&out, w, "text='IMPORTS 0'$") == 1; + assert card_count(&out, w, "text='reaches 0 / reached by 5 / every edge exact'$") == 1; + assert card_count(&out, w, "text='uses'$") == 1; + assert card_count(&out, w, ",drawbox=$") == 7; +} + +test dcard_flags_a_focus_on_an_import_cycle { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const c : str = "p\tq\nq\tp\nq\tr\n$"; + const w : usize = dcard_graph(c, lit_len(c), "p$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w > 0; + assert card_count(&out, w, "text='ON AN IMPORT CYCLE':fontcolor=0xFFD700$") == 1; + assert card_count(&out, w, "text='imports and importers t27c resolved'$") == 0; + assert card_count(&out, w, "text='q'$") == 2; + const r : usize = dcard_graph(c, lit_len(c), "r$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert r > 0; + assert card_count(&out, r, "CYCLE$") == 0; +} + +test dcard_every_band_fits_the_card_without_overlap { + const half : usize = DCARD_BAND_FONT / 2; + assert CARD_SUB_Y + CARD_SUB_FONT / 2 < DCARD_UP_LABEL_Y - half; + assert DCARD_UP_LABEL_Y + half < DCARD_UP_ROW_Y; + assert dcard_band_bottom(DCARD_UP_ROW_Y, DCARD_ROWS) < DCARD_UP_MORE_Y - half; + assert DCARD_UP_MORE_Y + half < DCARD_FOCUS_Y; + assert DCARD_FOCUS_Y + DCARD_FOCUS_H < DCARD_DOWN_LABEL_Y - half; + assert DCARD_DOWN_LABEL_Y + half < DCARD_DOWN_ROW_Y; + assert dcard_band_bottom(DCARD_DOWN_ROW_Y, DCARD_ROWS) < DCARD_DOWN_MORE_Y - half; + assert DCARD_DOWN_MORE_Y + half < CARD_FOOT_Y - CARD_FOOT_FONT / 2; + var shown : usize = 1; + while (shown <= DCARD_ROWS) { + assert dcard_band_bottom(DCARD_UP_ROW_Y, shown) + CARD_GAP_MIN <= DCARD_FOCUS_Y; + shown += 1; + } + assert DCARD_FOCUS_Y + DCARD_FOCUS_H + CARD_GAP_MIN <= DCARD_DOWN_ROW_Y; + assert dcard_band_bottom(DCARD_UP_ROW_Y, 0) == DCARD_UP_ROW_Y; + var len : usize = 1; + while (len <= CARD_NAME_MAX) { + const fs : usize = dcard_name_font(len); + assert fs >= CARD_FONT_MIN; + assert fs * len * 3 <= (CARD_BOX_W - 2 * CARD_PAD) * 5; + assert fs + 2 * CARD_BORDER < DCARD_ROW_H; + len += 1; + } + const foot : usize = lit_len(DCARD_REACHES) + 4 + lit_len(DCARD_REACHED) + 4 + lit_len(DCARD_FOOT); + assert foot * CARD_FOOT_FONT * 3 <= CARD_W * 5; +} + +test negative_control_a_spec_not_in_the_list_is_not_drawn { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + assert dcard_graph(g, lit_len(g), "z$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; + assert dcard_graph(g, 0, "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; + assert dcard_graph(g, lit_len(g), "y$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) > 0; +} + +test negative_control_a_refused_walk_is_not_drawn { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) > 0; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 5, &ib, &rev, 256, &rb, &out, 8192) == 0; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 3, &rb, &out, 8192) == 0; +} + +test negative_control_a_shown_name_the_graph_cannot_draw_is_not_drawn { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const up : str = "a\tb\nx:y\ta\n$"; + assert dcard_graph(up, lit_len(up), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; + const down : str = "a\tb'\n$"; + assert dcard_graph(down, lit_len(down), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; + const hidden : str = "h\tm1\nh\tm2\nh\tm3\nh\tm:4\n$"; + const w : usize = dcard_graph(hidden, lit_len(hidden), "h$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert w > 0; + assert card_count(&out, w, "text='+1 more'$") == 1; + const shown : str = "h\tm1\nh\tm:2\nh\tm3\nh\tm4\n$"; + assert dcard_graph(shown, lit_len(shown), "h$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; + const focus : str = "a:b\tc\n$"; + assert dcard_graph(focus, lit_len(focus), "a:b$", 0, 3, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; +} + +test negative_control_a_card_that_does_not_fit_is_refused_whole { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + const full : usize = dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, 8192); + assert full > 0; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full) == full; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full - 1) == full; +} + +test negative_control_a_font_path_the_graph_cannot_quote_is_not_drawn { + var imp : [256]u8 = undefined; + var rev : [256]u8 = undefined; + var ib : [35]usize = undefined; + var rb : [35]usize = undefined; + var out : [8192]u8 = undefined; + const g : str = DEPS_FIXTURE; + const bad : str = "/fonts/a b.ttf$"; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, bad, lit_len(bad), &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, 0, &imp, 256, &ib, &rev, 256, &rb, &out, 8192) == 0; +} From b4da58493a0a16bec07c74866df3a0c2a0cc8f4f Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 06:59:18 +0700 Subject: [PATCH 2/4] t27f: deps card -- a fit test ten bytes short (Refs #10720) Co-Authored-By: Claude Opus 5.5 --- specs/t27f/deps_card.t27 | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/t27f/deps_card.t27 b/specs/t27f/deps_card.t27 index b3469d2540..ecd95984bd 100644 --- a/specs/t27f/deps_card.t27 +++ b/specs/t27f/deps_card.t27 @@ -484,6 +484,7 @@ test negative_control_a_card_that_does_not_fit_is_refused_whole { assert full > 0; assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full) == full; assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full - 1) == full; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full - 10) == full - 9; } test negative_control_a_font_path_the_graph_cannot_quote_is_not_drawn { From 2a56cabab766e6d10173b7c57da2967388eb42dc Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 07:01:28 +0700 Subject: [PATCH 3/4] t27f: deps card -- the fit test three bytes short, where a looser bound would differ (Refs #10720) Co-Authored-By: Claude Opus 5.5 --- specs/t27f/deps_card.t27 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/t27f/deps_card.t27 b/specs/t27f/deps_card.t27 index ecd95984bd..f5a262ddda 100644 --- a/specs/t27f/deps_card.t27 +++ b/specs/t27f/deps_card.t27 @@ -484,7 +484,7 @@ test negative_control_a_card_that_does_not_fit_is_refused_whole { assert full > 0; assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full) == full; assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full - 1) == full; - assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full - 10) == full - 9; + assert dcard_graph(g, lit_len(g), "a$", 0, 1, CARD_TEST_FONT, lit_len(CARD_TEST_FONT), &imp, 256, &ib, &rev, 256, &rb, &out, full - 3) == full - 2; } test negative_control_a_font_path_the_graph_cannot_quote_is_not_drawn { From 9955c7863a567772b5fd9a4fea0b752a8df3510f Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 07:07:47 +0700 Subject: [PATCH 4/4] t27f: deps card seals, and the skill section with four lab traps (Closes #10720) Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27f/SKILL.md | 45 ++++++++++++++++ .../seals/skills_skill_t27_deps_card.json | 28 ++++++++++ .trinity/seals/t27f_t27f_deps_card.json | 53 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 .trinity/seals/skills_skill_t27_deps_card.json create mode 100644 .trinity/seals/t27f_t27f_deps_card.json diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 7c9107c91a..a74741b4aa 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -434,6 +434,51 @@ Traps: until a fixture had fewer edges than `hops=` *and* still ended at B. Give every refusal a fixture that only that refusal catches. +### Draw who uses a spec as a card (t27#10720) + +`specs/t27f/deps_card.t27` draws one spec's neighbourhood. Its first three direct importers sit +above it and its first three direct imports below it, with `+N more` past three and the +transitive counts in the footer. A gold `ON AN IMPORT CYCLE` line appears when `deps_on_cycle` +holds. It imports why_card's `card_*` helpers rather than copying them, and its main reads +`DEPS_SPEC` and `DEPS_FONT`. Skill card: `t27/deps-card`. To build and render it, follow the +why-card recipe above with `deps_card` in place of `why_card`. + +Pixel checks per row: + +- fill at (106, y+6); +- stroke at (101, y+32); +- fill at both inner edges (110 and 970); +- backdrop in the gap at (300, y+70). + +Rows start at y = 280 + 76k above the focus and 820 + 76k below it. Then check: + +- the focus fill at (112, 650) and its 8 px stroke at (103, 688); +- the connector at (540, 630) above and (540, 745) below, or the backdrop at those points when a + band is empty; +- the white-pixel count in every row's text band. + +On 255910235 (2483 edge lines, 813 specs named): + +- 783 cards drew. `base/types` is the hub: 378 direct importers, reached by 400. +- 30 were refused: + - 29 because the spec's own name is over 58 bytes (`port/trios/...`); + - 1 because a shown neighbour's name is. +- No spec in the corpus is on an import cycle. +- 70 of 70 pixel checks held on the hub, leaf and mid cards. + +Traps: + +- **A shell helper that sets `tag` clobbers the caller's `tag`.** POSIX `sh` has no `local`: + `rows()` setting `tag=$4` sent every later check to `up.png`, which does not exist. Name each + helper's variables apart. +- **`sort -t "$TAB" -k3,3n -r` sorts ascending.** A key with its own ordering flag does not + inherit `-r`, so write `-k3,3nr`. In `sh`, `$'\t'` is two characters; use `TAB=$(printf '\t')`. +- **A one-byte-short fit test cannot tell `w > cap` from `w > cap + k`.** Both return `cap + 1` + there. The original and the mutant only differ when the room is 2..k+1 bytes short, so the + test that kills the mutant is three bytes short, not ten. +- **Bands are in edge-list order, so a hub shows `account/*` first.** That is the stated rule, + not a ranking; do not caption the first three as the most important. + ## What this skill does not claim The graph IR is a schema (`specs/t27f/spec_graph.t27`) and `specs/t27f/spec_graph_export.t27` turns source into it, but only exact declarations, spans and edges within the IR's capacity, with `specs/t27f/spec_graph_derive.t27` adding the derived and unknown part by stated rules, and no host calls either yet; no renderer draws a T27F view in the Trinity UI, no diff --git a/.trinity/seals/skills_skill_t27_deps_card.json b/.trinity/seals/skills_skill_t27_deps_card.json new file mode 100644 index 0000000000..10c6104843 --- /dev/null +++ b/.trinity/seals/skills_skill_t27_deps_card.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:416288691c04ceaabc9f206bafc063de6a4fe2bf76eed4cec1e8ed7facbd23a4", + "gen_hash_rust": "sha256:1c2f00377a1c61e676f3ce92dcb7f9c0bcf7ef10750b16f7ad3b5b88b75c15a9", + "gen_hash_verilog": "sha256:4158a6d406b22bfef789ca15dc84c3d867e96e0ccb4aaca4d536fed85da9c8cd", + "gen_hash_zig": "sha256:fb59aa9ded647f4e34da11c01542d1a5ee40454dee7a5f36d7fb1379269b45b0", + "module": "skill_t27_deps_card", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T00:03:42Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:2a603711c22872cdbd2f5898ac5980e9b920cd85857f881d6f40f482996f4cf2", + "spec_path": "specs/skills/t27-deps-card.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 1, + "total": 1, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27f_t27f_deps_card.json b/.trinity/seals/t27f_t27f_deps_card.json new file mode 100644 index 0000000000..65d4dbd6cb --- /dev/null +++ b/.trinity/seals/t27f_t27f_deps_card.json @@ -0,0 +1,53 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", + "closure": [ + { + "spec": "specs/ci/affected.t27", + "spec_hash": "sha256:38a7bdc245f1974f0d5f1a55a45966decd70098afdb89e882f568da3650ee9f9" + }, + { + "spec": "specs/claw/t27f_gate.t27", + "spec_hash": "sha256:856be049f9ef3de20cce2f7d18ca3b78a5f4a8ad660dddd8f2cf1aa97bc61ee7" + }, + { + "spec": "specs/policy/text_lines.t27", + "spec_hash": "sha256:13c90895a4a44c760cce6b3b65f72f937298633062f040f8489486108ab0c2d1" + }, + { + "spec": "specs/t27f/spec_deps.t27", + "spec_hash": "sha256:c6e6c03537dd7238ce7cceb937dfe307294ab59d4946de95f7d96e40333cb070" + }, + { + "spec": "specs/t27f/spec_graph_view.t27", + "spec_hash": "sha256:d4c318162bf52405208d4016d0505c8037cd648b58feb3ad5240a2695c6730fe" + }, + { + "spec": "specs/t27f/why_card.t27", + "spec_hash": "sha256:01381c902fe4b5702dc26505a0d081faa0895bfaf60ef51a529cfef894a8ae68" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:e0771c20aea80a2d085ea8151707616be36da0a1075f48dcc81471b21dfb3624", + "gen_hash_rust": "sha256:8b918de8a45d5fb657999906ab375c03b0f4f75289bb4662a1d34143ba99886d", + "gen_hash_verilog": "sha256:ed6c7559eb3bb422722770679c14d41c53a6a56fe3521d444cdfc408b22eb66c", + "gen_hash_zig": "sha256:eecc2a4bc35cf81dff2bde43f15e53903728aa66d5898de7c916e60fefff1c38", + "module": "t27f_deps_card", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T00:03:40Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:05e744da11ef490c883a7a46c9d5ee7869a1b21f50f1999e6dda0706d68b5f0d", + "spec_path": "specs/t27f/deps_card.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 11, + "total": 11, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file