From d156d7d5ce6967654cf4b6e809eea59906d3f82c Mon Sep 17 00:00:00 2001 From: nielsbosma Date: Tue, 23 Jun 2026 19:03:21 +0200 Subject: [PATCH] Add detector coverage and fix miscategorizations for stack hashing Implements the data-only detector plans (P1-P6) from the consolidated src/.review backlog, all gated on whether they move a Stack Descriptor Hash slot. 18 new rules plus category corrections; no existing fixture detection changes (only rulesLoaded 760->778). - Maven ecosystem: quarkus (framework), hibernate (orm), junit (testing), sqlite maven JDBC facet, protobuf (library). - .NET desktop: avalonia, monogame, elmish, linqpad frameworks; sharpdx library; adapter-gated high-confidence NUnit promotion (supersedes bare NUnit). - Rust: tonic, ratatui, pyo3 frameworks (tokio intentionally omitted). - AI/ML: pytorch (correct `torch` pkg) + tensorflow + pytorch-lightning + langchain + streamlit + gradio as frameworks; transformers/diffusers libraries. - De-pollute hash slots: react/remix/tanstack routers -> library, commander/yargs -> library, jekyll -> documentation, upstash redis -> saas, drop insta's over-broad .snap matcher. --- .../Snapshots/django-spa.verified.yaml | 2 +- .../Snapshots/go-service.verified.yaml | 2 +- .../next-dotnet-monorepo.verified.yaml | 2 +- .../Snapshots/numpy-style-lib.verified.yaml | 2 +- .../data/detectors/database.yml | 8 ++- .../data/detectors/dotnet.yml | 50 ++++++++++++++++ .../data/detectors/framework.yml | 59 ++++++++++++++++++- .../data/detectors/library.yml | 36 +++++++++++ src/Ivy.StackAnalyzer/data/detectors/orm.yml | 10 ++++ .../data/detectors/styling.yml | 10 ++-- .../data/detectors/testing.yml | 13 +++- src/Ivy.StackAnalyzer/data/detectors/tool.yml | 19 ++++-- 12 files changed, 196 insertions(+), 17 deletions(-) diff --git a/src/Ivy.StackAnalyzer.Test/Snapshots/django-spa.verified.yaml b/src/Ivy.StackAnalyzer.Test/Snapshots/django-spa.verified.yaml index 49e4a2e..12ad061 100644 --- a/src/Ivy.StackAnalyzer.Test/Snapshots/django-spa.verified.yaml +++ b/src/Ivy.StackAnalyzer.Test/Snapshots/django-spa.verified.yaml @@ -151,6 +151,6 @@ readme: in `frontend/`. metadata: analyzerVersion: 0.1.0 - rulesLoaded: 760 + rulesLoaded: 778 languageDefsLoaded: 815 ignoredDirectories: [] diff --git a/src/Ivy.StackAnalyzer.Test/Snapshots/go-service.verified.yaml b/src/Ivy.StackAnalyzer.Test/Snapshots/go-service.verified.yaml index fbdf677..13dc9dd 100644 --- a/src/Ivy.StackAnalyzer.Test/Snapshots/go-service.verified.yaml +++ b/src/Ivy.StackAnalyzer.Test/Snapshots/go-service.verified.yaml @@ -62,6 +62,6 @@ infrastructure: - Dockerfile metadata: analyzerVersion: 0.1.0 - rulesLoaded: 760 + rulesLoaded: 778 languageDefsLoaded: 815 ignoredDirectories: [] diff --git a/src/Ivy.StackAnalyzer.Test/Snapshots/next-dotnet-monorepo.verified.yaml b/src/Ivy.StackAnalyzer.Test/Snapshots/next-dotnet-monorepo.verified.yaml index 4344f25..ab50add 100644 --- a/src/Ivy.StackAnalyzer.Test/Snapshots/next-dotnet-monorepo.verified.yaml +++ b/src/Ivy.StackAnalyzer.Test/Snapshots/next-dotnet-monorepo.verified.yaml @@ -220,6 +220,6 @@ readme: backed by Postgres. metadata: analyzerVersion: 0.1.0 - rulesLoaded: 760 + rulesLoaded: 778 languageDefsLoaded: 815 ignoredDirectories: [] diff --git a/src/Ivy.StackAnalyzer.Test/Snapshots/numpy-style-lib.verified.yaml b/src/Ivy.StackAnalyzer.Test/Snapshots/numpy-style-lib.verified.yaml index 960e6d3..ffc248d 100644 --- a/src/Ivy.StackAnalyzer.Test/Snapshots/numpy-style-lib.verified.yaml +++ b/src/Ivy.StackAnalyzer.Test/Snapshots/numpy-style-lib.verified.yaml @@ -53,6 +53,6 @@ technologies: infrastructure: [] metadata: analyzerVersion: 0.1.0 - rulesLoaded: 760 + rulesLoaded: 778 languageDefsLoaded: 815 ignoredDirectories: [] diff --git a/src/Ivy.StackAnalyzer/data/detectors/database.yml b/src/Ivy.StackAnalyzer/data/detectors/database.yml index 5298db2..51f4c2d 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/database.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/database.yml @@ -935,6 +935,9 @@ - { ecosystem: nuget, name: sqlite-net-pcl } # --- pypi (pysqlite3/sqlite3 are stdlib; aiosqlite is the safe signal) --- - { ecosystem: pypi, name: aiosqlite } + # --- maven (JVM JDBC drivers) --- + - { ecosystem: maven, name: "org.xerial:sqlite-jdbc" } + - { ecosystem: maven, name: "io.quarkiverse.jdbc:quarkus-jdbc-sqlite" } depPrefix: - { ecosystem: nuget, prefix: "SQLitePCLRaw." } depsRegex: @@ -1025,9 +1028,12 @@ - { ecosystem: composer, name: typesense/laravel-scout-typesense-driver } - { ecosystem: terraform, name: airbyte_destination_typesense } confidence: high +# Upstash Redis is a serverless cache/SaaS (commonly a rate-limiter), not the +# application's database engine; classify as saas so it never fills the db slot. +# A real self-hosted Redis is still caught by the generic `redis` rule. - id: upstash.redis name: Upstash Redis - category: database + category: saas match: deps: - { ecosystem: npm, name: "@upstash/redis" } diff --git a/src/Ivy.StackAnalyzer/data/detectors/dotnet.yml b/src/Ivy.StackAnalyzer/data/detectors/dotnet.yml index 275ff29..600d838 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/dotnet.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/dotnet.yml @@ -61,6 +61,45 @@ sdk: ["Microsoft.NET.Sdk.Worker"] confidence: high +- id: avalonia + name: Avalonia + category: framework + match: + deps: + - { ecosystem: nuget, name: "Avalonia" } + depPrefix: + - { ecosystem: nuget, prefix: "Avalonia." } + confidence: high + +- id: monogame + name: MonoGame + category: framework + match: + depPrefix: + - { ecosystem: nuget, prefix: "MonoGame.Framework" } + confidence: high + +- id: elmish + name: Elmish + category: framework + match: + deps: + - { ecosystem: nuget, name: "Elmish" } + - { ecosystem: nuget, name: "Fable.Elmish" } + depPrefix: + - { ecosystem: nuget, prefix: "Elmish." } + - { ecosystem: nuget, prefix: "Fable.Elmish." } + confidence: high + +- id: linqpad + name: LINQPad + category: framework + match: + deps: + - { ecosystem: nuget, name: "LINQPad.Runtime" } + - { ecosystem: nuget, name: "LINQPad" } + confidence: high + - id: ivy-framework name: Ivy Framework category: framework @@ -96,6 +135,17 @@ - { ecosystem: nuget, name: "NUnit" } confidence: low # bare NUnit ref is weak evidence; needs Test.Sdk/adapter to be a real test framework +# The test adapter proves a runnable NUnit suite; promote to high and supersede +# the weak bare-NUnit signal so the `test:` hash slot is populated. +- id: nunit-adapter + name: NUnit + category: testing + match: + deps: + - { ecosystem: nuget, name: "NUnit3TestAdapter" } + supersedes: [nunit] + confidence: high + - id: mstest name: MSTest category: testing diff --git a/src/Ivy.StackAnalyzer/data/detectors/framework.yml b/src/Ivy.StackAnalyzer/data/detectors/framework.yml index 3a3a38d..3bb1660 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/framework.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/framework.yml @@ -76,9 +76,34 @@ deps: - { ecosystem: cargo, name: axum } confidence: high +- id: tonic + name: tonic + category: framework + match: + deps: + - { ecosystem: cargo, name: tonic } + - { ecosystem: cargo, name: tonic-build } + confidence: high +- id: ratatui + name: Ratatui + category: framework + match: + deps: + - { ecosystem: cargo, name: ratatui } + - { ecosystem: cargo, name: ratatui-macros } + confidence: high +- id: pyo3 + name: PyO3 + category: framework + match: + deps: + - { ecosystem: cargo, name: pyo3 } + confidence: high +# commander/yargs are argument parsers, not application frameworks; keep them out +# of the framework hash slot (a CLI tool is a `lib`-role, language-only stack). - id: commander name: Commander - category: framework + category: library match: deps: - { ecosystem: npm, name: commander } @@ -139,7 +164,7 @@ confidence: high - id: yargs name: Yargs - category: framework + category: library match: deps: - { ecosystem: npm, name: yargs } @@ -196,6 +221,32 @@ deps: - { ecosystem: pypi, name: django } confidence: high +- id: quarkus + name: Quarkus + category: framework + match: + depPrefix: + - { ecosystem: maven, prefix: "io.quarkus:" } + - { ecosystem: maven, prefix: "io.quarkiverse" } + confidence: high +- id: langchain + name: LangChain + category: framework + match: + deps: + - { ecosystem: pypi, name: langchain } + - { ecosystem: pypi, name: langchain-core } + - { ecosystem: pypi, name: langchain-community } + - { ecosystem: npm, name: langchain } + - { ecosystem: npm, name: "@langchain/core" } + confidence: high +- id: streamlit + name: Streamlit + category: framework + match: + deps: + - { ecosystem: pypi, name: streamlit } + confidence: high - id: docsify name: Docsify category: documentation @@ -347,9 +398,11 @@ depsRegex: - { ecosystem: npm, pattern: "@ionic\\/" } confidence: high +# Jekyll is a static-site/docs generator, not an application framework; classify +# as documentation (like MkDocs/Docusaurus) so it never fills the framework slot. - id: jekyll name: Jekyll - category: framework + category: documentation match: deps: - { ecosystem: githubAction, name: jeffreytse/jekyll-deploy-action } diff --git a/src/Ivy.StackAnalyzer/data/detectors/library.yml b/src/Ivy.StackAnalyzer/data/detectors/library.yml index 10633d1..b94c346 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/library.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/library.yml @@ -1,4 +1,40 @@ # Technology detectors (category: library) seeded from specfy/stack-analyser (MIT). +# Hand-added libraries below are surfaced as facts but are NOT framework/db/orm +# slots, so they never enter the stack hash. +- id: protobuf + name: Protocol Buffers + category: library + match: + deps: + - { ecosystem: npm, name: protobufjs } + - { ecosystem: pypi, name: protobuf } + - { ecosystem: cargo, name: prost } + - { ecosystem: go, name: google.golang.org/protobuf } + depPrefix: + - { ecosystem: maven, prefix: "com.google.protobuf" } + extensions: [proto] + confidence: high +- id: transformers + name: Transformers + category: library + match: + deps: + - { ecosystem: pypi, name: transformers } + confidence: high +- id: diffusers + name: Diffusers + category: library + match: + deps: + - { ecosystem: pypi, name: diffusers } + confidence: high +- id: sharpdx + name: SharpDX + category: library + match: + depPrefix: + - { ecosystem: nuget, prefix: "SharpDX" } + confidence: high - id: airbyte name: Airbyte category: library diff --git a/src/Ivy.StackAnalyzer/data/detectors/orm.yml b/src/Ivy.StackAnalyzer/data/detectors/orm.yml index 95938e0..6d720be 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/orm.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/orm.yml @@ -34,6 +34,16 @@ deps: - { ecosystem: go, name: gorm.io/gorm } confidence: high +- id: hibernate + name: Hibernate + category: orm + match: + deps: + - { ecosystem: maven, name: "io.quarkus:quarkus-hibernate-orm-panache" } + depPrefix: + - { ecosystem: maven, prefix: "org.hibernate" } + - { ecosystem: maven, prefix: "io.quarkus:quarkus-hibernate" } + confidence: high - id: knex name: Knex category: orm diff --git a/src/Ivy.StackAnalyzer/data/detectors/styling.yml b/src/Ivy.StackAnalyzer/data/detectors/styling.yml index faa526c..fe09d46 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/styling.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/styling.yml @@ -61,7 +61,7 @@ confidence: high - id: gradio name: Gradio - category: styling + category: framework match: deps: - { ecosystem: pypi, name: gradio } @@ -128,9 +128,11 @@ - { ecosystem: npm, name: "@radix-ui/react-label" } - { ecosystem: npm, name: radix-ui } confidence: high +# Routers are not a styling/UI choice; classify as library so they never pollute +# the styling hash slot (the SDH spec drops routers). - id: reactrouterdom name: React Router - category: styling + category: library match: deps: - { ecosystem: npm, name: react-router-dom } @@ -154,7 +156,7 @@ confidence: high - id: remixrouter name: Remix Router - category: styling + category: library match: deps: - { ecosystem: npm, name: "@remix-run/router" } @@ -184,7 +186,7 @@ confidence: high - id: tanstackrouter name: TanStack Router - category: styling + category: library match: deps: - { ecosystem: npm, name: "@tanstack/react-router" } diff --git a/src/Ivy.StackAnalyzer/data/detectors/testing.yml b/src/Ivy.StackAnalyzer/data/detectors/testing.yml index 79a72dd..1171c1c 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/testing.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/testing.yml @@ -29,13 +29,24 @@ - { ecosystem: go, name: github.com/frankban/quicktest } filesRegex: ["_test\\.go$"] confidence: high +# `.snap` files are shared by many snapshot tools (Jest, Vitest); they are not +# proof of the Rust `insta` crate. Match the cargo dependency only. - id: insta name: insta category: testing match: deps: - { ecosystem: cargo, name: insta } - filesRegex: ["\\.snap$"] + confidence: high +- id: junit + name: JUnit + category: testing + match: + deps: + - { ecosystem: maven, name: "junit:junit" } + - { ecosystem: maven, name: "io.quarkus:quarkus-junit5" } + depPrefix: + - { ecosystem: maven, prefix: "org.junit" } confidence: high - id: jest name: Jest diff --git a/src/Ivy.StackAnalyzer/data/detectors/tool.yml b/src/Ivy.StackAnalyzer/data/detectors/tool.yml index 8c51b9d..9129d96 100644 --- a/src/Ivy.StackAnalyzer/data/detectors/tool.yml +++ b/src/Ivy.StackAnalyzer/data/detectors/tool.yml @@ -130,13 +130,24 @@ - { ecosystem: cargo, name: openapi } files: [openapi.yaml, openapi.json, openapi.yml] confidence: high +# ML frameworks are the defining choice of an ML repo -> framework slot. The PyPI +# package is `torch` (`pytorch` is the conda name); match both. - id: pytorch - name: Pytorch - category: tool + name: PyTorch + category: framework match: deps: + - { ecosystem: pypi, name: torch } - { ecosystem: pypi, name: pytorch } confidence: high +- id: pytorch-lightning + name: PyTorch Lightning + category: framework + match: + deps: + - { ecosystem: pypi, name: pytorch-lightning } + - { ecosystem: pypi, name: lightning } + confidence: high - id: reactemail name: React Email category: tool @@ -154,8 +165,8 @@ - { ecosystem: npm, name: "@opentelemetry/instrumentation-socket.io" } confidence: high - id: tensorflow - name: Tensorflow - category: tool + name: TensorFlow + category: framework match: deps: - { ecosystem: docker, name: tensorflow/tensorflow }