Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Part V: Language Backends — One Protocol, Seven Scanners

One JSON protocol. Seven language backends. The Diem instance doesn't care which language produced the scan.

Most specification tools treat every language the same: parse comments, match strings, hope for the best. That works until someone renames a method, deletes a test file, or typos a feature identifier. Then the spec drifts silently and nobody notices until a customer reports the bug that was supposedly "verified."

tspec takes the opposite approach. Each language gets a backend that speaks that language's native type system. The C# backend uses Roslyn. The TypeScript backend uses decorators and keyof. The Rust backend uses proc macros. They all disagree on syntax. They all agree on output: a single JSON document that Diem consumes without knowing or caring which compiler produced it.


The Backend Architecture

Seven backends, seven scanners, seven ways to express the same idea: "this code implements that acceptance criterion."

  • tspec-cs --- C#: Roslyn source generators + [Verifies(typeof(F), nameof(F.AC))]. Full semantic model. The compiler catches every broken link before the binary exists.
  • tspec-ts --- TypeScript: decorators (@Implements<F>('ac')) + keyof T. Can run as a regex scanner for speed or invoke the TS compiler API for full type resolution.
  • tspec-java --- Java: annotations (@Implements(feature=F.class, ac="name")) + annotation processor. Violations surface at compile time, not in a log file three sprints later.
  • tspec-groovy --- Groovy: AST transformations + annotations. Same annotation surface as Java but with Groovy's compile-time metaprogramming.
  • tspec-py --- Python: decorators (@implements(Feature, 'ac')) + ast module scanner. No runtime import required for scanning --- the AST module reads the source directly.
  • tspec-go --- Go: interface-based features + struct tags + go/ast package. Idiomatic Go: no magic, no reflection, just interfaces and tags.
  • tspec-rs --- Rust: trait-based features + proc macros (#[implements(NavigationFeature, "toc_click")]) + syn/quote. The proc macro validates at compile time; if the feature or AC does not exist, cargo build fails.

Each backend understands its own language deeply. None of them try to be generic. That is the entire point.

Three Languages, One Idea

The same test link expressed in TypeScript, C#, and Rust:

TypeScript:

@FeatureTest(NavigationFeature)
class NavigationTests {
  @Implements<NavigationFeature>('tocClickLoadsPage')
  async 'clicking TOC loads page'({ page }) { ... }
}

C#:

[ForRequirement(typeof(NavigationFeature))]
public class NavigationTests
{
    [Verifies(typeof(NavigationFeature), nameof(NavigationFeature.TocClickLoadsPage))]
    public async Task TocClick_LoadsPage() { ... }
}

Rust:

#[feature_test(NavigationFeature)]
mod navigation_tests {
    #[implements(NavigationFeature, "toc_click_loads_page")]
    #[test]
    fn toc_click_loads_page() { ... }
}

Different syntax. Different compilers. Same semantic: "this test verifies the toc_click_loads_page acceptance criterion on NavigationFeature."


The JSON Protocol

Every backend produces the same schema. No exceptions. The Diem instance receives this document and does not inspect the language field to decide how to parse it --- the structure is identical regardless of origin.

{
  "project": "my-app",
  "language": "typescript",
  "branch": "main",
  "commit": "abc123def",
  "timestamp": "2026-03-28T10:00:00Z",
  "flavor": "agile",
  "hierarchy": [
    {
      "id": "IDENTITY",
      "title": "Identity & Access Management",
      "level": "Epic",
      "parent": null,
      "acs": []
    },
    {
      "id": "ROLES",
      "title": "User Roles and Permissions",
      "level": "Feature",
      "parent": "IDENTITY",
      "priority": "critical",
      "acs": [
        {
          "name": "adminCanAssignRoles",
          "description": "Admin users can assign roles to other users.",
          "covered": true,
          "tests": [
            { "file": "test/e2e/roles.spec.ts", "name": "admin assigns editor role" }
          ]
        },
        {
          "name": "nonAdminCannotAssign",
          "description": "Non-admin users cannot assign roles.",
          "covered": true,
          "tests": [
            { "file": "test/e2e/roles.spec.ts", "name": "viewer gets 403" }
          ]
        }
      ]
    }
  ],
  "crossCutting": [
    {
      "id": "BUG-42",
      "type": "Bug",
      "target": "ROLES",
      "severity": "critical",
      "acs": [
        { "name": "emptyRoleListHandled", "covered": false, "tests": [] }
      ]
    }
  ],
  "summary": {
    "totalFeatures": 20,
    "totalACs": 112,
    "coveredACs": 110,
    "percentage": 98
  }
}

The hierarchy array carries the full Epic/Feature/Story/Task tree. The crossCutting array holds Bugs and any items that reference features without being children of them. The summary block gives the Diem dashboard exactly the numbers it needs to render a compliance gauge without recomputing anything.

This schema is versioned. Backends target a schema version. Diem rejects documents with an unknown version rather than guessing. No silent data loss.


Shared CLI Subcommands

Every backend shares the same CLI surface. The tspec command dispatches to the correct language scanner based on project configuration. You never need to call tspec-cs directly --- tspec scan reads your .tspec.yaml and does the right thing.

Subcommand Purpose Local/Remote API Required
tspec init --flavor=agile --lang=ts Scaffold project Local No
tspec scan Run language-specific scanner Local No
tspec push Send JSON to Diem API Remote Yes
tspec sync --source=jira Generate features from external Remote Source API
tspec lint Language-specific linter rules Local No
tspec diff Compare two scan snapshots Local No
tspec report --format=json|csv|md|html Generate local reports Local No
tspec gate Pre-commit quality gate Local No
tspec ci CI-optimized scan + push + gate Remote Yes
tspec reverse test/e2e/nav.spec.ts Which features does this test cover? Local No
tspec workflow transition NAV --to=Review Manage workflow state Remote Yes
tspec assign NAV --to=alice@acme-corp.com Assign feature Remote Yes
tspec work --item #ID Focus on item (DX) Remote Yes
tspec done Finish current item (DX) Remote Yes
tspec next Pick next from backlog (DX) Remote Yes

The split matters. Commands marked "Local" work offline, on an airplane, in a sandbox without network access. Commands marked "Remote" talk to the Diem API or an external source. A developer working on a feature can scan, lint, gate, and report without ever authenticating. Only push requires a token.


Type Safety Comparison

Not all languages offer the same guarantees. That is fine. tspec meets each language where it is, extracts maximum safety from the type system available, and makes the tradeoffs explicit.

Backend Feature def AC linking Typo detection Rename safety
C# abstract record nameof() + typeof() Compile-time Full IDE
TypeScript abstract class keyof T Compile-time Full IDE
Java abstract class annotation + reflection Compile-time (annotation processor) IDE refactor
Groovy abstract class AST transformation Compile-time IDE refactor
Python abstract class decorator + string Runtime (linter catches) Partial
Go interface + struct tags struct tag Build-time (go vet) Partial
Rust trait + proc macro #[implements] attribute Compile-time (proc macro) Full (rust-analyzer)

C#, TypeScript, and Rust sit at the top: typos are impossible because the compiler rejects them. Java and Groovy are close behind thanks to annotation processors and AST transforms. Python and Go trade some static safety for ecosystem conventions --- but tspec lint fills the gap by scanning for broken references before tspec gate lets a commit through.

The point is not that every language must be equally strict. The point is that every language pushes as far as its type system allows, and the JSON output is equally trustworthy regardless.


Before and After

Where you start:

Diagram

One language. One scanner. Output goes to the console and dies.

Where you end up:

Diagram

Seven languages. Seven scanners. One protocol. One Diem instance that aggregates everything into a single compliance dashboard. A polyglot organization --- .NET backend, TypeScript frontend, Rust edge services, Python ML pipeline --- gets one unified view of what is specified, what is implemented, and what is tested.


Why This Matters

The backend architecture is the reason tspec scales beyond a single team or a single language. Without it, you either force everyone onto one stack (unrealistic) or you accept that cross-language traceability is impossible (unacceptable). The JSON protocol is the contract. The backends are the adapters. The Diem instance is the single source of truth.

Add a new language? Write a scanner that outputs the JSON schema. Register it. Done. The rest of the ecosystem --- dashboards, gates, reports, workflow transitions --- works immediately because it never depended on the language in the first place.


Previous: Part IV: Custom Workflows | Next: Part VI: Developer Experience