Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7a361c8
Fix React StrictMode causing double WebSocket connections
cloutiertyler Jan 13, 2026
673a985
Improve error message when table is not found
cloutiertyler Jan 13, 2026
3c42af9
Make person table public in basic-react template
cloutiertyler Jan 13, 2026
2296443
Replace --database flag with positional argument in spacetime dev
cloutiertyler Jan 12, 2026
7148ecf
Updated CLI docs
cloutiertyler Jan 13, 2026
7f50121
Update LLM benchmark results
clockwork-labs-bot Jan 13, 2026
b77ccbc
Update quickstart guides with table/reducer examples and CLI usage
cloutiertyler Jan 13, 2026
3f467d1
Split columns docs into separate pages and improve table styling
cloutiertyler Jan 13, 2026
4b0f30f
Rename "Scheduled Tables" to "Schedule Tables"
cloutiertyler Jan 13, 2026
2f73906
Add "Why Tables" section explaining table-oriented design
cloutiertyler Jan 13, 2026
78c1092
Add physical/logical independence section and improve tables page str…
cloutiertyler Jan 13, 2026
c5c8069
Rewrite column types page with unified table and guidance sections
cloutiertyler Jan 13, 2026
3f55eb3
Update column types and rewrite indexes documentation
cloutiertyler Jan 14, 2026
acd1d84
Fix broken documentation links
cloutiertyler Jan 14, 2026
e54e7b9
Merge origin/master into tyler/claude-docs-3
cloutiertyler Jan 14, 2026
f0900bc
Reorganize table documentation: auto-increment, constraints, and defa…
cloutiertyler Jan 14, 2026
6312f74
Add direct indexes documentation and expand access permissions page
cloutiertyler Jan 14, 2026
1f33925
Add table access examples to reducers documentation
cloutiertyler Jan 14, 2026
6d51eb3
Expand ViewContext vs AnonymousViewContext with performance guidance …
cloutiertyler Jan 14, 2026
6c55d5e
Add Rust Table trait import note and expand view context documentation
cloutiertyler Jan 14, 2026
07bdca4
Add TypeScript to subscriptions, fix groupIds, deprecate RLS
cloutiertyler Jan 14, 2026
8d65f02
Apply suggestions from code review
cloutiertyler Jan 15, 2026
d186d04
Update LLM benchmark results
clockwork-labs-bot Jan 16, 2026
8d6239a
Merge branch 'master' into tyler/claude-docs-3
cloutiertyler Jan 16, 2026
d0eb9af
Add Rust/C# AI rules and update quickstarts
cloutiertyler Jan 16, 2026
dd20413
Add AI rules reference section to llms.md
cloutiertyler Jan 16, 2026
43411b4
Add documentation directory to llms.md
cloutiertyler Jan 16, 2026
6b5725f
Add Opencode support (AGENTS.md) to AI rules installation
cloutiertyler Jan 16, 2026
dbbbba8
Add C# sum type docs and strip ANSI codes from benchmark output
cloutiertyler Jan 16, 2026
5134210
Fix C# table names to use PascalCase convention
cloutiertyler Jan 16, 2026
22bf757
cargo fmt
cloutiertyler Jan 16, 2026
d46d9d7
Update LLM benchmark results
clockwork-labs-bot Jan 16, 2026
898260a
Add table naming docs and update C# golden answers to PascalCase
cloutiertyler Jan 16, 2026
d5b38ee
Add documentation for multiple tables with same type
cloutiertyler Jan 16, 2026
8d52ede
Update LLM benchmark results
clockwork-labs-bot Jan 16, 2026
d75d6b9
Add analyze command to generate LLM-powered failure analysis
cloutiertyler Jan 16, 2026
ba3f386
Add failure analysis to LLM benchmark CI workflow
cloutiertyler Jan 16, 2026
7df16ce
Merge master into tyler/claude-docs-3
cloutiertyler Jan 16, 2026
35ce1cd
Fix clippy lint: use next_back() instead of last()
cloutiertyler Jan 16, 2026
3f73abd
Update Lib.cs
jdetter Jan 16, 2026
bb6950d
Update Lib.cs
jdetter Jan 16, 2026
f43151b
Update LLM benchmark results
clockwork-labs-bot Jan 16, 2026
94e9adc
Improve LLM benchmark tooling and CI workflow
cloutiertyler Jan 16, 2026
b1fb0b7
Fix C# template table names to use PascalCase
cloutiertyler Jan 16, 2026
44a5eb3
Apply suggestions from code review
cloutiertyler Jan 16, 2026
376608c
Small fix
cloutiertyler Jan 16, 2026
9b6ba56
Fixed bug with Rust example
cloutiertyler Jan 16, 2026
ebd5d8f
Fixed typescript bug
cloutiertyler Jan 16, 2026
2cc905c
Add C# examples to access-permissions.md view sections
cloutiertyler Jan 16, 2026
f6ff91a
Remove .iter() from view examples and explain why it's not allowed
cloutiertyler Jan 16, 2026
fa0edcc
Clarify Query return type as alternative to indexed access in views
cloutiertyler Jan 16, 2026
77444a8
Apply suggestions from code review
jdetter Jan 16, 2026
0c9debd
Add back leaderboard example using btree index filter
cloutiertyler Jan 16, 2026
4047284
Add C# example for Region-Based Design in views.md
cloutiertyler Jan 16, 2026
30de53b
Switched links to use maincloud instead of localhost in examples
cloutiertyler Jan 16, 2026
6c34fe2
Fixed the my-spacetime-app problem
cloutiertyler Jan 16, 2026
4585f6a
Always post new PR comment instead of updating existing one
cloutiertyler Jan 16, 2026
8a44cb4
Update C# benchmark prompts to use PascalCase table names
cloutiertyler Jan 16, 2026
2de31f6
Enable prompt caching for LLM benchmark providers
cloutiertyler Jan 16, 2026
201ca8f
Update LLM benchmark results
clockwork-labs-bot Jan 17, 2026
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
102 changes: 41 additions & 61 deletions .github/workflows/llm-benchmark-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ jobs:
if: |
(github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/update-llm-benchmark')) ||
(github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
runs-on: spacetimedb-new-runner
container:
image: localhost:5000/spacetimedb-ci:latest
options: >-
--privileged
steps:
# Here we install the spacetime CLI for faster execution of the tests
# SpacetimeDB itself is not under test here, rather it's the docs.
Expand Down Expand Up @@ -201,6 +205,18 @@ jobs:
llm_benchmark ci-quickfix
llm_benchmark ci-check

# Generate failure analysis if there are any failures
- name: Generate failure analysis
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
llm_benchmark analyze -o docs/llms/docs-benchmark-analysis.md || true

# Generate PR comment markdown (compares against master baseline)
- name: Generate PR comment markdown
run: |
llm_benchmark ci-comment

- name: Ensure only docs/llms changed
run: |
set -euo pipefail
Expand All @@ -226,77 +242,41 @@ jobs:
github-token: ${{ secrets.CLOCKWORK_LABS_BOT_PAT }}
script: |
const fs = require('fs');
// docs-benchmark files are used for CI (testing documentation quality)
const summaryPath = 'docs/llms/docs-benchmark-summary.json';
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));

// Extract results for the modes checked by ci-check
// Rust: rustdoc_json, C#: docs
const rustResults = summary.by_language?.rust?.modes?.rustdoc_json?.models?.['GPT-5'];
const csharpResults = summary.by_language?.csharp?.modes?.docs?.models?.['GPT-5'];

const formatPct = (val) => val !== undefined ? `${val.toFixed(1)}%` : 'N/A';

let table = `## LLM Benchmark Results (ci-quickfix)\n\n`;
table += `| Language | Mode | Category | Tests Passed | Pass % | Task Pass % |\n`;
table += `|----------|------|----------|--------------|--------|-------------|\n`;

if (rustResults) {
const cats = rustResults.categories || {};
if (cats.basics) {
const c = cats.basics;
table += `| Rust | rustdoc_json | basics | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
}
if (cats.schema) {
const c = cats.schema;
table += `| Rust | rustdoc_json | schema | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
}
const t = rustResults.totals;
table += `| Rust | rustdoc_json | **total** | ${t.passed_tests}/${t.total_tests} | ${formatPct(t.pass_pct)} | ${formatPct(t.task_pass_pct)} |\n`;
}

if (csharpResults) {
const cats = csharpResults.categories || {};
if (cats.basics) {
const c = cats.basics;
table += `| C# | docs | basics | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
}
if (cats.schema) {
const c = cats.schema;
table += `| C# | docs | schema | ${c.passed_tests}/${c.total_tests} | ${formatPct(c.pass_pct)} | ${formatPct(c.task_pass_pct)} |\n`;
// Read the pre-generated comment markdown
const commentPath = 'docs/llms/docs-benchmark-comment.md';
if (!fs.existsSync(commentPath)) {
core.setFailed(`Comment file not found: ${commentPath}`);
return;
}
let body = fs.readFileSync(commentPath, 'utf8');

// Check if failure analysis exists and append it
const analysisPath = 'docs/llms/docs-benchmark-analysis.md';
if (fs.existsSync(analysisPath)) {
const analysis = fs.readFileSync(analysisPath, 'utf8');
// Only include if there's meaningful content (not just "no failures")
if (!analysis.includes('No failures found')) {
body += `\n<details>\n<summary>Failure Analysis (click to expand)</summary>\n\n${analysis}\n</details>`;
}
const t = csharpResults.totals;
table += `| C# | docs | **total** | ${t.passed_tests}/${t.total_tests} | ${formatPct(t.pass_pct)} | ${formatPct(t.task_pass_pct)} |\n`;
}

table += `\n<sub>Generated at: ${summary.generated_at}</sub>`;

const issue_number = Number(process.env.PR_NUMBER);

// Find and update existing comment or create new one
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
});

const marker = '## LLM Benchmark Results (ci-quickfix)';
const existingComment = comments.data.find(c => c.body.startsWith(marker));

if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: table,
});
} else {
// Always post a new comment
console.log(`Posting new comment on PR #${issue_number}...`);
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body: table,
body,
});
console.log('Comment created successfully');
} catch (err) {
console.error('Failed to post comment:', err.message);
console.error('Full error:', JSON.stringify(err, null, 2));
throw err;
}

# The benchmarks only modify the docs/llms directory.
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions crates/cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ fn get_manifest_dir() -> PathBuf {
// templates list at templates/templates-list.json
// * `get_ai_rules_base` - returns base AI rules for all languages
// * `get_ai_rules_typescript` - returns TypeScript-specific AI rules
// * `get_ai_rules_rust` - returns Rust-specific AI rules
// * `get_ai_rules_csharp` - returns C#-specific AI rules
fn generate_template_files() {
let manifest_dir = get_manifest_dir();
let repo_root = get_repo_root();
Expand Down Expand Up @@ -140,6 +142,34 @@ fn generate_template_files() {
panic!("Could not find \"docs/static/ai-rules/spacetimedb-typescript.mdc\" file.");
}

// Rust-specific rules
let rust_rules_path = ai_rules_dir.join("spacetimedb-rust.mdc");
if rust_rules_path.exists() {
generated_code.push_str("pub fn get_ai_rules_rust() -> &'static str {\n");
generated_code.push_str(&format!(
" include_str!(\"{}\")\n",
rust_rules_path.to_str().unwrap().replace('\\', "\\\\")
));
generated_code.push_str("}\n\n");
println!("cargo:rerun-if-changed={}", rust_rules_path.display());
} else {
panic!("Could not find \"docs/static/ai-rules/spacetimedb-rust.mdc\" file.");
}

// C#-specific rules
let csharp_rules_path = ai_rules_dir.join("spacetimedb-csharp.mdc");
if csharp_rules_path.exists() {
generated_code.push_str("pub fn get_ai_rules_csharp() -> &'static str {\n");
generated_code.push_str(&format!(
" include_str!(\"{}\")\n",
csharp_rules_path.to_str().unwrap().replace('\\', "\\\\")
));
generated_code.push_str("}\n\n");
println!("cargo:rerun-if-changed={}", csharp_rules_path.display());
} else {
panic!("Could not find \"docs/static/ai-rules/spacetimedb-csharp.mdc\" file.");
}

// Expose workspace metadata so `spacetime init` can rewrite template manifests without hardcoding versions.
generated_code.push_str("pub fn get_workspace_edition() -> &'static str {\n");
generated_code.push_str(&format!(" \"{}\"\n", workspace_edition.escape_default()));
Expand Down
43 changes: 35 additions & 8 deletions crates/cli/src/subcommands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1667,15 +1667,22 @@ fn set_dependency_version(item: &mut Item, version: &str, remove_path: bool) {
/// Writes rules to:
/// - .cursor/rules/ (Cursor)
/// - CLAUDE.md (Claude Code)
/// - AGENTS.md (Opencode)
/// - .windsurfrules (Windsurf)
/// - .github/copilot-instructions.md (VS Code Copilot)
fn install_ai_rules(config: &TemplateConfig, project_path: &Path) -> anyhow::Result<()> {
let base_rules = embedded::get_ai_rules_base();
let ts_rules = embedded::get_ai_rules_typescript();
let rust_rules = embedded::get_ai_rules_rust();
let csharp_rules = embedded::get_ai_rules_csharp();

// Check if TypeScript is used in either server or client
// Check which languages are used in server or client
let uses_typescript = config.server_lang == Some(ServerLanguage::TypeScript)
|| config.client_lang == Some(ClientLanguage::TypeScript);
let uses_rust =
config.server_lang == Some(ServerLanguage::Rust) || config.client_lang == Some(ClientLanguage::Rust);
let uses_csharp =
config.server_lang == Some(ServerLanguage::Csharp) || config.client_lang == Some(ClientLanguage::Csharp);

// 1. Cursor: .cursor/rules/ directory with separate files
let cursor_dir = project_path.join(".cursor/rules");
Expand All @@ -1684,24 +1691,44 @@ fn install_ai_rules(config: &TemplateConfig, project_path: &Path) -> anyhow::Res
if uses_typescript {
fs::write(cursor_dir.join("spacetimedb-typescript.mdc"), ts_rules)?;
}
if uses_rust {
fs::write(cursor_dir.join("spacetimedb-rust.mdc"), rust_rules)?;
}
if uses_csharp {
fs::write(cursor_dir.join("spacetimedb-csharp.mdc"), csharp_rules)?;
}

// Build combined content for single-file AI assistants
// Strip the YAML frontmatter from the .mdc files for non-Cursor tools
let base_content = strip_mdc_frontmatter(base_rules);
let combined_content = if uses_typescript {
let mut combined_content = base_content.to_string();

if uses_typescript {
let ts_content = strip_mdc_frontmatter(ts_rules);
format!("{}\n\n{}", base_content, ts_content)
} else {
base_content.to_string()
};
combined_content.push_str("\n\n");
combined_content.push_str(ts_content);
}
if uses_rust {
let rust_content = strip_mdc_frontmatter(rust_rules);
combined_content.push_str("\n\n");
combined_content.push_str(rust_content);
}
if uses_csharp {
let csharp_content = strip_mdc_frontmatter(csharp_rules);
combined_content.push_str("\n\n");
combined_content.push_str(csharp_content);
}

// 2. Claude Code: CLAUDE.md
fs::write(project_path.join("CLAUDE.md"), &combined_content)?;

// 3. Windsurf: .windsurfrules
// 3. Opencode: AGENTS.md
fs::write(project_path.join("AGENTS.md"), &combined_content)?;

// 4. Windsurf: .windsurfrules
fs::write(project_path.join(".windsurfrules"), &combined_content)?;

// 4. VS Code Copilot: .github/copilot-instructions.md
// 5. VS Code Copilot: .github/copilot-instructions.md
let github_dir = project_path.join(".github");
fs::create_dir_all(&github_dir)?;
fs::write(github_dir.join("copilot-instructions.md"), &combined_content)?;
Expand Down
64 changes: 63 additions & 1 deletion docs/DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ This document explains how to configure the environment, run the LLM benchmark t
1. [Quick Checks & Fixes](#quick-checks-fixes)
2. [Environment Variables](#environment-variables)
3. [Benchmark Suite](#benchmark-suite)
4. [Troubleshooting](#troubleshooting)
4. [Context Construction](#context-construction)
5. [Troubleshooting](#troubleshooting)
---

## Quick Checks & Fixes
Expand Down Expand Up @@ -231,6 +232,11 @@ cargo llm run --force
cargo llm ci-check --lang rust
cargo llm ci-check --lang csharp

# Generate PR comment markdown (compares against master baseline)
cargo llm ci-comment
# With custom baseline ref
cargo llm ci-comment --baseline-ref origin/main

```

Outputs:
Expand All @@ -239,6 +245,62 @@ Outputs:

---

## Context Construction

The benchmark tool constructs a context (documentation) that is sent to the LLM along with each task prompt. The context varies by language and mode.

### Modes

| Mode | Language | Source | Description |
|------|----------|--------|-------------|
| `rustdoc_json` | Rust | `crates/bindings` | Generates rustdoc JSON and extracts documentation from the spacetimedb crate |
| `docs` | C# | `docs/docs/**/*.md` | Concatenates all markdown files from the documentation |

### Tab Filtering

When building context for a specific language, the tool filters `<Tabs>` components to only include content relevant to the target language. This reduces noise and helps the LLM focus on the correct syntax.

**Filtered tab groupIds:**

| groupId | Purpose | Tab Values |
|---------|---------|------------|
| `server-language` | Server module code examples | `rust`, `csharp`, `typescript` |
| `client-language` | Client SDK code examples | `rust`, `csharp`, `typescript`, `cpp`, `blueprint` |

**Filtering behavior:**
- For C# tests: Only `value="csharp"` tabs are kept
- For Rust tests: Only `value="rust"` tabs are kept
- If no matching tab exists (e.g., `client-language` with only `cpp`/`blueprint`), the entire tabs block is removed

**Example transformation:**

Before (in markdown):
```html
<Tabs groupId="server-language" queryString>
<TabItem value="csharp" label="C#">
C# code here
</TabItem>
<TabItem value="rust" label="Rust">
Rust code here
</TabItem>
</Tabs>
```

After (for C# context):
```
C# code here
```

### Documentation Best Practices

When writing documentation that will be used by the benchmark:

1. **Use consistent tab groupIds**: Always use `server-language` for server module code and `client-language` for client SDK code
2. **Include all supported languages**: Ensure each `<Tabs>` block has tabs for all languages you want to test
3. **Use consistent naming conventions**: The benchmark compares LLM output against golden answers, so documentation should reflect the expected conventions (e.g., PascalCase table names for C#)

---

## Troubleshooting

**HTTP 400/404 from providers**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const players = table(
<TabItem value="csharp" label="C#">

```csharp
[SpacetimeDB.Table(Name = "players", Public = true)]
[SpacetimeDB.Table(Name = "Player", Public = true)]
public partial struct Player
{
[SpacetimeDB.PrimaryKey]
Expand Down Expand Up @@ -193,7 +193,7 @@ spacetimedb.reducer('world', (ctx) => {
```

While SpacetimeDB doesn't support nested transactions,
a reducer can [schedule another reducer](/tables/scheduled-tables) to run at an interval,
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
or at a specific time.

</TabItem>
Expand All @@ -218,7 +218,7 @@ public static void World(ReducerContext ctx)
```

While SpacetimeDB doesn't support nested transactions,
a reducer can [schedule another reducer](/tables/scheduled-tables) to run at an interval,
a reducer can [schedule another reducer](/tables/schedule-tables) to run at an interval,
or at a specific time.

</TabItem>
Expand Down
Loading
Loading