Semantic Development
Zero-dependency Go library for matching natural language queries against accessibility tree elements.
Essential Commands
Before any PR:
./dev pr # runs: check + e2e + lint corpus + bench
During development:
./dev test # unit tests (fast)
./dev check # fmt + vet + lint + test race (full validation)
./dev build # build ./semantic CLI binary
Quality regression checks:
./dev baseline check # compare quality against baseline
./dev runtime # compare performance against baseline
When quality changes intentionally:
./dev baseline update # accept new quality baseline (after review)
When to Use Each
| Scenario | Command |
|---|---|
| Made code changes, quick sanity | ./dev test |
| Ready to commit | ./dev check |
| Before opening PR | ./dev pr |
| Changed scoring/matching logic | ./dev baseline check |
| Performance-sensitive changes | ./dev runtime |
| Tuning weights | ./dev tune then ./dev bench |
Architecture
semantic.go Public API (type aliases + constructors)
semantic_test.go API-level smoke tests
internal/
types/types.go Type definitions (interfaces, structs)
engine/ All matching implementations (hidden)
combined.go Fused lexical + embedding matcher
lexical.go Jaccard similarity + synonyms + role boosting
embedding.go Cosine similarity on dense vectors
hashing.go Feature hashing embedder (zero-dep)
synonyms.go 54 synonym groups for UI vocabulary
stopwords.go Context-aware stopword removal
recovery/ Public subpackage
engine.go RecoveryEngine (semantic re-matching)
cache.go IntentCache (per-tab LRU)
failure.go FailureType classification
cmd/semantic/main.go CLI tool (find, match, classify)
cmd/semantic-bench/ Benchmark CLI (check, baseline, calibrate, tune, runtime)
Key Design Decisions
internal/— implementations are hidden. Consumers only seeElementMatcherinterface + constructors.recovery/— public subpackage. Pinchtab imports bothsemanticandsemantic/recovery.- Zero dependencies — hashing embedder, no ML models, no network calls.
- Stateless — every
Find()call is independent. Thread-safe by default.
Workflow: New Feature or Bug Fix
Run doctor first:
./dev doctorMake changes — implementations go in
internal/engine/, types ininternal/types/Run checks:
./dev check # fmt + vet + lint + test with racePre-commit hook runs gofmt + golangci-lint automatically on staged files.
Benchmark Improvement Loop
When implementing changes that affect matching quality:
./dev baseline # create baseline (first time only)
# ... make changes ...
./dev bench # run benchmark, compare to baseline
./dev baseline update # accept new baseline (if improved)
Key metrics:
- MRR — Mean Reciprocal Rank (higher = finds correct element faster)
- P@1 — Precision at 1 (is top result correct?)
- Hit@3 — Any correct result in top 3?
Adding test cases:
- Add to
tests/benchmark/corpus/*/queries.json - Run
./dev lint corpusto validate - Run
./dev bench— shows regression until fixed
Public API Surface
Only these symbols are visible to consumers:
// Types (from internal/types via aliases)
semantic.ElementMatcher // interface
semantic.Embedder // interface
semantic.ElementDescriptor // struct
semantic.ElementMatch // struct
semantic.FindOptions // struct
semantic.FindResult // struct
// Constructors
semantic.NewCombinedMatcher(embedder) → ElementMatcher
semantic.NewHashingEmbedder(dim) → Embedder
semantic.NewLexicalMatcher() → ElementMatcher
semantic.NewEmbeddingMatcher(e) → ElementMatcher
// Functions
semantic.CalibrateConfidence(score) → string
semantic.LexicalScore(query, desc) → float64
semantic.CosineSimilarity(a, b) → float64
// Recovery subpackage
recovery.IntentCache, recovery.IntentEntry
recovery.RecoveryEngine, recovery.RecoveryResult
recovery.ClassifyFailure, recovery.DefaultRecoveryConfig
Testing
- 167 tests across 3 packages (root, engine, recovery)
internal/engine/has unit tests for all matchers + benchmark suite- Root has API-level smoke tests
recovery/has scenario tests (SPA re-render, checkout, login, etc.)
Release Process
- Update
npm/package.jsonversion git tag v0.X.0 && git push origin v0.X.0- CI builds binaries (GoReleaser) + opens homebrew tap PR
- npm publish manually or via
release-manual-publish.yml