Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
"source": {
"source": "github",
"repo": "dravensoft-dev/arena",
"ref": "v11.0.2"
"ref": "v11.0.3"
},
"description": "Arena, Dravensoft's design system, as an Agent Skill: design tokens, React and Angular primitives, a shared Tailwind layer, guidelines, and a style plugin any project can replace. It carries the design language and not the skin, so any project brings its own palette and fonts.",
"version": "11.0.2",
"version": "11.0.3",
"author": {
"name": "Dravensoft"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "arena",
"displayName": "Arena — Dravensoft Design System",
"version": "11.0.2",
"version": "11.0.3",
"description": "Build interfaces with Arena, a token-driven design system: design tokens, React and Angular component libraries, a shared Tailwind layer, guidelines, and a style plugin any project can replace. It carries the design language and not the skin, so any project brings its own palette and fonts.",
"author": {
"name": "Dravensoft"
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ two copies and never reads either for meaning. Verify with
are allowed, in a gate's own reason string, or in the component's `.prompt.md`. **Somewhere a
stale copy of it fails something.**

`bun run check:docs` holds the size rule, the punctuation rule and the comment rule, and
`bun run check:docs` holds the size rule, for a document and for a table cell, the punctuation
rule and the comment rule, and
`bun run check:citations` holds every path that prose names to existing. **The present-tense rule
is the one no gate holds**, because nothing mechanical can judge it.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ package publishes only when something it ships changed.
that means for an upgrade.

## Latest project artifacts
- **Repo/Claude Code plugin**: 11.0.2
- **Repo/Claude Code plugin**: 11.0.3
- [npm React package](https://www.npmjs.com/package/@dravensoft/arena-react?activeTab=versions)
- [npm Angular package](https://www.npmjs.com/package/@dravensoft/arena-angular?activeTab=versions)
- [npm contracts package](https://www.npmjs.com/package/@dravensoft/arena-contracts?activeTab=versions)
Expand Down
5 changes: 5 additions & 0 deletions scripts/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ stdin or are imported.
silently, because the wrong path still exists. That module is
the one place that counts, which is why moving *it* is the one move needing care.

**Never spell what a walk skips because it is not this repository.** Compose
`withForeignTrees(...)` from `lib/arena/foreign-trees.ts` and add only what the walk skips for
its own reasons. A skip set spelled per walk goes short in some of them, and a walk missing
`.claude` reads a git worktree there as a second copy of the tree.

**A library never imports a gate.** `lib/` is the bottom of the graph: `arena/layers.ts`,
`core/arena-tokens.ts` and the rest are there because more than one gate reads them, and a
gate reaching down is the only direction allowed. Across domains the same holds in both
Expand Down
3 changes: 2 additions & 1 deletion scripts/build/arena/build-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
HERO_SHEET, HERO_SOURCE, HERO_FILE, HERO_WIDTH, HERO_HEIGHT, heroPage, heroStyles,
} from '../../lib/arena/hero-page.ts';
import { LLMS_INDEX, layerFile, index, corpus, servedDocs } from '../../lib/arena/llms-index.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'build:site',
Expand All @@ -50,7 +51,7 @@ export const OG_SOURCE = 'og.html';
export const OG_WIDTH = 1200;
export const OG_HEIGHT = 630;

export const NEVER_COPIED = new Set(['node_modules', '.git']);
export const NEVER_COPIED = withForeignTrees();

export const BENCHES_REPOSITORY = 'https://github.com/dravensoft-dev/arena-web-benches';

Expand Down
42 changes: 21 additions & 21 deletions scripts/check/arena/AGENTS.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion scripts/check/arena/check-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { relPosix } from '../../utils/posix-path.ts';
import { isMainModule } from '../../utils/main-module.ts';
import { walkFiles } from '../../utils/walk-files.ts';
import { repoRoot as root } from '../../lib/arena/repo-root.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const ROUTER = 'AGENTS.md';

Expand All @@ -23,7 +24,7 @@ export const SURVIVORS = new Map([
+ 'to neither.'],
]);

export const SKIPPED_ANYWHERE = new Set(['node_modules', '.git', '.claude']);
export const SKIPPED_ANYWHERE = withForeignTrees();
export const SKIPPED_UNDER_FRAMEWORKS = new Set(['dist', 'vendor']);

export const LINK = /\]\(([^)\s]+)/g;
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { blankComments, expressionLeaves, readValue, skipString } from './check-
import { HAND_DRAWN, categoryOf, inScope, manifestFor } from '../../lib/tailwind/manifest-surfaces.ts';
import { kebab } from '../../utils/case.ts';
import { repoRoot } from '../../lib/arena/repo-root.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:appearance',
Expand All @@ -34,7 +35,7 @@ export const EXEMPT = new Map<string, string>([]);
const REACT_COMPONENTS = join(repoRoot, 'frameworks/react/components');
const ANGULAR_COMPONENTS = join(repoRoot, 'frameworks/angular/components');

const SKIPPED_DIRECTORIES = new Set(['dist', 'node_modules']);
const SKIPPED_DIRECTORIES = withForeignTrees('dist');
const SKIPPED_INFIXES = ['.test.', '.generated.', '.demo.'];
const SOURCE_EXTENSIONS = ['.tsx', '.jsx', '.ts'];

Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-citations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import { findComments } from '../../lib/arena/comments.ts';
import { repoRoot as root } from '../../lib/arena/repo-root.ts';
import { allowsHeader } from './check-docs.ts';
import { strands } from './check-contracts-neutrality.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const SKIPPED_ANYWHERE = new Set(['node_modules', '.git', '.claude']);
export const SKIPPED_ANYWHERE = withForeignTrees();

export const SKIPPED_UNDER_FRAMEWORKS = new Set(['dist', 'build', 'vendor']);

Expand Down
20 changes: 19 additions & 1 deletion scripts/check/arena/check-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
isConsumerDocument, BRANCH_SWITCH, branchSwitchProblems,
RULE_OWNERS, CONTRIBUTOR_BRANCH, ruleOwnerProblems, statesRule, CONSUMER_OWN_OUTPUT,
FOREIGN_CODE, foreignCodeProblems, componentCountProblems,
COMMENT_RULE_SKIPS, SOURCE_EXTENSIONS,
COMMENT_RULE_SKIPS, SOURCE_EXTENSIONS, MAX_CELL_CHARS, cellSizeProblems,
} from './check-docs.ts';

function tree(files: Record<string, string>) {
Expand Down Expand Up @@ -64,6 +64,24 @@ test('both document rules report how many documents they actually read', () => {
rmSync(root, { recursive: true });
});

test('a table cell over its limit is reported by line, and a fenced table or a long line is not a cell', () => {
const cell = (n: number) => 'x'.repeat(n);
const root = tree({
'AGENTS.md': [
'| gate | fails when |', '| --- | --- |',
`| \`a.ts\` | ${cell(MAX_CELL_CHARS)} |`,
`| \`b.ts\` | ${cell(MAX_CELL_CHARS + 1)} |`,
`| \`c.ts\` | ${cell(10)} \\| ${cell(MAX_CELL_CHARS - 20)} |`,
'', '```', `| ${cell(MAX_CELL_CHARS + 1)} |`, '```', '', cell(MAX_CELL_CHARS * 2), '',
].join('\n'),
});
const { problems, cells } = cellSizeProblems(root);
assert.equal(problems.length, 1);
assert.match(problems[0] ?? '', /^AGENTS\.md:4: a table cell of 2001 characters, over the 2000 limit/);
assert.equal(cells, 10, 'an escaped pipe stays inside its cell and a fence holds no table');
rmSync(root, { recursive: true });
});

test('SIZE_ALLOWANCE is empty, and that emptiness is the claim', () => {
assert.deepEqual([...SIZE_ALLOWANCE.keys()], []);
assert.equal(limitFor('AGENTS.md'), MAX_DOCUMENT_CHARS);
Expand Down
60 changes: 46 additions & 14 deletions scripts/check/arena/check-docs.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
/* Holds the documentation norms nothing else checks: every .md under
* MAX_DOCUMENT_CHARS unless SIZE_ALLOWANCE raises it, no banned punctuation in
* a document's prose, the comment rule, which lets scripts and tests carry one
* header of at most HEADER_MAX_LINES and every other hand-written source none,
* the branch boundary, which keeps a contributor path out of a consumer's last
* stop and, through RULE_OWNERS, a rule off the branch that does not own it,
* FOREIGN_CODE, which keeps another design system out of the examples a reader
* copies, and the claim a consumer page makes about what SHIPS: how many
* components. SIZE_ALLOWANCE is empty, and that emptiness is the claim: a
* document falling back inside the shared limit fails. */
/* Holds the documentation norms nothing else checks: every .md under MAX_DOCUMENT_CHARS
* unless SIZE_ALLOWANCE raises it, every table cell under MAX_CELL_CHARS since a document
* cap does not cap a row, no banned punctuation in prose, the comment rule, which lets
* scripts and tests carry one header of at most HEADER_MAX_LINES and every other
* hand-written source none, the branch boundary, which keeps a contributor path out of a
* consumer's last stop and, through RULE_OWNERS, a rule off the branch that does not own it,
* FOREIGN_CODE, which keeps another design system out of the examples a reader copies, and
* the claim a consumer page makes about what SHIPS: how many components. SIZE_ALLOWANCE is
* empty, and that emptiness is the claim: a document falling back inside the shared limit
* fails. */

import { readFileSync, existsSync } from 'node:fs';
import { join, basename } from 'node:path';
import { isMainModule } from '../../utils/main-module.ts';
import { walkFiles } from '../../utils/walk-files.ts';
import { readJson } from '../../utils/read-file.ts';
import { findComments } from '../../lib/arena/comments.ts';
import { proseSegments, fencedLines } from '../../lib/arena/markdown-prose.ts';
import { proseSegments, fencedLines, unfenced } from '../../lib/arena/markdown-prose.ts';
import { repoRoot as ROOT } from '../../lib/arena/repo-root.ts';
import { emittedTree } from '../../lib/arena/layers.ts';
import { relPosix } from '../../utils/posix-path.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const MAX_DOCUMENT_CHARS = 60_000;
export const MAX_CELL_CHARS = 2_000;
export const HEADER_MAX_LINES = 10;
export const QUOTED_RUN_CHARS = 90;

Expand Down Expand Up @@ -57,7 +59,7 @@ export const COMMENT_RULE_SKIPS = new Map([
+ 'and stripping it would take the specimen out of the pages that render it'],
]);
export const SCANNED_TREES = ['scripts', 'frameworks', '.github'];
const SKIPPED_DIRECTORIES = new Set(['node_modules', '.git', 'dist']);
const SKIPPED_DIRECTORIES = withForeignTrees('dist');

export const READ_DESPITE_THE_DOT = new Set(['.gitkeep', '.github']);

Expand Down Expand Up @@ -293,6 +295,34 @@ export function documentSizeProblems(root = ROOT, allowance = SIZE_ALLOWANCE) {
return { problems, scanned: scanned.length };
}

export const TABLE_ROW = /^ {0,3}\|/;

export function tableCells(source: string) {
return unfenced(source).split('\n').flatMap((text, index) => TABLE_ROW.test(text)
? text.trim().replace(/^\||\|$/g, '').split(/(?<!\\)\|/)
.map((cell) => ({ line: index + 1, chars: cell.trim().length }))
: []);
}

export function cellSizeProblems(root = ROOT) {
const problems = [];
let cells = 0;
for (const path of documents(root)) {
const rel = relPosix(root, path);
if (exempt(SIZE_EXEMPT, rel)) continue;
for (const { line, chars } of tableCells(readFileSync(path, 'utf8'))) {
cells += 1;
if (chars <= MAX_CELL_CHARS) continue;
problems.push(
`${rel}:${line}: a table cell of ${chars} characters, over the ${MAX_CELL_CHARS} limit. A `
+ 'document cap does not cap a row, and a cell that needs a paragraph is a level nobody '
+ 'wrote: keep the claim in the row and move the argument to where it is enforced',
);
}
}
return { problems, cells };
}

export function staleAllowanceProblems(sizes: Map<string, number>, allowance = SIZE_ALLOWANCE) {
const problems = [];
for (const [rel, { limit, reason }] of allowance) {
Expand Down Expand Up @@ -462,8 +492,9 @@ function main() {
});
const foreign = foreignCodeProblems();
const counts = componentCountProblems();
const cells = cellSizeProblems();
const problems = [
...empty, ...branchSwitchProblems(), ...sizes.problems, ...punctuation.problems,
...empty, ...branchSwitchProblems(), ...sizes.problems, ...cells.problems, ...punctuation.problems,
...comments.problems, ...branch.problems, ...ruleOwnerProblems(), ...foreign.problems,
...counts.problems,
];
Expand All @@ -475,7 +506,8 @@ function main() {
}
console.log(
`check-docs: ${sizes.scanned} document(s) inside their limit, ${MAX_DOCUMENT_CHARS} characters `
+ `bar ${SIZE_ALLOWANCE.size} on the record, and clear of `
+ `bar ${SIZE_ALLOWANCE.size} on the record, ${cells.cells} table cell(s) inside `
+ `${MAX_CELL_CHARS}, and clear of `
+ `banned punctuation; ${comments.scanned} hand-written source(s) hold to the comment rule; `
+ `${branch.scanned} consumer document(s) cite no contributor path, offer no foreign framework `
+ `in a fenced example, and neither branch states one of the other's ${RULE_OWNERS.length} `
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-duplicate-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { repoRoot as root } from '../../lib/arena/repo-root.ts';
import { emittedTree } from '../../lib/arena/layers.ts';
import { captured } from '../../utils/captures.ts';
import { relPosix } from '../../utils/posix-path.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:duplicate-constants',
Expand Down Expand Up @@ -61,7 +62,7 @@ export function numericConstants(source: string) {

const SCAN_EXT = new Set(['.js', '.jsx', '.ts', '.tsx']);

const SKIP_DIRS = new Set(['node_modules', 'vendor', 'dist']);
const SKIP_DIRS = withForeignTrees('vendor', 'dist');

export function sourceFiles(dir: string): string[] {
const emitted = emittedTree();
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { isMainModule } from '../../utils/main-module.ts';
import { walkFiles } from '../../utils/walk-files.ts';
import { findComments } from '../../lib/arena/comments.ts';
import { repoRoot as ROOT } from '../../lib/arena/repo-root.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:generated',
Expand All @@ -27,7 +28,7 @@ export const node = {


const SCANNED_EXTENSIONS = ['.mjs', '.jsx', '.tsx', '.ts', '.js', '.css'];
const SKIPPED_DIRECTORIES = new Set(['node_modules', '.git', 'build', 'dist', '.cache']);
const SKIPPED_DIRECTORIES = withForeignTrees('build', 'dist', '.cache');

export const UNMARKED = {
'intro/support.js':
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { relPosix } from '../../utils/posix-path.ts';
import { isMainModule } from '../../utils/main-module.ts';
import { walkFiles } from '../../utils/walk-files.ts';
import { repoRoot as ROOT } from '../../lib/arena/repo-root.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:icons',
Expand All @@ -27,7 +28,7 @@ export const PHOSPHOR = 'node_modules/@phosphor-icons/web/src';

export const SCANNED_ROOTS = ['frameworks', 'intro', 'contracts', 'docs'];
export const SCANNED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.html', '.json', '.md', '.css'];
export const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', 'build', '.git', 'vendor']);
export const SKIPPED_DIRECTORIES = withForeignTrees('dist', 'build', 'vendor');

export const ICON_TOKEN = /\bph(?:-[a-z0-9]+)+\b/g;

Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-layer-independence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { isMainModule } from '../../utils/main-module.ts';
import { walkFiles } from '../../utils/walk-files.ts';
import { LAYERS } from '../../lib/arena/layers.ts';
import { repoRoot as root } from '../../lib/arena/repo-root.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:layer-independence',
Expand Down Expand Up @@ -64,7 +65,7 @@ export const EXEMPT = new Map<string, string>([]);
const SCAN_EXT = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.json', '.css', '.html', '.md']);
export const MODULE_EXT = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs']);
export const REFERENCE_EXT = new Set([...MODULE_EXT, '.html']);
const SKIP_DIRS = new Set(['node_modules', 'vendor', 'build', 'dist']);
const SKIP_DIRS = withForeignTrees('vendor', 'build', 'dist');

export function layerFiles(layerDir: string): string[] {
return walkFiles(layerDir, { skip: (name) => SKIP_DIRS.has(name) })
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { kebab } from '../../utils/case.ts';
import { repoRoot } from '../../lib/arena/repo-root.ts';
import { classesManifest } from '../../lib/tailwind/component-css.ts';
import { categoryOf, coveredContracts, everyComponent, hasOwnManifest } from '../../lib/tailwind/manifest-surfaces.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:parts',
Expand All @@ -36,7 +37,7 @@ export const LAYERS = { react: '.tsx', angular: '.ts' } as const;

export type Layer = keyof typeof LAYERS;

const SKIPPED_DIRECTORIES = new Set(['dist', 'node_modules']);
const SKIPPED_DIRECTORIES = withForeignTrees('dist');
const SKIPPED_INFIXES = ['.test.', '.generated.', '.demo.', '.card.'];

const CLASS_SITE = {
Expand Down
3 changes: 2 additions & 1 deletion scripts/check/arena/check-playgrounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { memberEntries, fieldEntries } from '../../lib/arena/contract-shapes.ts'
import type { ComponentContract, MemberSpec, TypeContract } from '../../lib/arena/contract-shapes.ts';
import type { Fixture, FixtureChild } from '../../lib/arena/playground-model.ts';
import { captured } from '../../utils/captures.ts';
import { withForeignTrees } from '../../lib/arena/foreign-trees.ts';

export const node = {
name: 'check:playgrounds',
Expand Down Expand Up @@ -358,7 +359,7 @@ const CITING_TREES = LAYERS.map((layer) => join('frameworks', layer));
const PATH_LIKE = /frameworks\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+/g;
const PAGE_LIKE = /\b[A-Za-z][A-Za-z0-9]*\.(?:card|demo)(?:\.generated)?\.(?:html|json|entry\.tsx|entry\.ts)\b/g;

const CITING_SKIP = new Set(['node_modules', 'dist', 'build', 'vendor']);
const CITING_SKIP = withForeignTrees('dist', 'build', 'vendor');

const scanned = (dir: string) =>
(existsSync(dir) ? walkFiles(dir, { skip: (name) => CITING_SKIP.has(name) }) : []);
Expand Down
Loading
Loading