Managing dependencies
STIG's pinned dependency/toolchain areas have separate workflows:
Ecosystem | Source of truth | Used by |
|---|---|---|
Gradle runtime |
| Wrapper launchers in STIG and each consumer |
Kotlin / Gradle / JVM |
| All |
JavaScript / CSS / media tooling | Exact direct pins in | Dokka HTML output, browser tests, and headless Revideo documentation media |
Analyzer C++ toolchain |
| LLVM / PhASAR analyzer build inputs |
Developer GitLab tools | Binary versions and SHA-256 checksums in | Local CI execution, pipeline submission, and official Runner service management |
Qodana native inspection tools |
| Repository |
The developer GitLab tools are installed on demand in the user's cache, outside product dependencies. Update their binary versions and upstream checksums together, and verify the CLI contracts and configuration-cache behavior with GitlabDeveloperTasksTest. See developer CI commands for setup and use.
Build-logic language policy
Repository verification implementations live in stig-gradle-plugin/src/repositoryChecks/kotlin/com/stigway/gradle/repository. This independent Kotlin source set uses only the JDK and Kotlin standard library. Built-in JavaExec tasks invoke the checkers during execution; repositoryChecksElements supplies the archive checker to stig-idea-plugin. The root checkRestrictedKeywords task delegates to :stig-gradle-plugin:checkRestrictedKeywords. Neither checker requires the plugin's main classes, native analyzer, or an early build-script classpath dependency. Repository checkers are not packaged in the published consumer plugin. buildSrc is not used; adding it requires explicit user approval under AGENTS.md.
Kotlin and the Gradle Kotlin DSL are the default languages for repository-owned build logic, validation, metadata transformation, and static generation. The output format does not determine the implementation language: a task that deterministically writes JavaScript, JSON, HTML, SVG, or another static artifact should normally be implemented in Kotlin. Current examples are stigGenerateAsilPaletteJs, stigGenerateNexyGaugeFixtureJs, and stigGenerateStigIcons.
Build-time-compiled Kotlin KTE templates are the approved presentation layer for source/configuration scaffolds whose output is predominantly fixed text, currently the source-code-analysis Gradle DSL and C++ harness artifacts. Templates use ContentType.Plain; parsing, validation, ownership checks, lifecycle decisions, and template-model construction remain in Kotlin. KTE templates must not become a second business-logic layer. KotlinPoet remains the structured generator for Kotlin declaration files such as initialized viewpoints.
Node.js remains part of the supported and pinned toolchain. JavaScript or MJS is appropriate when it is the natural integration layer rather than merely a convenient scripting language, specifically:
browser runtime code bundled into generated reports;
browser-oriented tests and their small test-server/configuration adapters, such as Playwright tests;
thin configuration or adapters for Node-native tools such as esbuild; and
npm ecosystem operations that benefit from npm's canonical APIs and SemVer implementation.
scripts/update-js-vendor-deps.mjs is an intentional example of the last exception. It invokes npm view and uses the exactly pinned npm semver package to evaluate package versions, Node engines ranges, prereleases, and peer dependency compatibility. Moving it to Kotlin would still require invoking npm while either duplicating npm-specific version semantics or adding another JVM interpretation of them, so MJS is the clearer boundary. It is an explicit dependency-maintenance tool, not part of normal static asset generation.
Shell scripts may orchestrate OS package installation, environment selection, and external tool processes, but should not become a second implementation language for model transformations or static generators. Do not introduce Python or any other additional implementation language into build, generation, or repository tooling without explicit agreement. Existing exceptions do not establish permission for new ones. Prefer opportunistic, low-risk migration to Kotlin when existing non-Kotlin static-generation logic is touched; a blanket rewrite of browser code or Node-native integrations is neither required nor desired.
The plugin's independent verificationSupport source set owns dependency-verification policy parsing, conservative merging, reproducible release verification bundles, and verification of detached publication signatures. It uses only Kotlin/JDK APIs and invokes GnuPG with an isolated public keyring. Its classes are packaged in the consumer plugin and the repository checker archive. Repository maintenance invokes the same implementation through JavaExec tasks; the shell sequences those tasks and collects approval of the displayed trust changes.
License Guardrail
Do not add GPL-family dependencies to shipped STIG artifacts without an explicit licensing decision. The native analyzer is distributed with STIG, and the Dokka viewer bundle is shipped inside the Gradle plugin JAR, so both dependency sets affect downstream users.
Two removals are intentional and should not be reintroduced as dormant code, commented imports, or disabled build branches:
SVF integration was removed from the analyzer. PhASAR is configured with
PHASAR_USE_SVF=OFF; there are no SVF alias modes, SVF checker sources, or SVF build pins in the active toolchain.cytoscape-svgwas removed from the viewer bundle. SVG export/viewer behavior must use the current non-GPL viewer stack or another compatible dependency.
When adding Kotlin/Gradle, npm, or native dependencies, check the resolved license metadata in the lockfile or upstream project and keep the changelog and developer docs in sync with any licensing-relevant decision.
Add a Kotlin / Gradle dependency
There is no gradlew add command. Edit gradle/libs.versions.toml directly:
Then reference it in the consuming module's build.gradle.kts:
Refresh the affected dependency locks after a deliberate version/dependency edit. IntelliJ needs a Gradle reimport for autocomplete to resolve libs.jsoup.
For plugin DSL entries (alias(libs.plugins.foo)), add the plugin under [plugins] in the same TOML.
Locked JVM dependencies
Each of the seven modules applies gradle/dependency-locking.gradle.kts to its own JVM compile/runtime configurations, including test and custom source sets. Strict lock mode rejects unresolved lock changes; an exact direct catalog pin alone does not pin transitive resolution. The included build-logic build has its own lockfile and imports the same catalog. Normal builds never rewrite these files.
For a deliberate dependency update, refresh the convention build first because its plugin must load before the main build can configure. Then refresh the main build and review the lockfile diff:
Without --write-locks, resolveLockedDependencies validates the existing resolution. The root task includes the convention build. It resolves classpaths but never requests native compilation, analyzer execution, or PTH import. Maintenance uses configuration-time providers to resolve project models before configuration-cache serialization. The maintenance tasks deliberately discard their configuration-cache entry so every invocation performs fresh resolution, without serializing project or script objects. A dry run skips this resolution. scripts/update.sh follows this ordering when updating the catalog and keeps the convention build's Foojay resolver aligned with the root settings pin.
gradle/verification-metadata.xml enables Gradle's strict SHA-256 dependency verification, including repository metadata. resolveVerificationDependencies extends the lock-resolution scope with external source JARs from every module's JVM classpaths, including test source sets and the included convention build. The root tasks select all subprojects from the Gradle project model, so adding a module needs no separate maintenance list. It covers both source variants and the IDE's source-classifier queries for all resolved module components, including multiplatform modules whose binary variants redirect to another JVM module. Settings, root, and module build-plugin source JARs are resolved through their own buildscript repositories. The settings classpath is separate from project classpaths and includes Develocity and the Foojay resolver. The IDEA plugin module also resolves the pinned SDK sources by release version and platform build number. Artifact-query results are read to trigger Gradle's deferred verification. Synthetic Ivy modules extracted from the IDE archive have no separate sources; their binaries are covered by archive and classpath verification. It also resolves sources and metadata for Gradle's bundled Groovy modules, the running Gradle version's gradle-<version>-src.zip distribution, the separate POM queries used by third-party notices, and the lazily resolved Node archive. It checks that archive against Node's official checksum without running Node setup. IDE imports request source attachments independently of ordinary compilation, so a successful binary-only build cannot verify this scope. To deliberately refresh checksums without changing dependency selections:
Review every newly accepted checksum before committing. The initial generated file is a trust-on-first-use baseline of the resolved artifacts, not independently authenticated publisher provenance. Missing or changed downloaded artifacts fail normal builds; do not switch verification to lenient/off to continue. A new platform-specific artifact also requires an explicit reviewed checksum. The only exceptions are the IntelliJ plugin's generated Ivy descriptors in the bundledModule, bundledPlugin, and localIde groups, whose contents include absolute local paths. The downloaded IDE archive and bundled JARs remain verified.
The plugin verifier takes its IDE files directly from intellijPlatformDependency, reusing the pinned compilation SDK. Do not register that extracted SDK with pluginVerification.ides.current() or local(): those helpers turn the directory into a synthetic localIde artifact, and Gradle's PGP metadata writer fails while hashing it. org.jetbrains.intellij.platform.verifyPluginDefaultRecommendedIdes=false prevents additional IDE selection. This keeps the downloaded archive and bundled JARs under verification without duplicate IDE extraction or an exception for directory artifacts. The wrapper and dependency update phases use this same configuration.
scripts/update.sh retains the pre-update verification policy, selects armored key export, and refreshes SHA-256 pins and PGP metadata in STIG, the independent convention build, and the public template. A separate SHA-256 resolution pass records hashes for signed artifacts too. The Kotlin audit rejects changed hashes for already pinned artifacts, new verification bypasses, and Gradle-generated fallbacks for failed signatures. It reports newly trusted fingerprints and their scopes for maintainer review. Preparation removes only Gradle's automatically generated missing-key exclusions, preserving deliberate exclusions and every accepted checksum. PGP generation uses --refresh-dependencies to revisit cached missing signatures, --refresh-keys to retry cached key-download failures, and exports the resolved public keys. This makes a maintenance run reconsider metadata and signatures that a fresh CI cache will fetch instead of retaining a local missing-signature result. Unless the policy already configures key servers, preparation selects https://keyserver.ubuntu.com and https://keys.openpgp.org, avoiding the unavailable servers in Gradle's default pool. The audit rejects any remaining generated missing-key exclusions instead of completing with an incomplete key refresh. Confirm publisher identities and review deliberate exceptions before approving the prompt; --grammar-yes does not approve dependency trust. The script then performs strict source and binary resolution without write flags, followed by the STIG build. The shell saves the accepted XML before Gradle loads settings plugins. Preparation refreshes PGP metadata explicitly, so a retry can discover settings-plugin signers before ordinary strict invocations. This generation step does not replace the Kotlin audit or trust prompt. On failure, cancellation, or declined trust, the script restores the accepted XML from each checkout's .gradle/stig/verification-update/before.xml and retains the candidate XML and public keys in a failed-* directory beside it. A retry also restores any unfinished policy left by an older updater before loading Gradle. Public keyring entries alone do not grant trust; the restored XML determines which keys and checksums are accepted. Version and lock changes remain available for review, and the original baseline survives retries until verification and the build pass.
The Gradle plugin publication includes a signed verification ZIP classifier containing the release version, verification XML, armored public keyring, exact Gradle wrapper, daemon JVM criteria and standalone consumer updater. createStigVerificationBundle requires signatures to be enabled, SHA-256 pins to be present, and the exported armored keyring to exist. The publication-signature gate also verifies this ZIP before upload. Establish and review the incoming PGP baseline before releasing an updater-capable build; adding the packaging task does not itself approve publisher keys or enable the checked-in policy. Use scripts/update.sh --verification to establish or refresh this policy for the current versions without upgrading dependencies or running the full build. --dependencies deliberately preserves signing-key trust and cannot enable the PGP policy required by the publication bundle.
Consumer stigUpdate starts from its checked-in standalone updater before Gradle loads, and authenticates this bundle before advancing the version pin or installing its Gradle runtime. It never treats a successful checksum-generation run as publisher authentication. See installation for the version handover and dependency-only maintenance command.
Gradle uses the current top-level build's verification policy for included builds. STIG's policy covers build-logic when invoked from STIG; it does not replace a consumer's own policy when STIG is included in that consumer. The consumer source-mode regression fixtures exercise selection independently of this checksum baseline. build-logic/gradle/verification-metadata.xml covers independent -p build-logic invocations.
The public template, the product consumer and PTH also own strict JVM locks and verification metadata. Their settings apply the consumer-owned locking script with gradle.lifecycle.beforeProject, including newly initialized modules, rather than mutating subprojects from the root. PTH's standalone preprocessing build owns a separate baseline. See each consumer's gradle/README.md for refresh commands. A source-mode/version change that changes the resolved graph requires an explicit reviewed refresh; normal builds never silently rewrite it.
The version catalog also owns the compile-testing fork pin. Its selected version remains 0.12.1; the former root extra property that overrode a different catalog entry has been removed.
Native FetchContent dependencies use immutable commits for the selected CLI11, nlohmann/json, and GoogleTest releases, not movable Git tags. Commit-pinned checkouts do not use shallow tag-only cloning. The PhASAR commit and LLVM major remain owned by scripts/pins.env; OS package versions are represented in the native toolchain identity, not claimed to be a fully hermetic toolchain.
Wrapper distributions in STIG and the three consumers have distributionSha256Sum checksums. The wrapper updater fetches the checksum for the exact newly selected distribution; it never removes verification to allow an upgrade.
Daemon JVM and compilation toolchains
The java entry in gradle/libs.versions.toml owns the approved Java provisioning release, including its build number. Its feature version selects the compiler, tests, default consumer model toolchain and isolated STIG engine launchers. The IDEA plugin uses the same target. The current feature is Java 25. The standalone consumer updater, its verification support and shared execution API are compiled with that JDK but retain Java 21 bytecode and API compatibility: the updater runs before Gradle can select or provision its daemon. No Java 21 compiler is required.
scripts/update.sh updates Java after the IDEA target. Kotlin build logic reads the pinned IDEA distribution's bundled JBR release file and rejects a Java feature newer than that runtime. --patch permits patch, hotfix and build updates; --minor also permits Java interim updates; --major permits feature updates within that IDEA runtime limit. The normal catalog exception mechanism applies: add versions.add("java") to versionCatalogUpdate { pin { ... } }, with the incompatibility and removal criterion documented beside it, to hold the complete release and its existing provisioning URLs and archive checksums.
gradle/java-toolchain.properties records exact Foojay package URLs and SHA-256 archive checksums for the approved release: Temurin on Linux, macOS and Windows x64, and Zulu on Windows ARM64, where Temurin has no distribution. Every required platform must have the exact release before either pin file changes. The checksum authenticates the approved archive's bytes; different operating systems and architectures necessarily have different archives and checksums. A changed checksum for an unchanged package is an error, not an automatically accepted update.
The checked-in gradle/stig-java-bootstrap.jar provisions this archive before Gradle starts. Its source lives in the plugin's javaBootstrap source set; syncJavaBootstrap regenerates the reproducible archive and checkJavaBootstrap verifies it. The consumer updater packages the same implementation. The bootstrap verifies the download before extraction and checks the installed content inventory when launching a build. Installations are shared under <Gradle user home>/stig/java/<platform>/<sha256>/jdk, with a file lock for concurrent launches. Projects selecting the same platform and checksum reuse one installation.
Gradle's daemon criteria select a Java feature, not an exact release and checksum. They also take precedence over org.gradle.java.home. STIG therefore keeps the criteria file comment-only and supplies the verified Java home before launching Gradle. syncJavaDaemonCriteria installs that maintained file in the root and build-logic; direct updateDaemonJvm is rejected. Compiler discovery and automatic Gradle downloads are restricted so another installed Java patch cannot silently replace the selected runtime. The bootstrap supplies org.gradle.java.installations.* command-line settings with -D, the supported Gradle property syntax; the deprecated -P project-property syntax is not used. Settings validation checks the effective policy, including command-line overrides, before selecting a compiler.
Task-reference generation creates temporary Gradle projects in a separate JVM. Each temporary project also disables Java auto-detection and downloads and points compiler discovery at the generator's approved JVM before applying the STIG plugin. These projects do not inherit the outer build's discovery settings.
Consumer stigUpdate installs the Java release metadata, bootstrap, disabled daemon criteria and maintained compiler convention from the selected STIG checkout or authenticated updater JAR. It propagates the same policy to the actual recursively included builds whose canonical directories are inside the consumer root. Sibling STIG checkouts and other external builds retain their own policy. Existing included-build wrappers receive the selected root wrapper JAR and distribution properties, with the maintained Java bootstrap hook; builds without wrappers do not acquire one. Managed settings hooks persist the convention for ordinary builds and standalone invocations of an included build. The standalone updater's maintenance init script installs the policy before included settings plugins compile, so first adoption also covers plugins needed to configure the consumer. This script is invocation-scoped and does not modify Gradle's global init directory. Published updates obtain the new updater before launching Gradle. An older checked-in updater used for local-source adoption must first be refreshed from the selected checkout; see installation.
The selected STIG release owns the compiler feature: the convention aligns an older explicit jvmToolchain(21) with the selected release and reports the change. Explicit source, bytecode and API targets remain separate; a Java 21 bootstrap target does not make Java 25 dependencies usable on Java 21. The consumer does not independently select a newer Java release. Its maintained files under gradle/ and settings hooks belong in version control alongside the updater and wrapper.
The launcher accepts any working Java 21 or newer from JAVA_HOME, then from PATH. That JVM only starts the bootstrap; the selected STIG release owns the JVM used for Gradle, compilation and tests. An additional Java 21 installation is unnecessary when a newer launcher is already available. Offline builds require the approved archive or verified installation in the shared cache; a different installed Java 25 is not a fallback.
IDE Gradle imports bypass the wrapper launcher. The STIG IDEA plugin runs the project's versioned gradle/stig-java-bootstrap.jar --java-home entry point before Gradle validates its JVM or starts a daemon, for both imports and task execution. This uses the same exact release, checksum verification, proxy configuration and shared cache as the wrapper. IDEA's Gradle user home and offline setting are passed to the bootstrap. The plugin registers or reuses an SDK for the verified home and selects it for that linked Gradle project. Every execution checks the current pin, so another project's runtime or an SDK selected before an update cannot silently substitute for it. Preview imports and ordinary projects without the STIG bootstrap are unchanged. The IDEA application itself continues to use its bundled JetBrains Runtime.
Other Tooling API clients must likewise provision the runtime through the bootstrap and select its returned home before loading settings plugins. Settings validation rejects a different runtime.
Shared conventions, isolation, and relocation
build-logic is a standard included plugin build, not buildSrc. Each module applies the shared JVM module convention itself. It owns shared JVM/compiler/test defaults, legal archive contents, reproducible file order, timestamp-independent archives, and documentation/publication aliases. Dependencies between projects use Gradle's type-safe projects accessors in STIG and all three consumers. PTH components own their dependencies instead of receiving them through root subprojects mutation.
Isolated Projects isolates mutable Gradle configuration, not application dependencies. Core, engine and plugin modules may still depend on one another. Keep those dependencies and the type-safe accessors.
Generated knowledge references, media and glossary now cross project boundaries as outgoing artifacts. The engine owns its generated glossary under its own build directory. The Codex generator consumes separate plugin classes, runtime dependencies, version metadata and descriptors; it cannot depend on the plugin JAR, which embeds the generated Codex bundle. Engine generators and IDEA inspection generation resolve their own declared runtime classpaths. Authored repository files use the read-only IsolatedProject directory view. Both Node workspace consumers register the same build-logic service themselves; the OS file lock retains cross-process protection without one project configuring another's tasks.
Isolated Projects is not enabled globally. The earlier help diagnostics recorded 207, then 54 problems (21 diagnostic entries). After the artifact/service migration, the strict pilot stops during application of Axion 1.21.4: its allprojects callback accesses another project's Project.provider. Warning-mode diagnostics hit the same hard failure, so this is not a new complete count of remaining violations. The convention still reads Axion's canonical root version, and task-specific reads such as IDEA changelog access also need a contract. Keep Axion as the single version authority; do not add another Git/version parser. A compatible canonical version bootstrap and third-party plugins are prerequisites for a new complete pilot, including consumer builds and real task graphs rather than help alone.
The convention build's test verifies byte-identical fixture JARs after timestamp-only changes and relocation, plus FROM-CACHE Java compilation in the relocated project. Native build trees, absolute compilation commands, and analysis evidence paths are not covered by that relocation guarantee. See local development for native cache ownership and conservative incremental reuse.
Two additional offline fixtures verify that the consumer-owned lifecycle callback supports isolated configuration and rejects missing/changed locks and incorrect checksums, and that isolated projects share and release the Node workspace lock on both cold and warm configuration-cache runs. The lock task uses Gradle's typed @ServiceReference contract. These focused fixtures do not establish isolation compatibility of all STIG plugins.
Remote task-output caching
Gradle's remote build cache has the same purpose as a Bazel remote cache: reuse matching task outputs across machines. Gradle's HTTP cache contract is not automatically compatible with an existing Bazel endpoint. A service may support both, but its Gradle endpoint, authentication and upload policy must be established first.
No remote endpoint or uploads are enabled. Local caching and CI's existing cache archives work without a server. When a service is available, pilot relocatable JVM tasks with read-only access, then allow trusted CI writers. Do not share native build trees or analysis evidence until their relocation contracts have been verified.
Native Qodana through Gradle
Run ./gradlew qodanaCheck from the STIG source checkout, or the opt-in ./gradlew stigExtendedCheckModel from a consumer. Both run the free JVM Community analyzer directly on the host: no Docker, license, manually installed CLI, or PATH configuration is needed. The consumer task also runs stigCheckModel; it does not change the normal consumer check or build lifecycle. The repository gate remains part of preReleaseCheck, not the per-commit ciBuildAndTest gate.
Gradle is the interface. Internally its Qodana plugin installs a small CLI launcher, and that launcher starts the native analyzer. These have two central pins in gradle/libs.versions.toml:
qodanaselects the Gradle plugin and its embedded exact CLI version. STIG verifies that the upstream installer's version matches this pin, disables nightly selection, and lets that installer verify the CLI checksum.qodanaJvmCommunityselects the native analyzer release.gradle/qodana-analyzer.propertieslocks its immutable Linux, Windows, and macOS downloads for x86-64 and ARM64, with SHA-256 checksums. A normal scan uses only this lock, never the latest-release feed. The same pins and lock are packaged in the published STIG plugin.
The pinned CLI's native --linter mode selects a moving release feed. STIG instead verifies and extracts the locked archive under build/qodana/toolchain/ and passes its local path using --ide ... --within-docker false. Upstream marks --ide deprecated but still supports this local-distribution contract; this CLI has no exact native-version replacement. Its local startup also resolves the IDE relative to its own executable, so Gradle installs and verifies the pinned CLI in that distribution's bin/ directory, replacing the bundled launcher when its checksum differs. An incompatible future CLI must be reviewed, not replaced with an unpinned fallback. Linux and Windows use this pinned installation. macOS execution currently fails explicitly: the pinned upstream launcher fetches custom plugins from a moving release feed even with a local distribution. Its archive URLs remain locked for dependency review, but macOS must not be enabled until that separate plugin download can also be pinned.
Each scan retires the previous SARIF before starting. A successful exit is insufficient: the gate requires fresh, parseable SARIF, successful invocations, and zero unsuppressed findings. An interrupted scan, missing completion evidence, or failed invocation fails the build. qodana.yaml selects inspections and declares the same zero threshold. The consumer's own YAML inspection profile is respected; without one, STIG uses qodana.recommended.
Use scripts/update.sh --patch, --minor, or --major to update Qodana alongside the other tools. The ordinary catalog update handles the Gradle plugin/CLI; :stig-gradle-plugin:updateQodanaPins selects the newest stable native release allowed by that level and the CLI release family, then updates the catalog pin and all distribution checksums together. It fails before writing if release metadata or checksums are missing or invalid. The focused :stig-gradle-plugin:qodanaSupportTest gate covers interrupted scans, stale/malformed evidence, findings, and pin updates.
IntelliJ plugin tests
:stig-idea-plugin:test sets idea.load.plugins.id=ai.exida.stig.idea, so IDEA loads STIG and the dependencies declared in its plugin.xml, including Java and Kotlin. Gradle's IntelliJ test harness shares one classloader across plugins. Loading unrelated bundled plugins into that classloader causes an obfuscated-class collision between IDEA 2026.2.2's Ultimate startup activity and the platform. Keep the test plugin selection in stig-idea-plugin/build.gradle.kts; the pinned full IDEA distribution remains the input to compilation and plugin verification.
intellijIde in gradle/libs.versions.toml is the single IDEA version setting for compilation, platform tests, plugin verification, and the repository formatter. The plugin's minimum since-build is derived from that distribution's exact build number, including patch-level components. checkPluginArchive verifies the packaged descriptor against the same build number. Updating the catalog pin therefore updates the tested platform and the minimum supported IDE together; the locally installed Toolbox IDE does not select either version.
Add a JavaScript / CSS dependency (Dokka vendor)
Three steps:
Declare the npm package in
stig-gradle-plugin/js-vendor/package.jsonwith an exact version:{ "dependencies": { "your-new-lib": "1.2.3" } }Do not use
"*"or"^". Direct npm dependencies are pinned deliberately, like entries ingradle/libs.versions.toml.scripts/update.shis the single entrypoint that moves those pins forward, using the Node version pinned inscripts/toolchain-pins.env.Import it from the bundled viewer source under
stig-gradle-plugin/js-vendor/src/where it is used.esbuildwalkssrc/index.jsfor ordinary page controls andsrc/viewer-index.jsfor diagram viewers. The build writesstig-bundle.js/stig-bundle.cssandstig-viewers.js/stig-viewers.css.stig-viewer-loader.jsloads the viewer pair only on pages that need it. Keep new diagram-only dependencies out ofexpose-page-globals.js, and retain both pairs in Dokka's asset inputs. Source maps remain local-only at publication. Ordinary report gauges register only ECharts' gauge, graphic, and SVG components inexpose-page-globals.js; they must render without loadingstig-viewers.js.Regenerate the lockfile:
cd stig-gradle-plugin/js-vendor ../../scripts/run-pinned-node-tool.sh npm install --no-audit --no-fund --strict-allow-scripts
You do not edit DocsTasks.kt. The plugin ships the generated assets from stig-gradle-plugin/src/main/resources/dokka/assets/.
Headless documentation media
The IntelliJ IDEA tour deliberately separates artifact ownership from dependency locking:
stig-docs/src/main/kotlin/com/stigway/videos/ideaTour/owns the Revideo timeline, reusable scene components, render adapter, and verifier;stig-docs/src/main/kotlin/com/stigway/images/ideaTour/owns sanitized canonical screenshots;stig-gradle-plugin/js-vendor/package.jsonandpackage-lock.jsonremain the repository-wide source of truth for exact Node dependencies, including Revideo, its Vite host, TypeScript, Puppeteer, and Playwright;stig-docs/build/media/contains the generated MP4, poster, and Writerside-only dark-theme aliases and is never checked in. The captures and video are already dark and theme-neutral. Writerside nevertheless requires a dark filename for each one, so the build generates aliases rather than storing duplicate source binaries.
scripts/setup.sh installs pinned Node/npm dependencies, Puppeteer's matching Chrome, Playwright Chromium, and mandatory system FFmpeg/FFprobe on both Fedora and Ubuntu. Revideo uses Puppeteer's browser for video rendering; Playwright uses its own Chromium for viewer tests. Revideo uses its package-locked FFmpeg for muxing because its fluent-ffmpeg 2.1 parser does not understand the additional device column in FFmpeg 8's format table; it uses system ffprobe because strict npm lifecycle policy leaves the bundled probe installer disabled. The Gradle tasks use current system FFmpeg/FFprobe to verify the H.264 output with 8-bit 4:2:0 sampling (yuv420p or yuvj420p) and extract the poster.
Task | Contract |
|---|---|
| Type-check the Revideo source and syntax-check both Node adapters; does not render |
| Render and verify the 1920×1080, 30 fps MP4, extract the poster, and create Writerside theme aliases |
| Copy canonical Writerside input plus generated media into |
The normal :stig-docs:build lifecycle depends on ideaTourMedia. A clean build performs one headless render; later builds are up to date while the source screenshots, Revideo files, lockfile, pinned Node version, and FFmpeg versions remain unchanged. When a build also includes :stig-gradle-plugin:stigViewerRenderTest, video rendering waits for that suite to finish so the two browser jobs do not compete for host resources.
npm install-script policy
All repository-owned npm installation entry points use --strict-allow-scripts. A dependency with preinstall, install, or postinstall code must therefore be reviewed and covered by the root allowScripts object in stig-gradle-plugin/js-vendor/package.json. Approvals use an exact package-version key, for example:
The esbuild approval permits its pinned postinstall to locate or validate the matching locked platform binary. The Puppeteer approval permits its installer to download matching Chrome and Chrome Headless Shell binaries into the user's Puppeteer cache. scripts/update-js-vendor-deps.mjs carries these two package approvals forward with their exact direct pins and preserves decisions for other exact versions, which may still be required transitively. The subsequent strict npm install validates that no additional package has introduced unreviewed lifecycle code. The exact fsevents approval covers Playwright's optional macOS dependency and its node-gyp rebuild install action. npm's strict preflight requires this decision on Linux even though the package itself is Darwin-only. Because fsevents is transitive, dependency updates do not move its approval automatically; a new locked version requires another explicit review.
For any new lifecycle script, inspect the exact package version and script before changing the policy. Record an approved exact version as true, or use npm's explicit denial mechanism when the package is safe and functional without the script. Do not use --dangerously-allow-all-scripts, and do not suppress the warning without recording a reviewed decision in the shared package policy.
After a strict installation fails, node_modules can still contain the previous versions. npm install-scripts approve operates on installed packages, so it cannot approve a new version that has not been installed. In that case, record the reviewed exact package@version decision in allowScripts, then retry the pinned npm install command above to finish updating the lockfile. After installation, use npm install-scripts prune --dry-run to review obsolete policy entries.
Third-party notices
The canonical third-party notice file is generated at the repository root:
The same generated content is also copied to the Writerside topic:
Run the generator whenever dependency inputs move:
This single task refreshes the JVM/Gradle runtime notice set, the npm viewer bundle notice set, and the native analyzer notice set. It is intentionally manual because it updates checked-in files. scripts/update.sh runs it after changing Gradle, Node, and npm pins.
CI and the local release gate both reach the same verifier through releaseCoreVerification:
The verifier regenerates into build/ and compares against the checked-in root and Writerside copies. It must not rewrite the working tree. The checked-in root file is packaged into JVM JARs under META-INF/THIRD_PARTY_NOTICES.md. This includes gradle/stig-java-bootstrap.jar: after changing notices, run :stig-gradle-plugin:syncJavaBootstrap before checking that archive. scripts/update.sh and interactive/path-2/path-3 submissions perform this step after their source and notice maintenance.
Hand-written runtime JS shipped with the plugin (issue 13)
The diagram runtime also has hand-written sources living under stig-gradle-plugin/js-vendor/:
Source path | Vendored as | Purpose |
|---|---|---|
| bundled into | Mounts |
| bundled into | Shared selectable tooltip and right-click menu rendering for Cytoscape, Sigma, and SVG viewers. |
| bundled into | Upgrades executable-model boxes, loads complete referenced-model tables into interactive popups, and manages fold controls. |
| bundled into | Loads Kotlin-generated navigation branches, selects the current page, and restores tree expansion and scroll position. |
| bundled into | Read-only Monaco source browser used by Cytoscape and Sigma viewers. |
STIG selectively imports Web Awesome's wa-card, wa-badge, wa-tooltip, wa-format-date, wa-spinner, wa-details, wa-tree, wa-tree-item, and scoped offline Shoelace theme. Cards and badges provide the analysis quick links, format-date localizes exact build-provenance timestamps, spinner marks loading work, and details folds model boxes. The tree components provide navigation focus, keyboard selection, and folding; native anchors retain normal link behavior. The STIG adapter handles lazy loading and saved state, while Kotlin Dokka extensions own structure, labels, and order. The navigation scope selects the Shoelace palette's blue brand and gray neutral variants. Do not replace the direct component imports with Web Awesome's runtime loader or all-components entry point, and do not use its network-backed default icon library; STIG's generated SVG sprite provides the decorative chevron. The :stig-gradle-plugin:stigGenerateStigIcons task is implemented in Kotlin and extracts the referenced entries from the pinned Iconify JSON packages. The live report sprite currently uses Material Symbols and MDI. The model-icon proposal task compares six separately pinned families: Material Symbols, MDI, Lucide, Tabler, Phosphor, and Carbon. The full Iconify aggregate database is not installed or shipped.
Review discussions use the MIT-licensed @svar-ui/react-comments and @svar-ui/react-core packages with exactly pinned React/React DOM. One isolated React root renders SVAR's flow layout and Willow/WillowDark theme with external font links disabled. The CSS's published font URLs resolve to exactly pinned @fontsource/open-sans and @fontsource/roboto assets, embedded as data URLs by esbuild; an unmapped SVAR font fails the bundle build. Their SIL Open Font License and SVAR's MIT copyright/permission notices are embedded from the installed packages. STIG owns the draft-preserving composer and provider controller; no provider requests or tokens enter the React view. A custom format component uses MIT-licensed markdown-it (raw HTML and images disabled) and the existing DOMPurify dependency. GitHub adapters are separate browser/JVM implementations of the same versioned review record contract, tested with stig-core/src/testFixtures/resources/review/automated-record.json.
Adding a new Iconify icon used by STIG diagrams or report UI:
Pick the icon at https://icon-sets.iconify.design/ and ensure its exact
@iconify-json/<set>package is pinned injs-vendor/package.json.Add
stig-icons-<slug>→<set>:<name>toSTIG_ICONSinstig-engine/.../diagram/GenerateStigIcons.kt.Reference the new sprite id from
StigSpriteIconorStigIcon.Run
./gradlew :stig-gradle-plugin:stigGenerateStigIcons(the dependency update script also runs it automatically).
Cytoscape / interactive-graph stack
The cytoscape npm package and graph plugins are bundled by esbuild into the on-demand stig-viewers.js/stig-viewers.css. The shared overlay helper is also available in the ordinary page bundle:
Package | Bundling |
|---|---|
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
| bundled into |
tippy.js, @popperjs/core, and cytoscape-popper are not used by the diagram viewers. Web Awesome is limited to report controls, navigation and model folding, provenance date formatting, and Cytoscape loading indication; it does not implement graph interaction. Tooltips and right-click menus are hand-authored popover elements positioned by @floating-ui/dom; Cytoscape and Sigma use the same rendering helpers.
Adding another Cytoscape plugin: append it to dependencies in package.json, import/register/expose it in js-vendor/src/expose-globals.js, and register it from stig-cyto-embed.js's registerPlugins() when the embed needs direct defensive registration. Keep the package version exact.
Update all dependencies
In the STIG source repository, scripts/update.sh is the single entrypoint for normal Kotlin/Gradle, IntelliJ IDEA, Qodana, Node, and npm dependency updates:
The maintainer script has five mutually exclusive modes: --patch, --minor, --major, --dependencies, and --verification. Model projects and ordinary applications invoke ./gradlew stigUpdate directly; their update defaults to major, while a model project configured with stigSource=local retains its local devel behavior. Consumer stigUpdate selects a STIG release or local checkout first, then aligns its runtime and repairs dependency locks and checksums; its none mode retains the STIG selection. See installation. For the distinction between shared Java/Gradle/Kotlin pins and application-owned library versions, see application dependency maintenance. Checksum-only maintenance covers STIG and its independent convention build. It retains the previous policy, generates missing SHA-256 entries, rejects changes to existing checksum pins or signing-key trust, and runs strict verification before completing. Generation refreshes Gradle's dependency metadata cache, so an earlier cached lookup cannot hide metadata that a fresh CI build will request. It skips wrapper, catalog, toolchain, grammar, notice, and build updates. A failure restores the accepted verification policy. Review newly recorded artifact checksums before committing. Preparation also writes SHA-256 metadata, allowing the maintenance task to load when a changed wrapper needs new settings/buildscript artifacts. The policy is saved before that invocation, so the later audit still compares against the accepted pins. Failure recovery copies that saved policy directly and does not need a working Gradle classpath.
Verification-only maintenance also covers only STIG and its independent convention build. It refreshes both SHA-256 pins and PGP verification, exports public keys, audits the candidate, asks for trust review, and runs strict resolution. It preserves versions and locks and skips the template, upgrades, tests, and full build. A failed download, rejected audit, declined review, or failed strict resolution restores the accepted policy and retains the candidate for review. Rerunning the same command retries generated missing-key exclusions; no individual key edits are needed for temporary key-server failures. Consumer checksum repair retains its signing-key policy: new STIG publisher trust arrives through the authenticated release verification bundle.
For the version-update modes, the first phase updates Gradle itself in this STIG checkout and its physical sibling ../stig-project public template. Both checkouts must contain complete wrappers pinned to stable Gradle 9 or newer; preflight checks both before changing either. The script resolves its own checkout, so invoking it from another consumer does not change which repositories it updates. Other product consumers retain their own wrapper pins.
Gradle's native version selectors choose the release independently for each checkout: --patch uses its current MAJOR.MINOR line, --minor uses its current MAJOR, and --major uses latest. The wrapper writes an exact stable version, preserving the checkout's bin or all distribution type. When the version changes, the script runs the wrapper task again under the selected runtime so gradlew, gradlew.bat, and gradle-wrapper.jar are regenerated too. Before generating either wrapper, the checksum initializer fetches the official SHA-256 for Gradle's resolved exact distribution and writes distributionSha256Sum. It rejects non-Gradle download URLs and missing or malformed checksums; it never carries the previous release's checksum forward or removes verification to permit an upgrade.
If any template wrapper file changes, the script prints a warning with the template path and a reminder to review and commit those changes separately. This reminder also appears when a later phase fails. The script never creates a commit; committing STIG alone does not include stig-project changes.
Exact pins versus update holds
Exact versions are required for reproducible builds. They are stored in:
gradle/wrapper/gradle-wrapper.propertiesin each checkout for the Gradle runtime;gradle/libs.versions.tomlfor Kotlin, JVM libraries, and Gradle plugins;the inline
pluginManagemententries insettings.gradle.ktsfor settings plugins;stig-gradle-plugin/js-vendor/package.jsonpluspackage-lock.jsonfor npm;scripts/toolchain-pins.envfor Node; andstig-code-analyzer/scripts/pins.envfor the native analyzer toolchain.
Do not delete those exact versions or replace them with floating ranges. The update scripts move them deliberately and leave a reviewable diff.
An update hold is different: it excludes an exact pin from the normal update workflow. Gradle catalog holds are declared in build.gradle.kts inside versionCatalogUpdate { pin { ... } }; npm holds would be explicit exceptions in scripts/update-js-vendor-deps.mjs. Avoid adding holds unless a currently documented upstream incompatibility makes an update impossible.
Before running a full dependency update, remove existing update holds so the newest stable candidates are tested:
If verification fails, first fix STIG for the new dependency API and rerun the release gate. Restore a temporary update hold only when the incompatibility cannot reasonably be fixed in STIG. Document the dependency, held version, failing task, upstream issue or concrete incompatibility, and the condition for removing the hold. Never silently retain an old version merely because the first update attempt failed.
It runs:
Wrapper orchestration within
scripts/update.sh— updates the STIG and siblingstig-projectwrappers as described above./gradlew dependencyUpdates— reports outdated Gradle deps./gradlew versionCatalogUpdate— rewrites versions inlibs.versions.tomlUpdates the
intellijIdecatalog pin from JetBrains' stable IDEA Maven metadata, using the same--patch/--minor/--majorpolicy. The generic catalog updater cannot infer the artifact behind this raw version value. The plugin's minimum compatible build changes with the same pin and is verified against that distribution. When Toolbox is installed locally, the script reports whether its IDEA version matches the resulting Gradle pin; it never modifies Toolbox. It also updates Qodana's native analyzer pin and distribution/checksum lock within the selected CLI release family and the same compatibility level; normal scans do not query that feed. Codex usesgradle/codex-toolchain.properties: the updater adopts the latest stable native release when the selected compatibility level permits it and locks all six distributions. Changed upstream hashes for an already pinned version fail the update. NormalstigInstallSkillruns use only the packaged pins; Maven verification and the STIG guidance fingerprint remain separate. See Codex installation.Bumps the two inline plugin versions in
settings.gradle.kts(which can't live in the catalog)Updates
NODE_VERSIONinscripts/toolchain-pins.envto the newest LTS release allowed by--patch/--minor/--majorscripts/update-js-vendor-deps.mjs— rewrites exact npm pins and matching versioned install-script approvals inpackage.jsonaccording to the same--patch/--minor/--majormode. Stable candidates must be at or below the publisher'sdist-tags.latest; a numerically higher historical release is not an update. An existing pin above that tag fails with a request to review and explicitly correct the release line. The helper skips versions whoseengines.nodeexcludesNODE_VERSIONinscripts/toolchain-pins.env. Its fixture tests run before rewriting the pins.strict
npm installinjs-vendor/— rejects unreviewed lifecycle scripts and regeneratespackage-lock.json. The locked Playwright CLI then installs its required Chromium revision.:stig-gradle-plugin:stigGenerateStigIcons— uses the Kotlin generator to regeneratestig-icons.svgfrom the pinned Iconify JSON packages./gradlew --no-configuration-cache generateThirdPartyNotices— regenerates the checked-in root and Writerside third-party notice files./gradlew build— runs the bundle task and verifies nothing broke
It does not update stig-code-analyzer/scripts/pins.env; LLVM / PhASAR pins are analyzer-toolchain decisions and must be updated with analyzer validation.
The generated CI toolchain image installs the same exact Node.js release from scripts/toolchain-pins.env. Changing that file changes the toolchain image hash, so the same pipeline builds and consumes the new image tag.
The same image hash also includes gradle/libs.versions.toml. Changing intellijIde therefore creates a new image and prewarms the matching IntelliJ Platform distribution before the IDEA-plugin verification job runs.
The Gradle/JDK base image for CI is centralized as STIG_GRADLE_CI_IMAGE in .gitlab-ci.yml and passed into ci/toolchain.Dockerfile as BASE. Changing that value also changes the generated toolchain image hash. update.sh does not currently auto-select a newer Docker image tag; review and change the CI base image deliberately when changing the Java/Gradle toolchain.
scripts/setup.sh installs/selects the pinned Node version, runs npm ci including Puppeteer's approved browser installer, and installs Playwright Chromium through the locked local @playwright/test package.
The consumer plugin-build prerequisites :stig-gradle-plugin:stigDokkaBundleNpmInstall and :stig-gradle-plugin:stigDokkaBundle use the Gradle Node plugin, pinned in gradle/libs.versions.toml. It reads the same NODE_VERSION from scripts/toolchain-pins.env, installs the official distribution under stig-gradle-plugin/.gradle/nodejs/, and launches npm and esbuild directly. The Node setup task verifies the official SHA-256 checksum before unpacking. npm uses the version bundled with that Node distribution and retains --strict-allow-scripts; the standard npmInstall entry point delegates to STIG's existing installation task so there is one installation owner. These prerequisites do not invoke run-pinned-node-tool.sh or node-toolchain.sh. An operating-system file lock under stig-gradle-plugin/.gradle/ protects this workspace across Gradle processes. The lock is acquired before Node setup and held until the build's plugin and media consumers finish, so an IDEA Gradle import cannot reinstall node_modules while a terminal build installs dependencies or bundles the viewer. This prevents the concurrent removal and extraction that can otherwise produce npm ENOTEMPTY and missing-package errors. A contending build logs that it is waiting; it does not retry npm or switch toolchains.
This applies when a consumer selects a sibling STIG checkout, including ordinary stigGenerateHtml, stigPublishHtml, and stigInitElement invocations. The sibling checkout remains authoritative. The first build provisions the Node toolchain and locked dependencies automatically; later builds reuse their declared outputs. A published STIG plugin already contains the bundled report assets and does not build them on the consumer's machine.
Explicit setup, dependency maintenance, viewer verification, and documentation-media tasks still use scripts/run-pinned-node-tool.sh, which shares the installer used by the CI toolchain image.
After running the update
The script verifies with ./gradlew build, but that doesn't catch visual regressions in the Dokka HTML. After an update always:
Run
./gradlew :stig-gradle-plugin:functionalTest— TestKit-level smoke + JSoup-level HTML structure checks.Generate docs for a real project (e.g. via stig-project) and open the HTML.
Visually verify: ECharts gauges render, Sigma / Mermaid graphs draw, SVG zoom & pan works, no console errors in the browser devtools.
If something broke, review the diff:
git diff gradle/libs.versions.toml settings.gradle.kts \ scripts/toolchain-pins.env \ stig-gradle-plugin/js-vendor/package.json \ stig-gradle-plugin/js-vendor/package-lock.json \ stig-gradle-plugin/src/main/resources/dokka/assets/stig-icons.svg \ THIRD_PARTY_NOTICES.md \ stig-docs/src/main/kotlin/com/stigway/docs/about/third-party-notices.mdand pin the problematic entry to its previous version, then re-run.
Commit the dependency files together (catalog + settings + Node pin + npm package pins + lockfile + generated sprite + third-party notices if they changed).