diff --git a/public/examples/citrus-simulator-370.svg b/public/examples/citrus-simulator-370.svg new file mode 100644 index 0000000..1bae4ab --- /dev/null +++ b/public/examples/citrus-simulator-370.svg @@ -0,0 +1 @@ +org.citrusframework.simulatorconfiglistenerserviceimplSimulatorConfigurationProperties(...)@author Christoph Deppisch NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 2WMC: 2 ENC: 0.9ENC: 0.9 AC: 23AC: 23+5% EC: 8EC: 8SimulationResults [+1]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 1ENC: 1 AC: 1AC: 1 EC: 0EC: 0persistOnlyFailedScenarios : booleanresetEnabled : booleanSimulatorStatusListenerTest [+1 +1]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0fixture : SimulatorStatusListenerscenarioActionServiceMock : ScenarioActionServicescenarioExecutionServiceMock : ScenarioExecutionServicesimulatorConfigurationPropertiesMock : SimulatorConfigurationPropertiesgetRunningTests() : Map<String, TestResult>setup()SimulatorStatusListener [+2 -1 +1]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 21WMC: 21+5% ENC: 0.4ENC: 0.4+10% AC: 0AC: 0 EC: 24EC: 24+4%runningTests : Map<String, TestResult>logger : LoggerscenarioActionService : ScenarioActionServicescenarioExecutionService : ScenarioExecutionServicesimulatorConfigurationProperties : SimulatorConfigurationPropertiesonTestActionSkipped(TestCase, TestAction)SimulatorStatusListener(...)onTestFinish(TestCase)onTestActionFinish(TestCase, TestAction)onTestFailure(TestCase, Throwable)getParameters(TestCase) : String[]onTestSuccess(TestCase)onTestActionFailed(TestCase, TestAction, Throwable)onTestStart(TestCase)SimulatorStatusListener(...)onTestActionStart(TestCase, TestAction)ignoreTestAction(TestAction) : booleanOnTestSuccess [+1 +2]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0shouldDeleteScenarioExecutionWhenPersistOnlyFailedScenariosIsEnabled()shouldCompleteScenarioExecutionWithParameters()createAndGetScenarioExecutionId(TestCase) : longshouldCompleteScenarioExecutionWithoutParameters()ScenarioExecutionService [+1]Service Interface for managingScenarioExecution.WMC: 0WMC: 0 AC: 7AC: 7 EC: 10EC: 10findOneLazy(Long) : Optional<ScenarioExecution>save(ScenarioExecution) : ScenarioExecutiondeleteScenarioExecution(long)completeScenarioExecution(long, TestResult) : ScenarioExecutionfindAll(Pageable) : Page<ScenarioExecution>findOne(Long) : Optional<ScenarioExecution>createAndSaveExecutionScenario(String, List<ScenarioParameter>) : ScenarioExecutionScenarioExecutionServiceImpl [+1]Service implementation for managingScenarioExecution.NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 10WMC: 10+11% ENC: 0.3ENC: 0.3-10% AC: 0AC: 0 EC: 19EC: 19timeProvider : TimeProviderscenarioExecutionRepository : ScenarioExecutionRepositorylogger : LoggerScenarioExecutionServiceImpl(ScenarioExecutionRepository)findAll(Pageable) : Page<ScenarioExecution>findOne(Long) : Optional<ScenarioExecution>completeScenarioExecution(long, TestResult) : ScenarioExecutionsave(ScenarioExecution) : ScenarioExecutioncreateAndSaveExecutionScenario(String, List<ScenarioParameter>) : ScenarioExecutiondeleteScenarioExecution(long)findOneLazy(Long) : Optional<ScenarioExecution>striff.io \ No newline at end of file diff --git a/public/examples/smallrye-graphql-1424.svg b/public/examples/smallrye-graphql-1424.svg new file mode 100644 index 0000000..21b3b96 --- /dev/null +++ b/public/examples/smallrye-graphql-1424.svg @@ -0,0 +1 @@ +io.smallrye.graphqlapibootstrapexecutiondatafetcher.helperschemacreatorhelpermodeltest.apps.result.apiGraphQLResultMode [+2]AC: 0AC: 0 EC: 0EC: 0NONEERROR_FIELDSDataFetcherFactory [+2]Create the datafetchers for a certain operation @author Phillip Kruger[phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 55WMC: 55+4% ENC: 0.4ENC: 0.4 AC: 2AC: 2 EC: 26EC: 26dataFetcherServices : List<DataFetcherService>isMutinyMulti(Field) : booleangetOtherWrappedDataFetcher(Operation, Type) : PlugableDataFetchergetCompletionStageDataFetcher(Operation, Type) : PlugableDataFetcherDataFetcherFactory()isMutinyUni(Field) : booleanunwrap(Field, boolean) : WrappergetDefaultDataFetcher(Operation, Type) : PlugableDataFetcherisWrapped(Field) : booleangetDataFetcher(Operation, Type) : DataFetcher<T>getUniDataFetcher(Operation, Type) : PlugableDataFetchergetSourceBatchLoader(Operation, Type) : BatchLoaderWithContext<K, T>isCompletionStage(Field) : booleanget(Operation, Type) : VisFutureLike(Field) : booleangetOtherFieldDataFetcher(Operation, Type) : PlugableDataFetcherisPublisher(Field) : booleangetPublisherDataFetcher(Operation, Type) : PlugableDataFetchergetMultiDataFetcher(Operation, Type) : PlugableDataFetcherBootstrap [+3](...)Bootstrap MicroProfile GraphQL This create a graphql-java model from thesmallrye model @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 174WMC: 174+1% ENC: 1.0ENC: 1.0 AC: 3AC: 3 EC: 94EC: 94+3%createGraphQLInputType(Field) : GraphQLInputTypecreateGraphQLArgument(Argument) : GraphQLArgumentcreateGraphQLOutputType(Field, boolean) : GraphQLOutputTypeExecutionResponse [+1]Response from an execution @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 36WMC: 36+3% ENC: 0.8ENC: 0.8-5% AC: 8AC: 8 EC: 38EC: 38+3%jsonObjectFactory : JsonBuilderFactoryjsonB : JsonbERRORS : StringjsonProvider : JsonProviderEXTENSIONS : StringjsonReaderFactory : JsonReaderFactoryDATA : StringexecutionResult : ExecutionResulterrorsService : ExecutionErrorsServicegetExecutionResult() : ExecutionResulttoJsonValue(Object) : JsonValuegetExecutionResultAsJsonObject() : JsonObjectgetExecutionResultAsString() : StringaddDataToResponse(JsonObjectBuilder, ExecutionResult) : JsonObjectBuilderaddExtensionsToResponse(JsonObjectBuilder, ExecutionResult) : JsonObjectBuilderaddErrorsToResponse(JsonObjectBuilder, ExecutionResult) : JsonObjectBuilderbuildExtensions(Map<Object, Object>) : JsonObjecttoString() : StringExecutionResponse(ExecutionResult)addDataToResponse(JsonObjectBuilder, Object) : JsonObjectBuilderAbstractHelper [+1 +15](...)Help with the fields when fetching data. @author Phillip Kruger[phillip.kruger@redhat.com]NOC: 2NOC: 2 DIT: 1DIT: 1 WMC: 99WMC: 99+29% ENC: 0.8ENC: 0.8+1% AC: 4AC: 4 EC: 30EC: 30mapAdapter : DefaultMapAdapter<?, ?>transformerMap : Map<String, Transformer<?, ?>>recursiveAdapting(Object, Field, DataFetchingEnvironment) : ObjecttransformResult(Object, Field) : Map<String, Object>recursiveAdaptOptional(Object, Field, DataFetchingEnvironment) : Optional<Object>recursiveTransformOptional(Object, Field, DataFetchingEnvironment) : Optional<Object>getTransformer(Field) : Transformer<?, ?>recursiveTransformArray(Object, Field, DataFetchingEnvironment) : ObjectgetArrayType(Field) : Class<?>getKey(String, String, List<String>) : IntegerrecursiveAdaptCollection(Object, Field, DataFetchingEnvironment) : ObjectrecursiveTransformCollection(Object, Field, DataFetchingEnvironment) : ObjectrecursiveTransform(Object, Field, DataFetchingEnvironment) : ObjectgetFieldInField(Field) : FieldrecursiveAdaptArray(Object, Field, DataFetchingEnvironment) : ObjectgetGivenCollection(Object) : Collection<?>DefaultMapAdapter [+1]The adapter to change map to Entry Set.Users can also supply their own adapter.@author Phillip Kruger [phillip.kruger@redhat.com] @param Key @param ValueNOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 15WMC: 15 ENC: 0.6ENC: 0.6 AC: 3AC: 3 EC: 30EC: 30fieldAdaptionMap : Map<Field, Field>KEY : StringclassloadingService : ClassloadingServiceVALUE : StringgetAdaptedField(Field) : Fieldfrom(Set<Entry<K, V>>, Field) : Map<K, V>toObject(Reference, T) : Tto(Map<K, V>, List<K>, Field) : Set<Entry<K, V>>Annotations [+1](...)All the annotations we care about for a certain context There are multiplestatic methods to create the annotations for the correct context @author PhillipKruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 105WMC: 105 ENC: 0.2ENC: 0.2 AC: 23AC: 23 EC: 29EC: 29RESULT : DotNameScanningContextA simple registry to hold the current scanning info At this point we only keepthe index in the context @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 4WMC: 4 ENC: 0.5ENC: 0.5 AC: 14AC: 14+8% EC: 3EC: 3current : ThreadLocal<ScanningContext>index : IndexViewregister(IndexView)getIndex() : IndexViewScanningContext(IndexView)remove()OperationCreator [+1 +1]Creates a Operation object @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 2DIT: 2 WMC: 25WMC: 25+9% ENC: 0.8ENC: 0.8+4% AC: 4AC: 4 EC: 26EC: 26+4%argumentCreator : ArgumentCreatorgetOperationAnnotation(OperationType) : DotNametoError(Type) : StringcreateOperation(...) : OperationgetExecution(Annotations, Annotations) : ExecutegetOperationName(MethodInfo, OperationType, Annotations) : StringgetDefaultExecutionTypeName(MethodInfo, OperationType) : StringOperationCreator(ReferenceCreator, ArgumentCreator)validateFieldType(MethodInfo, OperationType)ModelCreator [+2]Abstract creator @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 4NOC: 4 DIT: 1DIT: 1 WMC: 12WMC: 12+9% ENC: 0.6ENC: 0.6 AC: 7AC: 7 EC: 21EC: 21+17%directives : DirectivesreferenceCreator : ReferenceCreatorgetReturnType(FieldInfo) : TypepopulateField(...)getReferenceCreator() : ReferenceCreatorgetTypeAutoNameStrategy() : TypeAutoNameStrategyModelCreator(ReferenceCreator)populateField(Direction, Field, Type, Annotations)setDirectives(Directives)doPopulateField(Direction, Field, Type, Annotations)getReturnType(MethodInfo) : TypeWrapperCreator [+4 -3]Helper with detecting if this field is in a wrapper If it is we create anWrapperInfo model that contains the relevant information @author Phillip Kruger[phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 23WMC: 23+10% ENC: 0.6ENC: 0.6-11% AC: 2AC: 2 EC: 16EC: 16+78%createWrapper(Type, Type) : Optional<Wrapper>createWrapper(Type, Type, Annotations, List<String>) : Optional<Wrapper>WrapperCreator()getWrapperType(Type) : WrapperTypegetWrapperOfWrapper(Type, Annotations) : Optional<Wrapper>createWrapper(Type, Annotations) : Optional<Wrapper>createWrapper(Type) : Optional<Wrapper>createWrapper(Type, Type, Annotations) : Optional<Wrapper>markParameterizedTypeNonNull(Type, Type) : booleangetTypeInCollection(Type) : TypegetWrapperOfWrapper(Type) : Optional<Wrapper>ReferenceCreator(...)Here we create references to things that might not yet exist. We store allreferences to be created later. @author Phillip Kruger[phillip.kruger@redhat.com] NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 98WMC: 98 ENC: 0.7ENC: 0.7 AC: 10AC: 10 EC: 40EC: 40AdaptWithHelper [+1]Helping with object adapters. This adds support for Adapting one type toanother. @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 23WMC: 23 ENC: 0.5ENC: 0.5 AC: 2AC: 2 EC: 22EC: 22getAdapterType(Annotations) : AdapterTypehasAnyAdaptWithAnnotations(Annotations) : booleanshouldCreateTypeInSchema(Annotations) : booleangetAdaptWith(...) : Optional<AdaptWith>getAdaptWith(...) : Optional<AdaptWith>getAdaptWith(...) : Optional<AdaptWith>AdaptWithHelper()WrapperType [+1]Represent a wrapper type in the Schema. @author Phillip Kruger[phillip.kruger@redhat.com]AC: 3AC: 3 EC: 1EC: 1COLLECTIONARRAYRESULTUNKNOWNOPTIONALMAPOperation [+3 +1](...)Represent an operation on a Query or Mutation, or an operation on a Type in thecase of a Source annotation. a Operation if a special kind on field that allowsarguments. @see Field @author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 2DIT: 2 WMC: 19WMC: 19+19% ENC: 0.3ENC: 0.3+4% AC: 33AC: 33+3% EC: 11EC: 11+22%declaredErrors : List<String>addDeclaredError(String)toString() : StringgetDeclaredErrors() : List<String>Wrapper [+7 -3 +4](...)If the type is wrapped in a generics bucket or in an array, keep the info here.@author Phillip Kruger [phillip.kruger@redhat.com]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 35WMC: 35+13% ENC: 0.2ENC: 0.2+6% AC: 12AC: 12 EC: 10EC: 10+25%nonNull : booleannotEmpty : booleandeclaredErrors : List<String>Wrapper(WrapperType, String, boolean)equals(Object) : booleansetDeclaredErrors(List<String>)getDeclaredErrors() : List<String>isResult() : booleansetNonNull(boolean)hashCode() : intisNotEmpty() : booleantoString() : StringsetNotEmpty(boolean)isNonNull() : booleanOrderBlockedReason [+2]AC: 0AC: 0 EC: 0EC: 0INVALID_ADDRESSPAYMENT_FAILEDOrderBlockedException [+3]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0reason : OrderBlockedReasongetReason() : OrderBlockedReasonOrderBlockedException(OrderBlockedReason)Order [+8]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0orderDate : LocalDateid : StringOrder()setOrderDate(LocalDate)Order(String, LocalDate)getId() : StringsetId(String)getOrderDate() : LocalDateOrderNotFoundExceptionNOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0ResultWrapperTestingApi [+3]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0ORDER_LIST : List<Order>ORDERS : Map<String, Order>order(String) : Order🎯 Structural Regression: Breaking contract change in WrapperWhy review: Wrapperremoved public methodsisNotEmpty()andsetNotEmpty(boolean). 12 components depend on thistype, and the removal may cause compilation or runtimefailures in those dependents. This is a stable contractchange that should be coordinated across the codebase.Fix:Restore the removed methodsisNotEmpty()andsetNotEmpty(boolean)inWrapperand deprecatethem instead, or update all 12 known dependents(e.g.,AbstractHelper,DefaultMapAdapter,DataFetcherFactory,Bootstrap) to use the newisNonNull()/setNonNull(boolean)equivalentsbefore removal.striff.io \ No newline at end of file diff --git a/public/examples/smallrye-graphql-2083.svg b/public/examples/smallrye-graphql-2083.svg new file mode 100644 index 0000000..b0603f6 --- /dev/null +++ b/public/examples/smallrye-graphql-2083.svg @@ -0,0 +1 @@ +io.smallrye.graphqlapiclient.impl.coreutilsCustomFloatScalarA base class for all CustomScalars that are based on GraphQL's Float.WMC: 0WMC: 0 AC: 4AC: 4+33% EC: 1EC: 1floatValueForSerialization() : BigDecimalCustomIntScalarA base class for all CustomScalars that are based on GraphQL's Int.WMC: 0WMC: 0 AC: 4AC: 4+33% EC: 1EC: 1intValueForSerialization() : BigIntegerCustomStringScalarA base class for all CustomScalars that are based on GraphQL's String.WMC: 0WMC: 0 AC: 4AC: 4+33% EC: 1EC: 1stringValueForSerialization() : StringInputObjectImplNOC: 0NOC: 0 DIT: 2DIT: 2 WMC: 3WMC: 3 ENC: 0ENC: 0 AC: 1AC: 1 EC: 6EC: 6build() : StringVariableImplNOC: 0NOC: 0 DIT: 2DIT: 2 WMC: 2WMC: 2 ENC: 0ENC: 0 AC: 1AC: 1 EC: 10EC: 10build() : StringInputObjectFieldImplNOC: 0NOC: 0 DIT: 2DIT: 2 WMC: 1WMC: 1 ENC: 0ENC: 0 AC: 1AC: 1 EC: 5EC: 5build() : StringValueFormatter [+1]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 33WMC: 33+10% ENC: 0.7ENC: 0.7 AC: 4AC: 4 EC: 20EC: 20+33%UNQUOTED_VALUES : Class<?>[]QUOTED_VALUES : Class<?>[]assignableFrom(Class<?>, Class<?>[]) : booleanformat(Object) : String_processIterable(Iterable<?>) : String_processArray(Object) : String_getAsQuotedString(String) : StringCustomFloat [+1]// custom float scalarNOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0floatValueForSerialization() : BigDecimalCustomString [+1]// custom string scalarNOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0stringValueForSerialization() : StringCustomInt [+1]// custom int scalarNOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0intValueForSerialization() : BigIntegerValueFormatterTest [+3 +1]NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0testUnsupportedInput()testCustomStringScalarInput()testEnumInput()testCustomIntScalarInput()testCustomFloatScalarInput()striff.io \ No newline at end of file diff --git a/public/examples/spring-ai-session-33.svg b/public/examples/spring-ai-session-33.svg new file mode 100644 index 0000000..3c9a596 --- /dev/null +++ b/public/examples/spring-ai-session-33.svg @@ -0,0 +1 @@ +org.springframework.ai.session.advisorSessionMemoryAdvisor [+1 +2](...)ABaseAdvisorthat manages conversation history using theSessionService,with optional context compaction. On each interaction: Retrieves the session'sevent history and prepends it to the prompt messages. Appends the current usermessage to the session, if accepted by the configuredMessageFilter. After themodel responds, appends the assistant message[s] to the session; messagesrejected by the configuredMessageFilterare skipped. By default, emptyassistant messages [blank text, no tool calls, and no media] are filtered out.Optionally triggers context compaction if the configured trigger fires. The**session is identified by the#SESSION_ID_CONTEXT_KEY<...NOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 29WMC: 29 ENC: 0.6ENC: 0.6+3% AC: 0AC: 0 EC: 41EC: 41before(ChatClientRequest, AdvisorChain) : ChatClientRequestafter(ChatClientResponse, AdvisorChain) : ChatClientResponsegetEventFilter(Map<String, @Nullable Object>) : EventFilterSessionMemoryAdvisorIT [+4](...)Integration tests forSessionMemoryAdvisorexercised end-to-end through aSpring Boot application context. No external LLM is required — the advisor'sbefore[]andafter[]hooks are invoked directly to verify that the sessionis populated correctly and that conversation history is re-injected intosubsequent prompts. @author Christian TzolovNOC: 0NOC: 0 DIT: 1DIT: 1 WMC: 0WMC: 0 ENC: 0ENC: 0 AC: 0AC: 0 EC: 0EC: 0shouldAppendMessagesToResolvedBranch()shouldUseRuntimeBranchFilterWhenLoadingHistory()shouldPreserveReadWriteBranchSymmetry()shouldFallbackToConfiguredEventFilter()striff.io \ No newline at end of file diff --git a/src/data/prExamples.ts b/src/data/prExamples.ts new file mode 100644 index 0000000..386f117 --- /dev/null +++ b/src/data/prExamples.ts @@ -0,0 +1,279 @@ +/** + * The /examples gallery: four public pull requests, and the output Striff produced on them. + * + * Everything in this file is quoted from a real analysis of a real pull request, and every + * claim in it was checked by hand against the two revisions named in `revisions` before it + * was allowed onto the page. `audit` is that check, written down. If a row cannot be + * reproduced by following its own audit lines, it does not belong here. + * + * Two rules that were learned the hard way while assembling this: + * + * 1. Check the BASE revision, not only the head. "This edge is new" and "this namespace is + * gone" are both claims about what the code looked like before. + * 2. Check the diagram too, not only the text on the page. The rendered SVG carries the + * review notes inside it; a note that cannot be verified disqualifies the whole image, + * because a reader can zoom in and read it. Two otherwise excellent candidates were + * dropped for exactly that. + */ + +/** Which kind of thing the example is showing. Drives the badge colour only. */ +export type PrExampleKind = "contract" | "boundary" | "cycle" | "docs"; + +/** One block of output, quoted verbatim from the analysis. */ +export type PrExampleOutput = { + /** DETECTOR severity, or the word the check itself used. */ + severity: "HIGH" | "MEDIUM" | "LOW" | "EVIDENCE"; + title: string; + body: string; + /** The suggested direction, when the run produced one. Quoted verbatim. */ + fix?: string; +}; + +/** One documented rule, its citation, and what the evaluator returned. */ +export type PrExampleRule = { + doc: string; + quote: string; + statement: string; + verdict: "HOLDS"; + /** Where the declaration actually is, as read by hand at the head revision. */ + found: string; +}; + +export type PrExample = { + id: string; + kind: PrExampleKind; + kindLabel: string; + repo: string; + pr: string; + prTitle: string; + prState: "Open" | "Merged"; + href: string; + /** One sentence, legible to somebody who has never seen this repository. */ + lede: string; + /** The run's own one-line summary of the diff it looked at. Verbatim. */ + headline: string; + svg: string; + /** How the diagram opens. Fractions of the image, so the same point at any stage width. */ + panzoom: { + focus: { x: number; y: number }; + desktopScale: number; + mobileScale: number; + }; + viewport: { desktopHeight: string; mobileHeight: string }; + revisions: { baseLabel: string; base: string; headLabel: string; head: string; compare: string }; + outputs: PrExampleOutput[]; + rules?: PrExampleRule[]; + /** Present when the run recorded the finding as evidence rather than raising it. */ + quietNote?: string; + /** What was checked by hand, and what was found. Each line is independently reproducible. */ + audit: string[]; +}; + +export const prExamples: PrExample[] = [ + { + id: "smallrye-1424", + kind: "contract", + kindLabel: "Contract change", + repo: "smallrye/smallrye-graphql", + pr: "#1424", + prTitle: "#1423: add GraphQLResult and wrap successful result", + prState: "Open", + href: "https://github.com/smallrye/smallrye-graphql/pull/1424", + lede: "Two accessors on a schema model class were renamed. The rename is one line in the diff. The number of components that depend on the class is not in the diff at all.", + headline: "1 structural regression detected", + svg: "/examples/smallrye-graphql-1424.svg", + panzoom: { focus: { x: 0.575, y: 0.65 }, desktopScale: 2.4, mobileScale: 2.4 }, + viewport: { desktopHeight: "32rem", mobileHeight: "12rem" }, + revisions: { + baseLabel: "base", + base: "6e20902", + headLabel: "head", + head: "ee8436b", + compare: "https://github.com/smallrye/smallrye-graphql/compare/6e20902ba83b5c62013df61d6eb9711931b1a9e0...t1:smallrye-graphql:ee8436b9614a1fae350fc5f299e711ac20d30293" + }, + outputs: [ + { + severity: "HIGH", + title: "Breaking contract change in Wrapper", + body: "`Wrapper` removed public methods `isNotEmpty()` and `setNotEmpty(boolean)`. 12 components depend on this type, and the removal may cause compilation or runtime failures in those dependents. This is a stable contract change that should be coordinated across the codebase.", + fix: "Restore the removed methods `isNotEmpty()` and `setNotEmpty(boolean)` in `Wrapper` and deprecate them instead, or update all 12 known dependents (e.g., `AbstractHelper`, `DefaultMapAdapter`, `DataFetcherFactory`, `Bootstrap`) to use the new `isNonNull()`/`setNonNull(boolean)` equivalents before removal." + } + ], + audit: [ + "`Wrapper.java`, in the `common/schema-model` module, declares `setNotEmpty(boolean)` and `isNotEmpty()` at the base revision. At the head revision it declares `setNonNull(boolean)` and `isNonNull()` instead — and neither of the two methods the check names.", + "Twelve files under `src/main` reference `Wrapper` at both revisions — the same count the check reports.", + "All four dependents the suggested fix names — `AbstractHelper`, `DefaultMapAdapter`, `DataFetcherFactory`, `Bootstrap` — reference `Wrapper` at the head revision.", + "The rename is the pull request's own work: the commit that performs it is on the branch, and the base revision is the merge base, so nothing here is an artefact of the branch being behind.", + "Only two `isNotEmpty()` calls survive anywhere in the repository at the head revision, and both are AssertJ string assertions in integration tests that have nothing to do with this class." + ] + }, + { + id: "citrus-370", + kind: "cycle", + kindLabel: "A loop closed", + repo: "citrusframework/citrus-simulator", + pr: "#370", + prTitle: "feat: persist only failed scenarios", + prState: "Open", + href: "https://github.com/citrusframework/citrus-simulator/pull/370", + lede: "A listener class gained a constructor parameter so it could read a configuration flag. That one parameter is the last edge of a loop that runs through four packages.", + headline: "7 components, 13 relationships, no items surfaced (2 evidence-only findings)", + svg: "/examples/citrus-simulator-370.svg", + panzoom: { focus: { x: 0.66, y: 0.60 }, desktopScale: 1, mobileScale: 1 }, + viewport: { desktopHeight: "38rem", mobileHeight: "22rem" }, + revisions: { + baseLabel: "base", + base: "c658ef8", + headLabel: "head", + head: "51917ce", + compare: "https://github.com/citrusframework/citrus-simulator/compare/c658ef84ef5cabba71bf8b7a99ebce664a9aa785...postfinance:citrus-simulator:51917ce9913122728aa9f756a1af203510507a57" + }, + outputs: [ + { + severity: "LOW", + title: "New directional dependency", + body: "New dependency from `org.citrusframework.simulator.listener` to `org.citrusframework.simulator.config`, with no prior edge in this direction." + }, + { + severity: "MEDIUM", + title: "Package cycle detected", + body: "A dependency cycle among 11 packages (37 edges) already turned before this PR, which adds org.citrusframework.simulator.listener -> org.citrusframework.simulator.config to it." + } + ], + quietNote: + "Both of these were recorded as evidence behind the diagram. Neither was raised as an item on the check — the loop was already turning, and this change adds an edge to it rather than creating it.", + audit: [ + "At the base revision, no file in `org.citrusframework.simulator.listener` references `org.citrusframework.simulator.config`. At the head revision, `SimulatorStatusListener` imports `SimulatorConfigurationProperties`.", + "The way back was already there, in production sources only, at the base revision: `SimulatorConfigurer` (config) imports `ScenarioMapper` (scenario.mapper); `ScenarioMappers` imports `HttpOperationScenario` (http); `SimulatorRestAutoConfiguration` imports `SimulatorMessageListener` (listener).", + "Those three edges plus the one this pull request adds form a closed loop: config → scenario.mapper → http → listener → config.", + "Rebuilding the package graph from imports independently puts both `listener` and `config` inside one strongly connected component at the base revision, which is what the second finding says: the cycle predates the change." + ] + }, + { + id: "smallrye-2083", + kind: "boundary", + kindLabel: "Boundary crossing", + repo: "smallrye/smallrye-graphql", + pr: "#2083", + prTitle: "Add custom scalar support to client value formatter", + prState: "Open", + href: "https://github.com/smallrye/smallrye-graphql/pull/2083", + lede: "Two files changed. One of them is a formatting helper inside the client, and it started importing types that are declared in the server module.", + headline: "11 components, 13 relationships, no items surfaced (1 evidence-only finding)", + svg: "/examples/smallrye-graphql-2083.svg", + panzoom: { focus: { x: 0.55, y: 0.58 }, desktopScale: 1.2, mobileScale: 1.2 }, + viewport: { desktopHeight: "32rem", mobileHeight: "10rem" }, + revisions: { + baseLabel: "base", + base: "c48e433", + headLabel: "head", + head: "b2484d4", + compare: "https://github.com/smallrye/smallrye-graphql/compare/c48e4330495cd4fe8374326cf26a014caaff7615...sap-ali:smallrye-graphql:b2484d4f130443119cc841c00635c2a38fb66749" + }, + outputs: [ + { + severity: "LOW", + title: "New directional dependency", + body: "New dependency from `io.smallrye.graphql.client.impl.core.utils` to `io.smallrye.graphql.api`, with no prior edge in this direction." + } + ], + quietNote: + "This is the quiet end of the output. One evidence-only finding, nothing raised on the check, and a diagram that puts the new edge in front of you without anybody having to argue about it.", + audit: [ + "The pull request changes exactly two files: `ValueFormatter.java` and its test.", + "At the base revision the package `io.smallrye.graphql.client.impl.core.utils` holds one production file and one test file, and neither mentions `io.smallrye.graphql.api`.", + "At the head revision `ValueFormatter` imports `CustomFloatScalar`, `CustomIntScalar` and `CustomStringScalar`, and branches on all three when formatting a value.", + "Those three types are declared under `server/api/src/main/java/io/smallrye/graphql/api/` — a different Maven module from the client implementation that now reaches for them." + ] + }, + { + id: "spring-ai-session-33", + kind: "docs", + kindLabel: "Documented rules", + repo: "spring-ai-community/spring-ai-session", + pr: "#33", + prTitle: "Add runtime branch isolation to SessionMemoryAdvisor", + prState: "Open", + href: "https://github.com/spring-ai-community/spring-ai-session/pull/33", + lede: "Nineteen sentences from this repository's own docs folder became checkable rules on this pull request. All nineteen held. Six of them are below, with the sentence each one came from.", + headline: "2 components, 2 relationships, no structural anomalies", + svg: "/examples/spring-ai-session-33.svg", + panzoom: { focus: { x: 0.5, y: 0.79 }, desktopScale: 1, mobileScale: 1 }, + viewport: { desktopHeight: "34rem", mobileHeight: "21rem" }, + revisions: { + baseLabel: "base", + base: "b283ba5", + headLabel: "head", + head: "0e70aa2", + compare: + "https://github.com/spring-ai-community/spring-ai-session/compare/b283ba585395a6b9717f77b2487b68beac0773de...AdepuSriCharan:spring-ai-session:0e70aa2821e965a12139841e43527a9de9a0c422" + }, + outputs: [], + rules: [ + { + doc: "docs/session-management/multi-agent.md", + quote: + "To apply branch isolation automatically inside `SessionMemoryAdvisor`, configure the `eventFilter` on the builder:", + statement: "SessionMemoryAdvisor depends on EventFilter", + verdict: "HOLDS", + found: "SessionMemoryAdvisor.java imports EventFilter and holds one as a field" + }, + { + doc: "docs/session-management/cross-session-recall.md", + quote: "The term count is capped at 20 per call (`CrossSessionRecallTools.MAX_QUERY_TERMS`).", + statement: "CrossSessionRecallTools declares `MAX_QUERY_TERMS` as a field", + verdict: "HOLDS", + found: "CrossSessionRecallTools.java line 75, `static final int MAX_QUERY_TERMS = 20`" + }, + { + doc: "docs/session-management/compaction.md", + quote: "All four strategies share a common safety rule enforced by `CompactionUtils.snapToTurnStart`:", + statement: "CompactionUtils declares `snapToTurnStart` as a method", + verdict: "HOLDS", + found: "CompactionUtils.java line 99" + }, + { + doc: "docs/session-management/recall-storage.md", + quote: "Events dropped from the active window are flagged `SessionEvent.isArchived()` and kept in the log.", + statement: "SessionEvent declares `isArchived` as a method", + verdict: "HOLDS", + found: "SessionEvent.java line 153" + }, + { + doc: "docs/session-management/recall-storage.md", + quote: + "Page size defaults to `EventFilter.DEFAULT_PAGE_SIZE` (10) and is configurable via the builder (`SessionEventTools.builder(sessionService).pageSize(20).build()`).", + statement: + "EventFilter declares `DEFAULT_PAGE_SIZE` as a field, and SessionEventTools declares `builder` as a method", + verdict: "HOLDS", + found: "EventFilter.java line 133 (`= 10`); SessionEventTools.java line 87" + }, + { + doc: "docs/session-management/compaction.md", + quote: + "This ensures `tokensEstimatedSaved` in `CompactionResult` accurately reflects the full cost of removed events, including tool-heavy turns.", + statement: "CompactionResult declares `tokensEstimatedSaved` as a field", + verdict: "HOLDS", + found: "CompactionResult.java line 31, a component of the record" + } + ], + quietNote: + "Nineteen rules, nineteen held, nothing raised. That is the ordinary result, and a page that only showed you the other kind would be lying about how often this fires.", + audit: [ + "Every sentence above appears verbatim in the file it is attributed to, at the head revision, once whitespace from the Markdown line wrapping is normalised.", + "Every declaration each rule asserts was read by hand in the source at the head revision; the file and line are printed beside it.", + "Every quote is prose from the documentation, not a code sample lifted out of a fenced block.", + "The base revision is the merge base of the pull request, so the comparison is the branch's own work and nothing else." + ] + } +]; + +/** + * The sample this page was drawn from. Stated because four hand-picked examples say nothing + * about how often any of this fires, and the honest answer is: most of the time, nothing. + */ +export const prExampleSample = { + pullRequests: 79, + repositories: 27, + noFinding: 45 +}; diff --git a/src/pages/examples.astro b/src/pages/examples.astro index 895ad42..2ff3d24 100644 --- a/src/pages/examples.astro +++ b/src/pages/examples.astro @@ -1,42 +1,63 @@ --- import BaseLayout from "../layouts/BaseLayout.astro"; import InlineCodeText from "../components/InlineCodeText.astro"; -import { exampleReviews } from "../data/exampleReviews"; +import { prExamples, prExampleSample } from "../data/prExamples"; -const extensionUrl = "https://chromewebstore.google.com/detail/striffs-for-github/gcbcjajnjbplgkhnbemlkadgnjnfjoen"; +const extensionUrl = + "https://chromewebstore.google.com/detail/striffs-for-github/gcbcjajnjbplgkhnbemlkadgnjnfjoen"; ---
-

- Public repo examples -

-

Four PRs that passed review and shouldn't have.

+

Public pull requests

+

Four changes, and what the structure said about them.

- Real public pull requests in Java, Python, TypeScript, and C#. Every one of them looked routine in the file diff, got approved, and quietly moved a boundary somebody will pay for later. Open any of them on GitHub and check what Striff caught for yourself. + Every example below is a real pull request in a public repository. The diagram is + the one Striff drew; the boxed text is what it produced, word for word. Under each + one is the check we ran by hand against both revisions before putting it here — so + you can repeat it rather than take our word for it.

-
-
- What to look for -

Boundary shifts, duplicated responsibilities, shared hotspots, and the relationships a file diff hides.

+
+
+ What you are looking at +

+ A structural diff: the components a change touched, and the dependencies between + them at both revisions. Additions in green, removals struck through, and the + relationships that only exist on one side of the change. +

+
+
+ Why these four +

+ Each shows something the file diff cannot: a type's dependents, a loop that closes, + a module boundary crossed by two files, a sentence in a design doc checked against + the code. +

-
- Why this matters -

These risks are invisible in a line-by-line diff. They only show up when you look at the change as a dependency graph, the way Striff does on every PR.

+
+ How often this fires +

+ These four were picked from {prExampleSample.pullRequests} public pull + requests across {prExampleSample.repositories} repositories. {prExampleSample.noFinding} + of them produced no finding at all. Most pull requests do not move the architecture, + and Striff is built to say so quietly. +

@@ -44,220 +65,596 @@ const extensionUrl = "https://chromewebstore.google.com/detail/striffs-for-githu +
- {exampleReviews.map((example, i) => ( - -
- -
-
- - {example.language} - {example.repo} - {example.pr} - Public PR -
-
-

{example.prTitle}

-

{example.summary}

-
-
- -
- -
-
-
- Striff review + {prExamples.map((example, i) => ( +
+ +
+
+ {example.kindLabel} + {example.repo} + {example.pr} + {example.prState} PR +
+
+

+ + {example.prTitle} + +

+

{example.lede}

+
-
- {example.metrics.slice(0, 2).map((metric) => ( - {metric} - ))} + +
+ +
+
+
+ Structural diff + {example.headline} +
+
+ + {example.revisions.baseLabel} {example.revisions.base} + + + + {example.revisions.headLabel} {example.revisions.head} + + +
-
-
-
-
-
-
- - - Open - - {example.prTitle} -
- {example.repo} {example.pr} -
-
- - - Conversation 22 - - - - Checks 11 - - - - Files changed 17 - +
+
+ {`Striff +
+ + + + + + Scroll to zoom · Drag to pan
-
- -
-
-
-

striff / architectural-review

-

Architectural review completed

-

Completed with {example.checks.length} findings across {example.metrics[0].toLowerCase()}.

-
- -
-
-
-
- {`${example.language} -
-
- -
- -
-
- - striff / architectural-review -
-
- Completed in GitHub Checks - +
+
+ +
+
+

+ {example.rules ? "Rules read out of the docs" : "What Striff produced"} +

+ + {example.outputs.map((output) => ( +
+
+ {output.severity} + {output.title}
+ + {output.fix && ( +

+ Suggested direction + +

+ )}
-
-
- - - Completed - - 1 check run with findings -
-
- Summary - Findings {example.checks.length} -
-
-
-

Review headline

-

{example.headline}

-

{example.checkHeader}

-
-
- {example.metrics[0]} - {example.metrics[1]} -
-
-
- {example.checks.map((check) => ( -
- - {check.level === "danger" ? "!" : check.level === "warn" ? "~" : "i"} - -
-

- + ))} + + {example.rules && ( +
    + {example.rules.map((rule) => ( +
  1. +
    + {rule.doc} + {rule.verdict}
    -
- ))} -
- -
-
-
-
+ + )} + + {example.quietNote && ( +

+ +

+ )}
-
-
- -
-
- ))} +
+ ))}
+ +

+ These are other people's repositories. Nothing here is a judgement about the code or the + people who wrote it — it is a record of what a change did to the shape of a codebase, and + every one of these pull requests is a perfectly reasonable change to make. +

+ +