Skip to content

Instantly share code, notes, and snippets.

@alexispurslane
Last active February 4, 2026 21:03
Show Gist options
  • Select an option

  • Save alexispurslane/4d01ac5522f1b58be2bc12867fc5cd75 to your computer and use it in GitHub Desktop.

Select an option

Save alexispurslane/4d01ac5522f1b58be2bc12867fc5cd75 to your computer and use it in GitHub Desktop.
BDD/type-first/spec-driven development workflow idea. Let's see if the most powerful open weight model yet can actually consistently listen to it!

2. Adding Features (Type-First BDD Workflow)

When the user proposes a new feature, follow this workflow where executable tests specify behavior and types + doc comments specify technical design:

Step 1: Agree on Behavior

Write natural language Gherkin scenarios and iterate with the user:

Given a source file with a file link
  And a target file exists
  When I request definition at the link position
    Then it should return the target file location

Step 2: Executable Specification

Translate to BDD tests in integration/<featurename>_test.go - this is the living behavioral spec:

func TestFileLinkDefinition(t *testing.T) {
    Given("a source file with a file link", t, setupFunc, func(t *testing.T, tc *LSPTestContext) {
        When(t, tc, "requesting definition at link position", "textDocument/definition", params,
            func(t *testing.T, locs []protocol.Location) {
                Then("returns target file location", t, func(t *testing.T) {
                    testza.AssertEqual(t, 1, len(locs))
                })
            })
    })
}

Each top level Test* function should correspond to the collection of Gherkin "Given" scenarios that describe a total feature. Put each such function in its own file.

Run the test to confirm it fails (red): just test TestFileLinkDefinition

Thread-Safety Verification Note: All just test* commands run with:

  • -race flag enabled (detects data races at runtime)
  • -parallel=4 (limited parallelism to catch concurrency bugs)
  • -timeout=60s (catches deadlocks/hangs)

This means your tests automatically verify thread-safety! If you introduce a data race or deadlock, the tests will fail with a detailed report. Keep tests deterministic and avoid shared mutable state between parallel tests.

Step 3: Technical Specification (Type-First)

Define data types and function signatures with doc comments. The types are the spec, the documentation comments define specific algorithms and semantics:

// IDLinkResolver finds target headings by UUID property.
// It searches the UUID index built by orgscanner during file scanning.
type IDLinkResolver struct {
    scanner *orgscanner.OrgScanner
}

// Resolve returns the location of the heading with the given ID.
// Returns nil if no heading with that ID exists.
// 
// Algorithm: look up the HeaderLocation on scanner.ProcessedFiles.UuidIndex map.
func (r *IDLinkResolver) Resolve(id string) *orgscanner.HeaderLocation

View generated docs: go doc github.com/alexispurslane/org-lsp/orgscanner IDLinkResolver

Step 4: Implement

Now implement to make tests pass:

  1. Add types with doc comments (technical spec)
  2. Add handler method to server.go
  3. Update capabilities in initialize()
  4. Run tests: just test TestFeatureName
  5. Debug with: ORG_LSP_LOG_LEVEL=DEBUG just test TestFeatureName

Step 5: Verify & Document

  • Run full test suite: just test
  • Update doc comments if implementation diverged from spec
  • The code now contains both executable behavior spec (tests) and technical spec (types + comments)

Key Principle: The only "spec documents" are:

  • integration/*_test.go - executable behavior specs
  • Go doc comments in the code - technical specs
  • ARCHITECTURE.md - high-level navigation (optional)

No separate SPEC.md needed - the spec lives in the code and is always up-to-date!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment