From 903e789f456fe8f1335fb61535094f1ff20c1808 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 30 Sep 2026 21:23:01 +0100 Subject: [PATCH 1/8] Move the CVD Policy to the Security section of the documentation The Coordinated Vulnerability Disclosure Policy now lives in the `documentation` repository, under Documentation > Security, at `/docs/security/cvd-policy/` (SpineEventEngine/documentation#88). - Pin the `documentation` module to the head of that pull request, which brings the Security section. This also raises `validation/docs` to the version `documentation` requires. - Remove the old page and add a static redirect from `/cvd-policy/`. GitHub Pages cannot send HTTP redirects, so the page redirects in the browser. Its URLs are site-relative, so the link check follows them to the local build rather than to the live site. `SECURITY.md` and other sites link to `/cvd-policy/`, so the redirect is to stay. - Point the footer link and the `Policy` field of `security.txt` to the new URL, and fix its `Expires` value: a stray space made the timestamp invalid under RFC 9116. - Let the docs layout honour `has_numbered_headings`, as the privacy layout does, so the moved policy keeps its numbered sections. - Fix the numbered table of contents: the number lacked `position: absolute`, so it took space next to the padding meant to hold it, leaving a wide gap and misaligning titles that wrap. Co-Authored-By: Claude Opus 5.5 --- .../scss/modules/_numbered-headings.scss | 4 + site/content/privacy/cvd-policy.md | 104 ------------------ site/go.mod | 4 +- site/go.sum | 4 + .../components/footer/copyright.html | 2 +- site/layouts/docs/single.html | 8 +- site/static/.well-known/security.txt | 4 +- site/static/cvd-policy/index.html | 26 +++++ 8 files changed, 46 insertions(+), 110 deletions(-) delete mode 100644 site/content/privacy/cvd-policy.md create mode 100644 site/static/cvd-policy/index.html diff --git a/site/assets/scss/modules/_numbered-headings.scss b/site/assets/scss/modules/_numbered-headings.scss index 50af4555..b3dbd781 100644 --- a/site/assets/scss/modules/_numbered-headings.scss +++ b/site/assets/scss/modules/_numbered-headings.scss @@ -133,10 +133,14 @@ #TableOfContents { ul { li { + position: relative; display: flex; + // The number sits in the left padding of the link, so it keeps its place + // when the title wraps. $counter-width: 30px; &:before { + position: absolute; display: block; counter-increment: toc; content: counter(toc) "."; diff --git a/site/content/privacy/cvd-policy.md b/site/content/privacy/cvd-policy.md deleted file mode 100644 index 4a7f6066..00000000 --- a/site/content/privacy/cvd-policy.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: Coordinated Vulnerability Disclosure Policy -lastmod: 2026-09-11 -url: /cvd-policy -header_type: fixed-header -body_class: privacy -has_numbered_headings: true ---- - -# Coordinated Vulnerability Disclosure Policy - -{{< date label="Last updated:" >}} - -## Why we have this Policy - -Security is an integral part of how we develop and maintain our product. - -We welcome responsible disclosure of security risks and product vulnerabilities by our customers, -security researchers, and anyone who may discover a potential vulnerability. - -This Policy explains how to report a security vulnerability affecting Spine Event Engine -(β€œSpine”), how we handle the information you provide, and what you can expect from us in -response. - -## What this Policy covers - -This Policy covers Spine, along with the third-party components we use inside it. - -## How to report - -Our single point of contact for vulnerability reports is {{< cloakemail "cra-request@codematters.ltd" >}}. - -When submitting a report, please indicate the affected version and include the following, where -available: - -- the name of the security risk and any relevant CWE and CVE identifiers; -- a description of the issue and clear steps to reproduce it; -- an illustrative example; and -- your assessment of the impact. - -We appreciate reports submitted in English. - -Please report the vulnerability to us before making any information about it public. - -## What happens next - -We will confirm receipt of your report within 3 business days. - -Within the next 10 business days, we will inform you of the results of our initial assessment. - -After that, we will provide the issue status updates at least once every 30 business days until -the matter is resolved or otherwise closed. - -## Publication - -Once the fix is available, we publish an advisory on our website and reference it in the release -notes. - -We ask you to coordinate the timing of any public disclosure with us. As a general rule, this -means waiting until we have released a fix or until 90 days have passed since your report, -whichever is earlier. - -## Rules we ask you to follow - -When conducting security research and reporting a vulnerability under this Policy, we ask that you: - -- act in good faith, and only to identify and report a vulnerability; -- use your own systems, licences and test data; -- stop testing once you have confirmed the vulnerability, and collect only the information - reasonably necessary to demonstrate it; -- avoid any activity that could harm our systems, our customers, or other people's data; -- keep information about the vulnerability confidential until disclosure has been coordinated - with us. - -## What you can expect from us - -If you follow this Policy, we will keep your report confidential and will not take legal action -against you or report your research to law enforcement. - -## Third-party components - -Spine incorporates third-party components and other dependencies. Where a vulnerability in such a -component affects Spine, we will treat the vulnerability as an issue affecting our product and -address it accordingly. - -When such an issue is reported, we will assess its impact on Spine, coordinate with the relevant -third-party maintainer, publish a security advisory where users are affected, and make available -any fixes or updates provided by the third-party provider. - -Where no fix is available, we will identify appropriate measures to eliminate or mitigate the -risk and inform affected users of any actions they should take in the meantime. - -If we develop a fix for the affected third-party component ourselves, we will, where appropriate, -share or coordinate that fix with the component's maintainer. - -## Reporting to authorities - -Where required by applicable law, we will notify the competent authorities, including the -Portuguese CSIRT (CNCS/CERT.PT) and ENISA, of actively exploited vulnerabilities and severe security -incidents affecting Spine within the time limits prescribed by the Cyber Resilience Act. We will -also inform affected users where required. - -Such notifications describe the vulnerability, not the person who reported it. We do not share -your identity with authorities unless the law obliges us to. diff --git a/site/go.mod b/site/go.mod index 7db4e2a0..d1f7a4e2 100644 --- a/site/go.mod +++ b/site/go.mod @@ -3,9 +3,9 @@ module github.com/SpineEventEngine/SpineEventEngine.github.io go 1.22.0 require ( - github.com/SpineEventEngine/documentation/docs v0.0.0-20260522190949-135339f8eef6 // indirect + github.com/SpineEventEngine/documentation/docs v0.0.0-20260930201256-1d7c3e1021be // indirect github.com/SpineEventEngine/site-commons v0.0.0-20260522171914-2a606d89558f // indirect - github.com/SpineEventEngine/validation/docs v0.0.0-20260522175555-cf09b4c706ea // indirect + github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65 // indirect github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800 // indirect github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 // indirect github.com/twbs/bootstrap v5.3.8+incompatible // indirect diff --git a/site/go.sum b/site/go.sum index 3306cea2..20c5cbab 100644 --- a/site/go.sum +++ b/site/go.sum @@ -2,12 +2,16 @@ github.com/SpineEventEngine/documentation/docs v0.0.0-20260522173421-4174bf3cf80 github.com/SpineEventEngine/documentation/docs v0.0.0-20260522173421-4174bf3cf80a/go.mod h1:0lSB4kPZY9Qk429mepYiraNIEucYDIwiNVhJ3Wn3gKo= github.com/SpineEventEngine/documentation/docs v0.0.0-20260522190949-135339f8eef6 h1:9oJp64ALkcO3RVMSe5Ft0q+WzseZQTFeVQEtyMfpfMM= github.com/SpineEventEngine/documentation/docs v0.0.0-20260522190949-135339f8eef6/go.mod h1:0lSB4kPZY9Qk429mepYiraNIEucYDIwiNVhJ3Wn3gKo= +github.com/SpineEventEngine/documentation/docs v0.0.0-20260930201256-1d7c3e1021be h1:BAzioFLPEdwwLNZmkcPR1ab3kzIF6xUI3LJmD//Hcno= +github.com/SpineEventEngine/documentation/docs v0.0.0-20260930201256-1d7c3e1021be/go.mod h1:fEPcJFgW19NDIdb/zGEXVPcUNVMGEBkqfgAZI0VMbVk= github.com/SpineEventEngine/site-commons v0.0.0-20260522171914-2a606d89558f h1:yF43XTDNj+w1S0fLA/XhrWMrhAoDpqPB7gmc4mUF/9Q= github.com/SpineEventEngine/site-commons v0.0.0-20260522171914-2a606d89558f/go.mod h1:tkAl4StIREKmz9r5PiJtuDhvwMMkFXKWcaTyxhIikho= github.com/SpineEventEngine/validation/docs v0.0.0-20260205202311-181ba8844107 h1:2DHRIwwLudP6l3Kh7Nd4KR4uXcsDtIAXE9adW68ivME= github.com/SpineEventEngine/validation/docs v0.0.0-20260205202311-181ba8844107/go.mod h1:STHyjXejVvPmfrxujfDvhofmjg55mMk+fwI3TVL0b4Y= github.com/SpineEventEngine/validation/docs v0.0.0-20260522175555-cf09b4c706ea h1:YEMSlr5KJXkZdK7epMSnN+6HSr2K41n8O3c8OhtF8pM= github.com/SpineEventEngine/validation/docs v0.0.0-20260522175555-cf09b4c706ea/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= +github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65 h1:gLlFsu7ZznIurecioKUHTP+sKD4EapeKFhtEqL/JvdU= +github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800 h1:j5myhhzYwIHTr5ctK96Elfgp5uRROvrlTzYwwe1nF8o= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800/go.mod h1:P5GGyhdxi00C5zW7vkRo/IS532gZY/YS2TS395Xaxho= github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 h1:GZxx4Hc+yb0/t3/rau1j8XlAxLE4CyXns2fqQbyqWfs= diff --git a/site/layouts/_partials/components/footer/copyright.html b/site/layouts/_partials/components/footer/copyright.html index d625f73d..dae5e4c5 100644 --- a/site/layouts/_partials/components/footer/copyright.html +++ b/site/layouts/_partials/components/footer/copyright.html @@ -30,7 +30,7 @@ © 2015 – {{ $currentYear }} diff --git a/site/layouts/docs/single.html b/site/layouts/docs/single.html index 34d5982c..c65b50dd 100644 --- a/site/layouts/docs/single.html +++ b/site/layouts/docs/single.html @@ -1,6 +1,12 @@ {{ define "main" }} {{ partial "components/navbar/navbar.html" . }} {{ partial "components/nav-hero.html" . }} - {{ partial "theme/docs/layout/three-column.html" . }} + {{ if .Params.has_numbered_headings }} +
+ {{ partial "theme/docs/layout/three-column.html" . }} +
+ {{ else }} + {{ partial "theme/docs/layout/three-column.html" . }} + {{ end }} {{ partial "components/go-top-button.html" . }} {{ end }} diff --git a/site/static/.well-known/security.txt b/site/static/.well-known/security.txt index 715be654..25a20f3b 100644 --- a/site/static/.well-known/security.txt +++ b/site/static/.well-known/security.txt @@ -1,5 +1,5 @@ Contact: mailto:cra-request@codematters.ltd -Expires: 2027-09-10 T13:05:00.000Z +Expires: 2027-09-10T13:05:00.000Z Preferred-Languages: en Canonical: https://spine.io/.well-known/security.txt -Policy: https://spine.io/cvd-policy/ +Policy: https://spine.io/docs/security/cvd-policy/ diff --git a/site/static/cvd-policy/index.html b/site/static/cvd-policy/index.html new file mode 100644 index 00000000..0437fb2a --- /dev/null +++ b/site/static/cvd-policy/index.html @@ -0,0 +1,26 @@ + + + + + + Coordinated Vulnerability Disclosure Policy + + + + + +

+ This page has moved. If you are not redirected, open the + Coordinated Vulnerability Disclosure Policy. +

+ + From 34673e2fe6363acd1e49f2042d6ba214e62930a7 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 30 Sep 2026 21:23:01 +0100 Subject: [PATCH 2/8] Keep the fetched security policy fresh The Security policy page fetches `SECURITY.md` from `SpineEventEngine/.github` when the site is built. - Expire fetched files after an hour. Hugo never expires them by default, and the `check-links` workflow restores the Hugo cache between runs, so it would keep serving the first copy. - Allow running the deploy by hand, from `master` only, to publish a change to `SECURITY.md` without a commit here. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/gh-pages.yml | 5 +++++ site/config/_default/hugo.toml | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 469ca37f..96196126 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -5,12 +5,17 @@ on: branches: - master - staging + # Rebuilds the site on demand, e.g. to publish a change to `SECURITY.md` in + # `SpineEventEngine/.github`: the Security policy page fetches that file at build time. + workflow_dispatch: env: HUGO_VERSION: 0.161.1 jobs: deploy: + # A manual run deploys only when started on `master`; on other branches, the job is skipped. + if: github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/master' runs-on: ubuntu-latest permissions: contents: write diff --git a/site/config/_default/hugo.toml b/site/config/_default/hugo.toml index c15de947..f7c7dead 100644 --- a/site/config/_default/hugo.toml +++ b/site/config/_default/hugo.toml @@ -44,6 +44,13 @@ disableKinds = ['taxonomy', 'term'] [security.node.permissions] disable = true +# Remote files that the build fetches, such as `SECURITY.md` for the Security policy page, +# stay cached for an hour. Hugo never expires them by default, so a cache that CI restores +# between runs would keep serving the first copy. +[caches] + [caches.getresource] + maxAge = '1h' + # Enables HTML rendering in markdown files. [markup] [markup.goldmark] From d0b7cbfae55c2911d7a82a8a0f8efe5b11218d97 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 30 Sep 2026 21:43:51 +0100 Subject: [PATCH 3/8] Never reuse a fetched `SECURITY.md` in a deploy A deploy, manual ones included, must publish the current `SECURITY.md`. With the one-hour expiry, a deploy that restored a Hugo cache would publish a copy up to an hour old. The deploy workflow doesn't restore the Hugo cache today, since Hugo doesn't use the directory it caches, but that is an accident of paths rather than a guarantee. Production builds now keep no fetched files at all. Development builds, including the link check, keep them for an hour. Co-Authored-By: Claude Opus 5.5 --- site/config/_default/hugo.toml | 3 ++- site/config/production/hugo.toml | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 site/config/production/hugo.toml diff --git a/site/config/_default/hugo.toml b/site/config/_default/hugo.toml index f7c7dead..0bbc120e 100644 --- a/site/config/_default/hugo.toml +++ b/site/config/_default/hugo.toml @@ -46,7 +46,8 @@ disableKinds = ['taxonomy', 'term'] # Remote files that the build fetches, such as `SECURITY.md` for the Security policy page, # stay cached for an hour. Hugo never expires them by default, so a cache that CI restores -# between runs would keep serving the first copy. +# between runs would keep serving the first copy. Deploys don't reuse them at all: +# see `config/production/hugo.toml`. [caches] [caches.getresource] maxAge = '1h' diff --git a/site/config/production/hugo.toml b/site/config/production/hugo.toml new file mode 100644 index 00000000..5cf31339 --- /dev/null +++ b/site/config/production/hugo.toml @@ -0,0 +1,5 @@ +# A deploy publishes the current `SECURITY.md` for the Security policy page, so it never reuses +# a fetched file, whatever the state of the Hugo cache. Other builds keep them for an hour. +[caches] + [caches.getresource] + maxAge = 0 From 90a23e9cc20f7bb5e187ffd215de01b9193f647c Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 30 Sep 2026 21:47:34 +0100 Subject: [PATCH 4/8] Update `config` --- .agents/_TOC.md | 23 -- .agents/advanced-safety-rules.md | 6 - .agents/coding-guidelines.md | 39 -- .agents/common-tasks.md | 6 - .agents/documentation-guidelines.md | 14 - .agents/documentation-tasks.md | 20 - .agents/guidelines | 1 + .agents/jvm-project.md | 37 -- .agents/project-structure-expectations.md | 21 - .agents/project.md | 97 +---- .agents/project.template.md | 18 - .agents/quick-reference-card.md | 7 - .agents/refactoring-guidelines.md | 3 - .agents/running-builds.md | 18 - .agents/safety-rules.md | 49 --- .agents/scripts | 1 + .agents/scripts/api-discovery/.gitignore | 3 - .agents/scripts/api-discovery/README.md | 158 ------- .agents/scripts/api-discovery/clean-cache | 143 ------- .agents/scripts/api-discovery/discover | 153 ------- .agents/scripts/api-discovery/extract-sources | 118 ------ .agents/scripts/api-discovery/lib/common.sh | 364 ---------------- .agents/scripts/api-discovery/update-sibling | 159 ------- .agents/scripts/pre-pr-gate.sh | 75 ---- .agents/scripts/protect-version-file.sh | 48 --- .agents/scripts/publish-version-gate.sh | 95 ----- .agents/scripts/sanitize-source-code.sh | 49 --- .agents/scripts/update-copyright.sh | 48 --- .agents/shared | 1 + .agents/skills | 1 + .agents/skills/api-discovery/SKILL.md | 288 ------------- .agents/skills/bump-gradle/SKILL.md | 148 ------- .agents/skills/bump-gradle/agents/openai.yaml | 4 - .agents/skills/bump-version/SKILL.md | 135 ------ .../skills/bump-version/agents/openai.yaml | 4 - .agents/skills/check-links/SKILL.md | 325 --------------- .agents/skills/dependency-audit/SKILL.md | 146 ------- .agents/skills/dependency-update/SKILL.md | 283 ------------- .../dependency-update/agents/openai.yaml | 4 - .agents/skills/java-to-kotlin/SKILL.md | 59 --- .../skills/java-to-kotlin/agents/openai.yaml | 4 - .agents/skills/kotlin-review/SKILL.md | 62 --- .agents/skills/move-files/SKILL.md | 57 --- .agents/skills/move-files/agents/openai.yaml | 4 - .agents/skills/pre-pr/SKILL.md | 183 -------- .agents/skills/review-docs/SKILL.md | 129 ------ .agents/skills/update-copyright/SKILL.md | 16 - .../update-copyright/agents/openai.yaml | 4 - .../scripts/update_copyright.py | 389 ------------------ .../tests/test_update_copyright.py | 130 ------ .agents/skills/version-bumped/SKILL.md | 99 ----- .../version-bumped/scripts/version-bumped.sh | 276 ------------- .agents/skills/writer/SKILL.md | 85 ---- .agents/skills/writer/agents/openai.yaml | 5 - .../writer/assets/templates/doc-page.md | 23 -- .../writer/assets/templates/kdoc-example.md | 11 - .../assets/templates/kotlin-java-example.md | 13 - .agents/testing.md | 8 - .agents/version-policy.md | 19 - .agents/widow-runt-orphan.jpg | Bin 54071 -> 0 bytes .claude/agents | 1 + .claude/agents/dependency-audit.md | 19 - .claude/agents/kotlin-review.md | 17 - .claude/agents/review-docs.md | 18 - .claude/commands | 1 + .claude/commands/bump-gradle.md | 13 - .claude/commands/bump-version.md | 13 - .claude/commands/dependency-update.md | 16 - .claude/commands/java-to-kotlin.md | 13 - .claude/commands/move-files.md | 12 - .claude/commands/pre-pr.md | 32 -- .claude/commands/review-docs.md | 21 - .claude/commands/run-build.md | 12 - .claude/commands/update-copyright.md | 12 - .claude/commands/write-docs.md | 14 - .claude/settings.json | 30 +- .github/copilot-instructions.md | 23 +- .github/workflows/check-links.yml | 15 +- .../workflows/gradle-wrapper-validation.yml | 19 - .github/workflows/secret-scan.yml | 70 ++++ .gitignore | 170 ++++++-- .gitmodules | 6 + .idea/copyright/CodeMatters_Open_Source.xml | 6 + .idea/live-templates/README.md | 4 +- .idea/misc.xml | 44 -- .junie/guidelines.md | 2 +- AGENTS.md | 70 +++- config | 2 +- docs/project.md | 96 +++++ init-submodules | 99 +++++ lychee.toml | 67 ++- 91 files changed, 567 insertions(+), 5058 deletions(-) delete mode 100644 .agents/_TOC.md delete mode 100644 .agents/advanced-safety-rules.md delete mode 100644 .agents/coding-guidelines.md delete mode 100644 .agents/common-tasks.md delete mode 100644 .agents/documentation-guidelines.md delete mode 100644 .agents/documentation-tasks.md create mode 120000 .agents/guidelines delete mode 100644 .agents/jvm-project.md delete mode 100644 .agents/project-structure-expectations.md mode change 100644 => 120000 .agents/project.md delete mode 100644 .agents/project.template.md delete mode 100644 .agents/quick-reference-card.md delete mode 100644 .agents/refactoring-guidelines.md delete mode 100644 .agents/running-builds.md delete mode 100644 .agents/safety-rules.md create mode 120000 .agents/scripts delete mode 100644 .agents/scripts/api-discovery/.gitignore delete mode 100644 .agents/scripts/api-discovery/README.md delete mode 100755 .agents/scripts/api-discovery/clean-cache delete mode 100755 .agents/scripts/api-discovery/discover delete mode 100755 .agents/scripts/api-discovery/extract-sources delete mode 100644 .agents/scripts/api-discovery/lib/common.sh delete mode 100755 .agents/scripts/api-discovery/update-sibling delete mode 100755 .agents/scripts/pre-pr-gate.sh delete mode 100755 .agents/scripts/protect-version-file.sh delete mode 100755 .agents/scripts/publish-version-gate.sh delete mode 100755 .agents/scripts/sanitize-source-code.sh delete mode 100755 .agents/scripts/update-copyright.sh create mode 160000 .agents/shared create mode 120000 .agents/skills delete mode 100644 .agents/skills/api-discovery/SKILL.md delete mode 100644 .agents/skills/bump-gradle/SKILL.md delete mode 100644 .agents/skills/bump-gradle/agents/openai.yaml delete mode 100644 .agents/skills/bump-version/SKILL.md delete mode 100644 .agents/skills/bump-version/agents/openai.yaml delete mode 100644 .agents/skills/check-links/SKILL.md delete mode 100644 .agents/skills/dependency-audit/SKILL.md delete mode 100644 .agents/skills/dependency-update/SKILL.md delete mode 100644 .agents/skills/dependency-update/agents/openai.yaml delete mode 100644 .agents/skills/java-to-kotlin/SKILL.md delete mode 100644 .agents/skills/java-to-kotlin/agents/openai.yaml delete mode 100644 .agents/skills/kotlin-review/SKILL.md delete mode 100644 .agents/skills/move-files/SKILL.md delete mode 100644 .agents/skills/move-files/agents/openai.yaml delete mode 100644 .agents/skills/pre-pr/SKILL.md delete mode 100644 .agents/skills/review-docs/SKILL.md delete mode 100644 .agents/skills/update-copyright/SKILL.md delete mode 100644 .agents/skills/update-copyright/agents/openai.yaml delete mode 100755 .agents/skills/update-copyright/scripts/update_copyright.py delete mode 100644 .agents/skills/update-copyright/tests/test_update_copyright.py delete mode 100644 .agents/skills/version-bumped/SKILL.md delete mode 100755 .agents/skills/version-bumped/scripts/version-bumped.sh delete mode 100644 .agents/skills/writer/SKILL.md delete mode 100644 .agents/skills/writer/agents/openai.yaml delete mode 100644 .agents/skills/writer/assets/templates/doc-page.md delete mode 100644 .agents/skills/writer/assets/templates/kdoc-example.md delete mode 100644 .agents/skills/writer/assets/templates/kotlin-java-example.md delete mode 100644 .agents/testing.md delete mode 100644 .agents/version-policy.md delete mode 100644 .agents/widow-runt-orphan.jpg create mode 120000 .claude/agents delete mode 100644 .claude/agents/dependency-audit.md delete mode 100644 .claude/agents/kotlin-review.md delete mode 100644 .claude/agents/review-docs.md create mode 120000 .claude/commands delete mode 100644 .claude/commands/bump-gradle.md delete mode 100644 .claude/commands/bump-version.md delete mode 100644 .claude/commands/dependency-update.md delete mode 100644 .claude/commands/java-to-kotlin.md delete mode 100644 .claude/commands/move-files.md delete mode 100644 .claude/commands/pre-pr.md delete mode 100644 .claude/commands/review-docs.md delete mode 100644 .claude/commands/run-build.md delete mode 100644 .claude/commands/update-copyright.md delete mode 100644 .claude/commands/write-docs.md delete mode 100644 .github/workflows/gradle-wrapper-validation.yml create mode 100644 .github/workflows/secret-scan.yml create mode 100644 .idea/copyright/CodeMatters_Open_Source.xml delete mode 100644 .idea/misc.xml create mode 100644 docs/project.md create mode 100755 init-submodules diff --git a/.agents/_TOC.md b/.agents/_TOC.md deleted file mode 100644 index 4be0656b..00000000 --- a/.agents/_TOC.md +++ /dev/null @@ -1,23 +0,0 @@ -# Table of Contents - -1. [Quick Reference Card](quick-reference-card.md) -2. [JVM project requirements](jvm-project.md) β€” language, build, and review checklist shared by all JVM repos -3. [Coding guidelines](coding-guidelines.md) -4. [Documentation & comments](documentation-guidelines.md) -5. [Documentation tasks](documentation-tasks.md) -6. [Running builds](running-builds.md) -7. [Version policy](version-policy.md) -8. [Project structure expectations](project-structure-expectations.md) -9. [Testing](testing.md) -10. [Safety rules](safety-rules.md) -11. [Advanced safety rules](advanced-safety-rules.md) -12. [Refactoring guidelines](refactoring-guidelines.md) -13. [Common tasks](common-tasks.md) -14. [Team memory](memory/MEMORY.md) -15. [Task plans](tasks/README.md) -16. [Java to Kotlin conversion](skills/java-to-kotlin/SKILL.md) -17. [Dependency update](skills/dependency-update/SKILL.md) -18. [Documentation review](skills/review-docs/SKILL.md) -19. [Pre-PR checklist](skills/pre-pr/SKILL.md) -20. [Kotlin code review](skills/kotlin-review/SKILL.md) -21. [Dependency audit](skills/dependency-audit/SKILL.md) diff --git a/.agents/advanced-safety-rules.md b/.agents/advanced-safety-rules.md deleted file mode 100644 index e4105813..00000000 --- a/.agents/advanced-safety-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# 🚨 Advanced safety rules - -- Do **not** auto-update external dependencies without explicit request. -- Do **not** inject analytics or telemetry code. -- Flag any usage of unsafe constructs (e.g., reflection, I/O on the main thread). -- Avoid generating blocking calls inside coroutines. diff --git a/.agents/coding-guidelines.md b/.agents/coding-guidelines.md deleted file mode 100644 index 12ede97c..00000000 --- a/.agents/coding-guidelines.md +++ /dev/null @@ -1,39 +0,0 @@ -# 🧾 Coding guidelines - -## Core principles - -- Adhere to [Spine Event Engine Documentation][spine-docs] for coding style. -- Generate code that compiles cleanly and passes static analysis. -- Respect existing architecture, naming conventions, and project structure. -- Write clear, incremental commits with descriptive messages. -- Include automated tests for any code change that alters functionality. - -## Kotlin best practices - -### βœ… Prefer -- **Kotlin idioms** over Java-style approaches: - - Extension functions - - `when` expressions - - Smart casts - - Data classes and sealed classes - - Immutable data structures -- **Simple nouns** over composite nouns (`user` > `userAccount`) -- **Generic parameters** over explicit variable types (`val list = mutableList()`) -- **Java interop annotations** only when needed (`@file:JvmName`, `@JvmStatic`) -- **Kotlin DSL** for Gradle files - -### ❌ Avoid -- Mutable data structures -- Java-style verbosity (builders with setters) -- Redundant null checks (`?.let` misuse) -- Using `!!` unless clearly justified -- Type names in variable names (`userObject`, `itemList`) -- String duplication (use constants in companion objects) -- Mixing Groovy and Kotlin DSLs in build logic -- Reflection unless specifically requested - -## Text formatting - - βœ… Replace double empty lines with a single empty line in the code. - - βœ… Remove trailing space characters in the code. - -[spine-docs]: https://github.com/SpineEventEngine/documentation/wiki diff --git a/.agents/common-tasks.md b/.agents/common-tasks.md deleted file mode 100644 index 5ee954d8..00000000 --- a/.agents/common-tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# πŸ“‹ Common tasks - -- **Adding a new dependency**: Update relevant files in `buildSrc` directory. -- **Creating a new module**: Follow existing module structure patterns. -- **Documentation**: Use KDoc style for public and internal APIs. -- **Testing**: Create comprehensive tests using Kotest assertions. diff --git a/.agents/documentation-guidelines.md b/.agents/documentation-guidelines.md deleted file mode 100644 index 6c9c1bae..00000000 --- a/.agents/documentation-guidelines.md +++ /dev/null @@ -1,14 +0,0 @@ -# Documentation & comments - -## Commenting guidelines -- Avoid inline comments in production code unless necessary. -- Inline comments are helpful in tests. -- When using TODO comments, follow the format on the [dedicated page][todo-comments]. -- File and directory names should be formatted as code. - -## Avoid widows, runts, orphans, or rivers - -Agents should **AVOID** text flow patters illustrated -on [this diagram](widow-runt-orphan.jpg). - -[todo-comments]: https://github.com/SpineEventEngine/documentation/wiki/TODO-comments diff --git a/.agents/documentation-tasks.md b/.agents/documentation-tasks.md deleted file mode 100644 index 8ac4660d..00000000 --- a/.agents/documentation-tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# πŸ“„ Documentation tasks - -1. Ensure all public and internal APIs have KDoc examples. -2. Add in-line code blocks for clarity in tests. -3. Convert inline API comments in Java to KDoc in Kotlin: - ```java - // Literal string to be inlined whenever a placeholder references a non-existent argument. - private final String missingArgumentMessage = "[MISSING ARGUMENT]"; - ``` - transforms to: - ```kotlin - /** - * Literal string to be inlined whenever a placeholder references a non-existent argument. - */ - private val missingArgumentMessage = "[MISSING ARGUMENT]" - ``` - -4. Javadoc -> KDoc conversion tasks: - - Remove `

` tags in the line with text: `"

This"` -> `"This"`. - - Replace `

` with empty line if the tag is the only text in the line. diff --git a/.agents/guidelines b/.agents/guidelines new file mode 120000 index 00000000..6f9d9663 --- /dev/null +++ b/.agents/guidelines @@ -0,0 +1 @@ +shared/guidelines \ No newline at end of file diff --git a/.agents/jvm-project.md b/.agents/jvm-project.md deleted file mode 100644 index e3c5d650..00000000 --- a/.agents/jvm-project.md +++ /dev/null @@ -1,37 +0,0 @@ -# JVM Project Requirements - -General requirements for all JVM projects in the Spine SDK organisation. -Repo-specific `project.md` files link here and add their own context. - -## Language and build - -- **Languages**: Kotlin (primary), Java (secondary). -- **Build**: Gradle with Kotlin DSL. -- **Static analysis**: detekt, ErrorProne, Checkstyle, PMD. -- **Testing**: JUnit 5, Kotest Assertions, Codecov. - -## Code review checklist - -**Correctness and safety** -- Code compiles and passes static analysis (detekt, ErrorProne, Checkstyle, PMD). -- No reflection or unsafe code unless explicitly approved in scope. -- No analytics, telemetry, or tracking code. -- No blocking calls inside coroutines. - -**Kotlin/Java style** -- Kotlin idioms preferred: extension functions, `when` expressions, data/sealed - classes, immutable data structures. -- No `!!` unless provably safe. No unchecked casts. -- No mutable state without justification. -- No string duplication β€” use constants. - -**Tests** -- New or changed functionality must include tests. -- Use stubs, not mocks. -- Prefer [Kotest assertions][kotest-assertions] over JUnit or Google Truth. - -**Versioning** -- If the repo has `version.gradle.kts`, every PR must include a version bump. - Flag the absence as a required change. - -[kotest-assertions]: https://kotest.io/docs/assertions/assertions.html diff --git a/.agents/project-structure-expectations.md b/.agents/project-structure-expectations.md deleted file mode 100644 index 22a3ab7d..00000000 --- a/.agents/project-structure-expectations.md +++ /dev/null @@ -1,21 +0,0 @@ -# πŸ“ Project structure expectations - -```yaml -.github -buildSrc/ - - src/ - β”œβ”€β”€ main/ - β”‚ β”œβ”€β”€ kotlin/ # Kotlin source files - β”‚ └── java/ # Legacy Java code - β”œβ”€β”€ test/ - β”‚ └── kotlin/ # Unit and integration tests - build.gradle.kts # Kotlin-based build configuration - - -build.gradle.kts # Kotlin-based build configuration -settings.gradle.kts # Project structure and settings -README.md # Project overview -AGENTS.md # Entry point for LLM agent instructions -version.gradle.kts # Declares the project version in versioned Gradle Build Tools repos. -``` diff --git a/.agents/project.md b/.agents/project.md deleted file mode 100644 index 8cf191de..00000000 --- a/.agents/project.md +++ /dev/null @@ -1,96 +0,0 @@ -# Project: spine.io - -## Overview - -This repository is the source of the public [spine.io](https://spine.io) website β€” -the umbrella surface for the Spine SDK organisation. It serves a dual role: -a **documentation portal** (pulling the canonical content from the -[`documentation`][documentation] repository as a Hugo Module) and a -**marketing site** with landing pages, examples, and a `getting-help` page that -sells products through a Paygate-backed checkout. The site is built with -[Hugo Extended][hugo] and hosted on GitHub Pages. - -## Architecture - -**Role in the org:** application (a static website). Not a library, tool, or -Gradle plugin. The Gradle build present in the repo is a **convenience -wrapper only** β€” no JVM compilation happens here. - -**Composition** - -- `site/` β€” the Hugo project (config, layouts, assets, content shell). -- `config/` β€” the only git submodule, pointing at - [`SpineEventEngine/config`][config] (shared Spine build conventions). -- `build.gradle.kts` β€” exposes shell-backed tasks: - `:runSite`, `:buildSite`, and `:checkLinks`. - -**External pieces (consumed, not vendored)** - -- [`documentation`][documentation] β€” pulled in as a Hugo Module; provides the - documentation content. Code snippets shown on the rendered site are - embedded *there* (via `embed-code`) before being pulled in as a module. -- [`site-commons`][site-commons] β€” Hugo theme module shared across Spine - public sites (anchor icons, snackbars, etc.). - -**CI / deployment** (`.github/workflows/`) - -- `gh-pages.yml` β€” builds the Hugo site and deploys to GitHub Pages on push - to the default branch. This is the deploy path; treat changes that affect - it with care. -- `check-links.yml` β€” validates rendered-site links (mirrors the local - `checkLinks` task and the `check-links` skill). -- `gradle-wrapper-validation.yml` β€” guards the Gradle wrapper checksum on - PRs that touch the wrapper. - -## Constraints & guardrails - -- **Hugo Extended version is pinned.** Use `v0.161.1` or higher of the - *Extended* build. A mismatched/non-extended Hugo will break the theme - pipeline and SCSS. -- **The `config/` submodule is pinned to a specific commit.** Do not bump - it outside a dedicated update task β€” drive-by pointer changes silently - alter shared Spine build conventions. -- **Documentation content lives in the [`documentation`][documentation] - repo, not here.** Edits to documentation pages must be made there (or in - the relevant doc module). This repo only owns the site shell, landing - pages, and Spine-SDK-wide marketing surfaces. - -## Local development - -Two equivalent ways to run the site locally: - -1. Via Gradle (preferred β€” handles working directory and Hugo flags): - ```shell - ./gradlew :runSite # build + serve on http://localhost:1313 - ./gradlew :buildSite # build only, output under site/public - ./gradlew :checkLinks # validate links against the built site - ``` -2. Directly with Hugo, from inside `site/`: - ```shell - hugo server - ``` - -If Hugo Module fetches misbehave (stale cache, theme not updating), clear -the module cache and retry: - -```shell -hugo mod clean --all -``` - -The `getting-help` page integrates with **Paygate**. In dev it points at the -staging Paygate server; test the checkout flow with -[LHV sandbox cards](https://merchant.lhv.ee/help/en/articles/12807566-test-cards). -Changes touching that page have a real third-party integration surface β€” -verify the flow end-to-end, not just the rendered markup. - -## Authoring - -- Content authoring conventions: [`AUTHORING.md`](../AUTHORING.md). -- Embedding code samples into pages happens in the - [`documentation`][documentation] repo, not here β€” see its - [`EMBEDDING.md`](https://github.com/SpineEventEngine/documentation/blob/master/EMBEDDING.md). - -[hugo]: https://gohugo.io/getting-started/quick-start/#step-1-install-hugo -[documentation]: https://github.com/SpineEventEngine/documentation -[site-commons]: https://github.com/SpineEventEngine/site-commons -[config]: https://github.com/SpineEventEngine/config diff --git a/.agents/project.md b/.agents/project.md new file mode 120000 index 00000000..7e0bf9bd --- /dev/null +++ b/.agents/project.md @@ -0,0 +1 @@ +../docs/project.md \ No newline at end of file diff --git a/.agents/project.template.md b/.agents/project.template.md deleted file mode 100644 index b6882e03..00000000 --- a/.agents/project.template.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# Project: - -## Overview - -*One paragraph: what this repo is, what problem it solves, and its role in the -Spine SDK organisation.* - -## Architecture - -*Role in the org: library / tool / Gradle plugin / application. -Key patterns, public API boundaries, and constraints specific to this repo.* - - diff --git a/.agents/quick-reference-card.md b/.agents/quick-reference-card.md deleted file mode 100644 index 2e890e42..00000000 --- a/.agents/quick-reference-card.md +++ /dev/null @@ -1,7 +0,0 @@ -# πŸ“ Quick Reference Card - -🚫 **Do not write to git history** (commit/push/tag/rebase/merge/cherry-pick/reset/`gh pr merge`) without explicit authorization. See -[`safety-rules.md`](safety-rules.md) β†’ *Commits and history-writing*. -Authorization comes only from a skill's `## Commit authorization` -section or from the user's current prompt β€” never from prior turns or -memory. diff --git a/.agents/refactoring-guidelines.md b/.agents/refactoring-guidelines.md deleted file mode 100644 index 191db49f..00000000 --- a/.agents/refactoring-guidelines.md +++ /dev/null @@ -1,3 +0,0 @@ -# βš™οΈ Refactoring guidelines - -- Do NOT replace Kotest assertions with standard Kotlin's built-in test assertions. diff --git a/.agents/running-builds.md b/.agents/running-builds.md deleted file mode 100644 index db0338d6..00000000 --- a/.agents/running-builds.md +++ /dev/null @@ -1,18 +0,0 @@ -# Running builds - -1. When modifying code, run: - ```bash - ./gradlew build - ``` - -2. If Protobuf (`.proto`) files are modified run: - ```bash - ./gradlew clean build - ``` - -3. Documentation-only changes in Kotlin or Java sources run: - ```bash - ./gradlew dokka - ``` - -4. Documentation-only changes do not require running tests! diff --git a/.agents/safety-rules.md b/.agents/safety-rules.md deleted file mode 100644 index e7fece3c..00000000 --- a/.agents/safety-rules.md +++ /dev/null @@ -1,49 +0,0 @@ -# Safety rules - -- βœ… All code must compile and pass static analysis. -- βœ… Do not auto-update external dependencies. -- ❌ Never use reflection or unsafe code without an explicit approval. -- ❌ No analytics or telemetry code. -- ❌ No blocking calls inside coroutines. - -## Commits and history-writing - -**Default: do not write to git history.** This is a hard rule for every -agent β€” the main thread, every subagent, every skill. It overrides any -local convenience or "the change looks done" instinct. - -The rule covers all of these operations: - -- `git commit`, `git commit-tree` -- `git push`, `git push --force` -- `git tag` -- `git rebase`, `git merge`, `git cherry-pick` against shared history -- `git reset` that discards committed work -- `gh release create`, `gh pr merge` - -Authorization to perform one of these operations exists only when **one** -of the following is true *right now*: - -1. **Skill-declared.** The currently active skill's `SKILL.md` contains - a `## Commit authorization` section that explicitly authorizes the - operation and constrains it (which files may be staged, the exact - commit subject, the maximum number of commits). The mere mention of - a commit message inside skill prose is **not** authorization β€” the - section heading must be present. -2. **User-instructed.** The user's *current* prompt explicitly tells - the agent to perform the operation. Examples that qualify: - "commit this", "make a commit with subject X", "push the branch", - "tag this release". Authorization from previous turns, from - `CLAUDE.md`, or from any memory file does **not** carry over. - -If neither holds, the agent: - -1. Stages relevant changes with `git add` (only if helpful for review). -2. Prints the proposed commit subject (if any) and `git diff --staged`. -3. **Stops.** The user runs the commit themselves, or replies with - explicit authorization in the next prompt. - -The project's `.claude/settings.json` keeps `Bash(git commit:*)` in -`permissions.ask` as defense-in-depth, but the primary enforcement is -this rule β€” agents must not propose commit attempts that rely on the -user clicking the prompt. diff --git a/.agents/scripts b/.agents/scripts new file mode 120000 index 00000000..96bf06e1 --- /dev/null +++ b/.agents/scripts @@ -0,0 +1 @@ +shared/scripts \ No newline at end of file diff --git a/.agents/scripts/api-discovery/.gitignore b/.agents/scripts/api-discovery/.gitignore deleted file mode 100644 index c824ff1d..00000000 --- a/.agents/scripts/api-discovery/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Per-developer override of for the api-discovery -# extraction cache. Contains an absolute path; do not commit. -.workspace-root diff --git a/.agents/scripts/api-discovery/README.md b/.agents/scripts/api-discovery/README.md deleted file mode 100644 index de4c631a..00000000 --- a/.agents/scripts/api-discovery/README.md +++ /dev/null @@ -1,158 +0,0 @@ -# `api-discovery` scripts - -Resolve the on-disk location of a Maven artifact's source code for -agents and developers, without repeatedly `unzip`-ing JARs out of the -Gradle cache. - -The agent-facing documentation lives in -[`../../skills/api-discovery/SKILL.md`](../../skills/api-discovery/SKILL.md); -this file is the implementation reference. - -## Why - -Agents investigating library APIs used to run dozens of `find -~/.gradle/caches` + `unzip -l` + `unzip -p` calls per question. Each -`unzip` decompresses the archive from scratch β€” slow and token-heavy. - -This package replaces that pattern with two cheap reads: - -1. **Sibling-first** β€” every Spine artifact maps to a sibling clone - under `//`. The source tree is already on - disk; we just resolve the right submodule path. -2. **Extraction cache** β€” non-Spine artifacts (Jackson, Guava, etc.) - have their `-sources.jar` extracted **once** to a per-workstation - cache. Subsequent queries return instantly. - -## Layout - -``` -.agents/scripts/api-discovery/ -β”œβ”€β”€ README.md # this file -β”œβ”€β”€ lib/common.sh # shared bash helpers -β”œβ”€β”€ discover # main entry β€” resolve a coordinate to a path -β”œβ”€β”€ extract-sources # one-shot JAR extraction (race-safe) -β”œβ”€β”€ update-sibling # `git pull --ff-only` a stale sibling (safe-guarded) -└── clean-cache # prune the extraction cache -``` - -The cache itself is **not** under the repo. It lives at: - -``` -/.agents/caches/api-discovery/sources//// -``` - -`` defaults to the parent of the consumer repo (e.g. -`/Users//Projects/Spine/` when the consumer repo is -`.../Spine/config/`). To override, write the absolute path to an -alternative root into `.workspace-root` next to this README (the -script is gitignored). - -## Bootstrap - -First-time use needs the cache directory created. The scripts detect -its absence and exit `10`; the skill instructs the agent to ask the -user whether to: - -1. **Approve** the default path, -2. **Provide an alternative** workspace root, -3. **Disable** the cache for this repo (recorded in per-developer - auto-memory; sibling-first resolution still works). - -## Scripts - -### `discover` - -``` -discover :: -discover : # version pulled from buildSrc -discover # Spine-only; group + version inferred -``` - -- **stdout** β€” absolute path to a directory you can `Grep`/`Read`. -- **stderr** β€” `STALE` warnings when the sibling's `versionToPublish` - differs from the declared dependency version, plus other - diagnostics. Always inspect. -- **exit 0** β€” path resolved (even if stale; the warning is on stderr). -- **exit 1** β€” unresolvable (missing sibling AND no sources JAR). -- **exit 10** β€” cache uninitialized; run the bootstrap flow. - -### `extract-sources` - -``` -extract-sources :: -``` - -Idempotent and race-safe. If the target directory is already populated -the script returns its path immediately. Concurrent first-time -extractions race on an atomic `mv` of a per-PID temp directory; the -loser discards its temp. - -### `update-sibling` - -``` -update-sibling # resolved under -update-sibling # acts on that path directly -``` - -Invoked by the agent (after user consent) when `discover` emits a -`STALE` warning. Safe by design: - -- Pulls **only** when the sibling is on `master` or `main` with a - clean working tree and a tracked upstream. -- On any other branch, exits `0` without touching anything β€” the - user's "advancing multiple subprojects at once" workflow keeps - feature branches checked out as intentional staging state. -- Refuses on detached HEAD (`3`), uncommitted changes (`4`), or - missing upstream (`5`). -- Never switches branches, never `--rebase`, never `--force`. - -On success (exit `0`), the script writes a single stable token to -**stdout** that names the outcome β€” callers should branch on the -token, not on stderr text. Failure paths produce empty stdout. - -Exit codes: - -| Code | stdout | Meaning | -|---|---|---| -| `0` | `pulled` | HEAD advanced to upstream tip | -| `0` | `up-to-date` | Already at upstream tip; nothing to do | -| `0` | `skipped-branch` | On a non-default branch; left untouched | -| `1` | _(empty)_ | Sibling not on disk | -| `2` | _(empty)_ | Not a git repository | -| `3` | _(empty)_ | Detached HEAD β€” refused | -| `4` | _(empty)_ | Working tree dirty β€” refused | -| `5` | _(empty)_ | No upstream tracking on default branch β€” refused | -| `6` | _(empty)_ | `git pull --ff-only` failed (divergence, network, etc.) | -| `64` | _(empty)_ | Usage error (no/too many arguments) β€” BSD `sysexits(3)` convention | - -### `clean-cache` - -``` -clean-cache --dry-run -clean-cache --older-than 30d [--dry-run] -clean-cache --all [--dry-run] -``` - -Manual pruning only. Nothing runs on a timer. - -## Conventions - -- **Bash 3.2 compatible** β€” macOS ships 3.2 by default. -- **No external dependencies** beyond `bash`, coreutils, `grep`, - `sed`, `awk`, `unzip`, `find`, and `git` (used only by - `update-sibling`). -- **stdout** is always the answer; **stderr** is diagnostics. Mix - them only by piping. -- Scripts source `lib/common.sh` after setting - `SPINE_API_DISCOVERY_DIR`, so the workspace-root pointer file is - reachable. - -## Troubleshooting - -| Symptom | Likely cause | Fix | -|---|---|---| -| `cache not initialized` (exit 10) | Bootstrap not run | Follow the skill's bootstrap prompt | -| `sibling not on disk` | Spine repo not cloned | `git clone` it next to your consumer repo | -| `STALE: ...` | Sibling drifted from declared version | Run `update-sibling ` (auto-skips feature branches), or accept the warning | -| `is in the Gradle cache but publishes no -sources.jar` | Upstream artifact has no sources | Read the binary `.class` files via a different tool, or look at GitHub directly | -| `is not in the local Gradle cache` | Gradle has not fetched the dep | `./gradlew dependencies` to populate, then retry | diff --git a/.agents/scripts/api-discovery/clean-cache b/.agents/scripts/api-discovery/clean-cache deleted file mode 100755 index 30f60492..00000000 --- a/.agents/scripts/api-discovery/clean-cache +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env bash -# -# Prune the workstation api-discovery extraction cache. -# -# Usage: -# clean-cache --dry-run # list what would be removed (no deletes) -# clean-cache --older-than 30d # remove entries older than 30 days -# clean-cache --older-than 7d --dry-run -# clean-cache --all # wipe the whole sources cache -# clean-cache --all --dry-run -# -# The cache is at -# `/.agents/caches/api-discovery/sources////`. -# "Age" is the directory's mtime (recorded at extraction time). -# -# Exit codes (see lib/common.sh): -# 0 β€” succeeded (with or without removals). -# 1 β€” bad arguments or filesystem error. -# 10 β€” cache not initialized (nothing to clean). -set -euo pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -SPINE_API_DISCOVERY_DIR="$SCRIPT_DIR" -export SPINE_API_DISCOVERY_DIR -# shellcheck source=lib/common.sh -. "$SCRIPT_DIR/lib/common.sh" - -usage() { - cat >&2 <<'EOF' -Usage: - clean-cache --dry-run - clean-cache --older-than [--dry-run] - clean-cache --all [--dry-run] - -DURATION is a `find -mtime`-style suffix: `7d`, `30d`, `90d` (days only). -EOF - exit "$EX_FAIL" -} - -mode="" -days="" -dry_run=0 - -while [ "$#" -gt 0 ]; do - case "$1" in - --dry-run) - dry_run=1 - shift - ;; - --all) - mode="all" - shift - ;; - --older-than) - mode="older-than" - shift - [ "$#" -gt 0 ] || usage - case "$1" in - *d) days="${1%d}" ;; - *) log_warn "duration must end in 'd' (days), got: $1"; usage ;; - esac - case "$days" in - ''|*[!0-9]*) log_warn "duration days must be numeric, got: $1"; usage ;; - esac - shift - ;; - -h|--help) - usage - ;; - *) - log_warn "unknown argument: $1" - usage - ;; - esac -done - -# Default to a no-op listing if no mode was given. -[ -n "$mode" ] || mode="older-than-default" - -if ! cache_initialized; then - log_warn "cache not initialized: $(cache_root)" - exit "$EX_NO_CACHE" -fi - -sources="$(sources_root)" - -# Collect targets into a temp list so we can preview and act consistently. -list_file="$(mktemp -t api-discovery-clean.XXXXXX)" -trap 'rm -f -- "$list_file"' EXIT - -case "$mode" in - all) - find "$sources" -mindepth 3 -maxdepth 3 -type d -print > "$list_file" 2>/dev/null || true - ;; - older-than) - find "$sources" -mindepth 3 -maxdepth 3 -type d -mtime "+$days" -print \ - > "$list_file" 2>/dev/null || true - ;; - older-than-default) - log_warn "no mode specified; use --all or --older-than d" - usage - ;; -esac - -count="$(wc -l < "$list_file" | tr -d '[:space:]')" - -# Prune now-empty `/` and `//` parents so the cache -# layout stays tidy. Two passes (artifact dirs first, then group dirs) so -# that emptying a group's last artifact also reclaims the group dir. -# Skipped under --dry-run so the command stays read-only. -prune_empty_parents() { - find "$sources" -mindepth 2 -maxdepth 2 -type d -empty -exec rmdir -- {} + \ - 2>/dev/null || true - find "$sources" -mindepth 1 -maxdepth 1 -type d -empty -exec rmdir -- {} + \ - 2>/dev/null || true -} - -if [ "$count" -eq 0 ]; then - log_warn "no entries match; cache untouched" - [ "$dry_run" -eq 0 ] && prune_empty_parents - exit "$EX_OK" -fi - -if [ "$dry_run" -eq 1 ]; then - log_warn "would remove $count entr$( [ "$count" -eq 1 ] && printf 'y' || printf 'ies' ):" - cat -- "$list_file" - exit "$EX_OK" -fi - -removed=0 -while IFS= read -r path; do - [ -n "$path" ] || continue - if rm -rf -- "$path"; then - removed=$((removed + 1)) - else - log_warn "failed to remove: $path" - fi -done < "$list_file" - -prune_empty_parents - -log_warn "removed $removed entr$( [ "$removed" -eq 1 ] && printf 'y' || printf 'ies' )" -exit "$EX_OK" diff --git a/.agents/scripts/api-discovery/discover b/.agents/scripts/api-discovery/discover deleted file mode 100755 index 2b69d36b..00000000 --- a/.agents/scripts/api-discovery/discover +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env bash -# -# Resolve the on-disk location of source code for a Maven artifact, so -# agents can `Grep`/`Read` it directly instead of repeatedly `unzip`ing -# sources JARs out of the Gradle cache. -# -# Strategy: -# 1. Spine artifacts (group = io.spine / io.spine.tools / etc.) are -# served from a sibling clone under `//`. The -# sibling is identified from the `github.com/SpineEventEngine/` -# URL inside the matching `buildSrc/.../dependency/local/.kt` -# file. Submodule paths are resolved by trying canonical name -# transformations (see `resolve_submodule_path`). -# 2. Anything else (and Spine fallbacks when no sibling is on disk) -# is served from the per-workstation extraction cache populated by -# `extract-sources`. -# -# Usage: -# discover :: -# discover : -# discover -# -# Output: -# stdout: absolute path to a directory containing the source tree. -# stderr: freshness warnings and explanatory diagnostics. Always -# inspect stderr before relying on the resolved path. -# -# Exit codes (see lib/common.sh): -# 0 β€” path resolved (path on stdout). -# 1 β€” unresolvable (sibling missing AND extraction failed). -# 10 β€” workstation cache directory not initialized AND the query -# requires the cache (i.e. sibling-first did not succeed). -# Spine-sibling resolution never triggers EX_NO_CACHE β€” the -# skill's "Non-cached" mode keeps working without bootstrap. -set -euo pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -SPINE_API_DISCOVERY_DIR="$SCRIPT_DIR" -export SPINE_API_DISCOVERY_DIR -# shellcheck source=lib/common.sh -. "$SCRIPT_DIR/lib/common.sh" - -usage() { - cat >&2 <<'EOF' -Usage: discover - -Where is one of: - group:artifact:version e.g. io.spine:spine-base:2.0.0-SNAPSHOT.390 - group:artifact e.g. io.spine:spine-base - artifact e.g. spine-base - -Spine artifacts resolve to the local sibling clone; non-Spine -artifacts resolve to the extracted-sources cache. -EOF - exit "$EX_FAIL" -} - -[ "$#" -eq 1 ] || usage - -parse_query "$1" -group="$Q_GROUP" -artifact="$Q_ARTIFACT" -version="$Q_VERSION" - -if [ -z "$artifact" ]; then - log_warn "empty artifact in query: $1" - exit "$EX_FAIL" -fi - -# NOTE: We intentionally do NOT check `cache_initialized` here. The -# Spine-sibling path doesn't need the cache, and the skill's -# "Non-cached" bootstrap option must keep that path working without -# bootstrap. If we end up falling through to `extract-sources`, that -# script enforces the cache check on its own and raises EX_NO_CACHE. - -# Try the sibling path for Spine artifacts. If the group is empty we -# still attempt the local-deps lookup; a hit means it's a Spine artifact. -try_sibling() { - local dep_file - dep_file="$(find_local_dep_file_for_artifact "$artifact")" - [ -n "$dep_file" ] || return 1 - - local sibling_name workspace sibling_path - sibling_name="$(sibling_name_from_dep_file "$dep_file")" - workspace="$(workspace_root)" || return 1 - sibling_path="$workspace/$sibling_name" - - if [ ! -d "$sibling_path" ]; then - log_warn "sibling not on disk: $sibling_path (declared in $dep_file)" - return 1 - fi - - local module_path - module_path="$(resolve_submodule_path "$sibling_path" "$artifact")" - - if [ ! -d "$module_path" ]; then - log_warn "resolved submodule path missing: $module_path" - return 1 - fi - - # Freshness check: declared version vs sibling's published version. - local declared sibling_v - declared="${version:-$(read_declared_version "$dep_file" || true)}" - sibling_v="$(read_sibling_version "$sibling_path" 2>/dev/null || true)" - - if [ -n "$declared" ] && [ -n "$sibling_v" ] && \ - [ "$declared" != "$sibling_v" ]; then - log_warn "STALE: $artifact declared $declared in $(basename -- "$dep_file") but sibling publishes $sibling_v" - log_warn "sources at $module_path may differ from the published artifact" - fi - - printf '%s\n' "$module_path" - return 0 -} - -# Try sibling first when it could plausibly be a Spine artifact. -if [ -z "$group" ] || is_spine_group "$group"; then - if try_sibling; then - exit "$EX_OK" - fi -fi - -# Fall back to the extraction cache. This needs a full coordinate. -if [ -z "$group" ] || [ -z "$artifact" ] || [ -z "$version" ]; then - # Try to fill in the missing pieces from a local dep file. - if [ -z "$version" ]; then - dep_file="$(find_local_dep_file_for_artifact "$artifact" || true)" - if [ -n "$dep_file" ]; then - version="$(read_declared_version "$dep_file" || true)" - fi - fi - if [ -z "$group" ]; then - # The local Spine objects all use Spine.group / Spine.toolsGroup. - # Without a sibling hit we can't disambiguate; require explicit group. - log_warn "cannot resolve $artifact without a Maven group" - log_warn "retry with :[:]" - exit "$EX_FAIL" - fi - if [ -z "$version" ]; then - log_warn "cannot resolve $group:$artifact without a version" - log_warn "retry with ::" - exit "$EX_FAIL" - fi -fi - -# Delegate to extract-sources. It handles "already cached" by returning -# the path immediately, so this path is fast on repeat queries. -# The `|| exit $?` idiom propagates extract-sources' exit code under -# `set -e`; do NOT split into `target=...; status=$?` β€” `set -e` may -# terminate the script before the status check runs. -target="$("$SCRIPT_DIR/extract-sources" "$group:$artifact:$version")" || exit $? - -printf '%s\n' "$target" diff --git a/.agents/scripts/api-discovery/extract-sources b/.agents/scripts/api-discovery/extract-sources deleted file mode 100755 index a8456680..00000000 --- a/.agents/scripts/api-discovery/extract-sources +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env bash -# -# Extract a `-sources.jar` from the local Gradle cache into the workstation -# api-discovery cache. Idempotent and race-safe: a second invocation that -# observes an existing target returns immediately; concurrent first-time -# extractions race on the atomic `mv` of a per-PID temp directory. -# -# Usage: -# extract-sources :: -# -# Output: -# stdout: absolute path to the extracted source tree. -# stderr: explanatory diagnostics on cache misses or failures. -# -# Exit codes (see lib/common.sh for the shared definitions): -# 0 β€” extraction successful (or already cached). -# 1 β€” sources unavailable (missing JAR, no `-sources` variant, etc.). -# 10 β€” workstation cache directory not initialized. -# -set -euo pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -SPINE_API_DISCOVERY_DIR="$SCRIPT_DIR" -export SPINE_API_DISCOVERY_DIR -# shellcheck source=lib/common.sh -. "$SCRIPT_DIR/lib/common.sh" - -usage() { - cat >&2 <<'EOF' -Usage: extract-sources :: - -Extracts the matching `-sources.jar` from the local Gradle cache into -`/.agents/caches/api-discovery/sources////`. -EOF - exit "$EX_FAIL" -} - -[ "$#" -eq 1 ] || usage - -parse_query "$1" -group="$Q_GROUP" -artifact="$Q_ARTIFACT" -version="$Q_VERSION" - -if [ -z "$group" ] || [ -z "$artifact" ] || [ -z "$version" ]; then - log_warn "extract-sources requires a full coordinate ::" - exit "$EX_FAIL" -fi - -if ! cache_initialized; then - log_warn "cache not initialized: $(cache_root)" - log_warn "run the api-discovery bootstrap flow to create it" - exit "$EX_NO_CACHE" -fi - -target="$(sources_root)/$group/$artifact/$version" - -if [ -d "$target" ] && [ -n "$(ls -A -- "$target" 2>/dev/null)" ]; then - printf '%s\n' "$target" - exit "$EX_OK" -fi - -sources_jar="$(find_gradle_cache_jar "$group" "$artifact" "$version" -sources)" -if [ -z "$sources_jar" ]; then - if [ -n "$(find_gradle_cache_jar "$group" "$artifact" "$version")" ]; then - log_warn "$group:$artifact:$version is in the Gradle cache but publishes no -sources.jar" - exit "$EX_FAIL" - fi - log_warn "$group:$artifact:$version is not in the local Gradle cache" - log_warn "run './gradlew dependencies' (or rebuild) to fetch it, then retry" - exit "$EX_FAIL" -fi - -parent="$(dirname -- "$target")" -mkdir -p -- "$parent" - -# Race-safe extraction: -# 1) extract into a sibling temp dir whose name embeds our PID, -# 2) check that the final target does not yet exist (race-lost -# detection); if it does, drop our temp and use the existing tree, -# 3) otherwise `mv tmp target`. Note that on macOS/Linux, `mv` into an -# existing directory silently moves the source INSIDE it β€” we cannot -# rely on `mv` failing when the race is lost. The pre-test catches -# the common case; step 4 catches the narrow window between test -# and mv. -# 4) post-mv, detect the rare race where `target` materialized between -# our existence test and the mv: in that case `target/` -# now exists; remove it. -tmp="${target}.tmp.$$" -trap 'rm -rf -- "$tmp"' EXIT -rm -rf -- "$tmp" -mkdir -p -- "$tmp" - -if ! unzip -q -o -- "$sources_jar" -d "$tmp"; then - log_warn "unzip failed for $sources_jar" - exit "$EX_FAIL" -fi - -if [ -e "$target" ]; then - # Another process beat us to it. Discard our temp; use theirs. - rm -rf -- "$tmp" -else - if ! mv -- "$tmp" "$target"; then - log_warn "could not move extracted sources into $target" - exit "$EX_FAIL" - fi - # Close the narrow race window: if `target` appeared between the - # existence test and the mv, `mv` silently moved `$tmp` INSIDE the - # winning extraction. Clean up the nested debris. - nested="$target/$(basename -- "$tmp")" - if [ -e "$nested" ]; then - rm -rf -- "$nested" - fi -fi -trap - EXIT - -log_warn "extracted $group:$artifact:$version -> $target" -printf '%s\n' "$target" diff --git a/.agents/scripts/api-discovery/lib/common.sh b/.agents/scripts/api-discovery/lib/common.sh deleted file mode 100644 index fa3e9c83..00000000 --- a/.agents/scripts/api-discovery/lib/common.sh +++ /dev/null @@ -1,364 +0,0 @@ -#!/usr/bin/env bash -# -# Shared helpers for the api-discovery scripts. -# Sourced by ../discover, ../extract-sources, ../clean-cache. -# -# All functions write diagnostics to stderr; "return values" go to stdout. -# -# Conventions: -# - Bash 3.2 compatible (macOS default). -# - No external deps beyond coreutils, grep, sed, unzip. -# - `set -euo pipefail` is set by the caller, not here. - -# Exit codes used across the scripts. -readonly EX_OK=0 -readonly EX_FAIL=1 -readonly EX_NO_CACHE=10 # cache uninitialized; agent runs bootstrap - -# Resolve the consumer repository root β€” the nearest ancestor of $PWD -# containing `buildSrc/src/main/kotlin/io/spine/dependency/`. Falls back to -# CLAUDE_PROJECT_DIR (set by Claude Code) if no such ancestor exists. -# Prints the absolute path to stdout. Exits non-zero if it cannot resolve. -consumer_repo_root() { - local dir="${1:-$PWD}" - while [ "$dir" != "/" ] && [ -n "$dir" ]; do - if [ -d "$dir/buildSrc/src/main/kotlin/io/spine/dependency" ]; then - printf '%s\n' "$dir" - return 0 - fi - dir="$(dirname -- "$dir")" - done - if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && \ - [ -d "$CLAUDE_PROJECT_DIR/buildSrc/src/main/kotlin/io/spine/dependency" ]; then - printf '%s\n' "$CLAUDE_PROJECT_DIR" - return 0 - fi - return 1 -} - -# Resolve the workspace root (parent of the consumer repo by default). -# Honors an optional pointer file at -# `/.workspace-root` containing an absolute path; used when the -# user picks "alternative root" during bootstrap. -workspace_root() { - local scripts_dir="${SPINE_API_DISCOVERY_DIR:-}" - if [ -z "$scripts_dir" ]; then - local repo - repo="$(consumer_repo_root)" || return 1 - scripts_dir="$repo/.agents/scripts/api-discovery" - fi - local pointer="$scripts_dir/.workspace-root" - if [ -f "$pointer" ]; then - # Read the first line verbatim. `IFS=` keeps internal spaces - # (paths like `/Users/me/Spine Workspace` must survive intact); - # `read -r` strips the trailing newline. Strip a stray CR for - # Windows-style line endings. - local custom="" - IFS= read -r custom < "$pointer" 2>/dev/null || true - custom="${custom%$'\r'}" - if [ -n "$custom" ] && [ -d "$custom" ]; then - printf '%s\n' "$custom" - return 0 - fi - fi - local repo - repo="$(consumer_repo_root)" || return 1 - (cd "$repo/.." && pwd) -} - -# Directory that holds the per-workstation api-discovery cache. -cache_root() { - local ws - ws="$(workspace_root)" || return 1 - printf '%s/.agents/caches/api-discovery\n' "$ws" -} - -# Subdirectory under cache_root where extracted sources live. -sources_root() { - local root - root="$(cache_root)" || return 1 - printf '%s/sources\n' "$root" -} - -# Returns 0 if the sources cache directory exists; 1 otherwise. -cache_initialized() { - local s - s="$(sources_root)" || return 1 - [ -d "$s" ] -} - -# Returns the first Gradle-cache JAR path matching the coordinates and -# optional suffix ("-sources" or empty). Empty stdout means "not found". -find_gradle_cache_jar() { - local group="$1" artifact="$2" version="$3" suffix="${4:-}" - local base="$HOME/.gradle/caches/modules-2/files-2.1/$group/$artifact/$version" - [ -d "$base" ] || return 0 - local jar - jar="$(find "$base" -maxdepth 2 -type f \ - -name "${artifact}-${version}${suffix}.jar" 2>/dev/null \ - | head -n 1)" - [ -n "$jar" ] && printf '%s\n' "$jar" - return 0 -} - -# Extract the canonical `const val version` value from a Spine local/.kt -# file. Anchors at line start (with optional access modifier) so that -# `const val version` strings inside KDoc, comments, or other quoted text -# do not match. Each local/.kt is expected to declare exactly one -# top-level `version` constant; multi-artifact files use different -# constant names (e.g. `mcVersion`) for their non-canonical versions. -read_declared_version() { - local file="$1" - [ -f "$file" ] || return 1 - sed -nE 's/^[[:space:]]*(private[[:space:]]+|internal[[:space:]]+|public[[:space:]]+|protected[[:space:]]+)?const[[:space:]]+val[[:space:]]+version[[:space:]]*=[[:space:]]*"([^"]+)".*/\2/p' \ - "$file" | head -n 1 -} - -# Read a `val [: Type] by extra("VALUE")` declaration from a file. -# Prints VALUE on stdout; empty if not found. -_read_extra_val() { - local file="$1" name="$2" - sed -nE 's/^[[:space:]]*val[[:space:]]+'"$name"'([[:space:]]*:[[:space:]]*[A-Za-z]+)?[[:space:]]+by[[:space:]]+extra\("([^"]+)"\).*/\2/p' \ - "$file" | head -n 1 -} - -# Read the sibling's "main" version from `/version.gradle.kts`. -# Tries (in order): -# 1. `versionToPublish` β€” canonical name used by most siblings. -# 2. `Version` β€” e.g. `mcJavaVersion` -# for sibling `mc-java`, `protoDataVersion` for `ProtoData`. -# Returns non-zero if neither is found; callers treat that as -# "freshness check unavailable". -read_sibling_version() { - local sibling="$1" - local file="$sibling/version.gradle.kts" - [ -f "$file" ] || return 1 - - local v - v="$(_read_extra_val "$file" "versionToPublish")" - if [ -n "$v" ]; then - printf '%s\n' "$v" - return 0 - fi - - local sibling_name camel - sibling_name="$(basename -- "$sibling")" - camel="$(camel_case_lower "$sibling_name")Version" - v="$(_read_extra_val "$file" "$camel")" - if [ -n "$v" ]; then - printf '%s\n' "$v" - return 0 - fi - - return 1 -} - -# Convert a PascalCase name to kebab-case. -# Examples: Base -> base; CoreJvm -> core-jvm; CoreJvmCompiler -> core-jvm-compiler. -kebab_case() { - printf '%s\n' "$1" | sed -E 's/([a-z0-9])([A-Z])/\1-\2/g; s/([A-Z]+)([A-Z][a-z])/\1-\2/g' \ - | tr '[:upper:]' '[:lower:]' -} - -# Convert a kebab-case or PascalCase name to camelCase (first letter lowercase). -# Examples: base-libraries -> baseLibraries; mc-java -> mcJava; -# core-jvm-compiler -> coreJvmCompiler; ProtoData -> protoData. -camel_case_lower() { - local input="$1" - local pascal - pascal="$(printf '%s\n' "$input" | awk -F- '{ - out="" - for (i = 1; i <= NF; i++) { - out = out toupper(substr($i, 1, 1)) substr($i, 2) - } - print out - }')" - local first rest - first="$(printf '%s' "$pascal" | cut -c1 | tr '[:upper:]' '[:lower:]')" - rest="$(printf '%s' "$pascal" | cut -c2-)" - printf '%s%s\n' "$first" "$rest" -} - -# Given a Spine local/.kt file, deduce its sibling repository name. -# Priority: -# 1. `https://github.com/SpineEventEngine/` URL inside the file. -# 2. kebab-case of the file's basename (without `.kt`). -sibling_name_from_dep_file() { - local file="$1" - [ -f "$file" ] || return 1 - local from_url - from_url="$(sed -nE 's|.*github\.com/SpineEventEngine/([A-Za-z0-9._-]+).*|\1|p' \ - "$file" | head -n 1)" - if [ -n "$from_url" ]; then - # Trim any trailing slash or punctuation. - from_url="${from_url%/}" - printf '%s\n' "$from_url" - return 0 - fi - local base - base="$(basename -- "$file" .kt)" - kebab_case "$base" -} - -# Returns 0 if the given directory contains a Kotlin/Java source set. -# Recognizes plain `src/main` and Kotlin Multiplatform names such as -# `src/commonMain`, `src/jvmMain`, `src/jsMain`, `src/nativeMain`. -has_source_set() { - local dir="$1" - [ -d "$dir/src" ] || return 1 - local candidate - for candidate in main commonMain jvmMain jsMain nativeMain; do - [ -d "$dir/src/$candidate" ] && return 0 - done - return 1 -} - -# Resolve a submodule inside a sibling that owns a given artifact. -# Tries candidate subdirectory names in order, returning the first that -# contains a recognizable source set: -# 1. Sibling root (single-module siblings such as `reflect`, `testlib`). -# 2. `/` (artifact name == submodule name). -# 3. `/` -# (`spine-base` -> `base-libraries/base`). -# 4. `/-`-stripped>` -# (`spine-protodata-backend` -> `ProtoData/backend`). -# 5. `/` -# (covers `spine-tool-base` -> `tool-base/tool-base`). -# Falls back to the sibling root when no candidate matches. -resolve_submodule_path() { - local sibling="$1" artifact="$2" - [ -d "$sibling" ] || return 1 - - if has_source_set "$sibling"; then - printf '%s\n' "$sibling" - return 0 - fi - - local sibling_name lower_sibling - sibling_name="$(basename -- "$sibling")" - lower_sibling="$(printf '%s' "$sibling_name" | tr '[:upper:]' '[:lower:]')" - - local candidates=() - candidates+=("$artifact") - - case "$artifact" in - spine-*) candidates+=("${artifact#spine-}") ;; - esac - - case "$artifact" in - spine-${lower_sibling}-*) candidates+=("${artifact#spine-${lower_sibling}-}") ;; - ${lower_sibling}-*) candidates+=("${artifact#${lower_sibling}-}") ;; - esac - - candidates+=("$sibling_name") - - local cand - for cand in "${candidates[@]}"; do - [ -n "$cand" ] || continue - if has_source_set "$sibling/$cand"; then - printf '%s/%s\n' "$sibling" "$cand" - return 0 - fi - done - - printf '%s\n' "$sibling" -} - -# Identify whether a Maven group belongs to the Spine sibling ecosystem. -# Returns 0 (true) for Spine groups, 1 (false) otherwise. -is_spine_group() { - case "$1" in - io.spine|io.spine.tools|io.spine.protodata|io.spine.validation) - return 0 - ;; - *) - return 1 - ;; - esac -} - -# Parse a query into group, artifact, version. Sets the globals -# Q_GROUP, Q_ARTIFACT, Q_VERSION. Some may be empty. -# Accepts: `group:artifact:version`, `group:artifact`, `artifact`, or a -# free-form name that we treat as either an artifact or a sibling label. -# Returns 0 always; the caller decides what an empty field means. -parse_query() { - local q="$1" - Q_GROUP=""; Q_ARTIFACT=""; Q_VERSION="" - local rest - case "$q" in - *:*:*) - Q_GROUP="${q%%:*}" - rest="${q#*:}" - Q_ARTIFACT="${rest%%:*}" - Q_VERSION="${rest#*:}" - ;; - *:*) - Q_GROUP="${q%%:*}" - Q_ARTIFACT="${q#*:}" - ;; - *) - Q_ARTIFACT="$q" - ;; - esac -} - -# Escape every non-alphanumeric character so the result is safe to embed -# in a POSIX ERE pattern. Cheap overkill β€” Maven artifact names should -# never need most of these, but the caller's input is untrusted. -escape_ere() { - printf '%s' "$1" | sed 's/[^A-Za-z0-9]/\\&/g' -} - -# Locate the consumer repo's local/.kt file that declares a Maven artifact. -# Some local files build artifact coordinates via Kotlin string templates -# (`"$prefix-base"`, `"$group:$prefix-java:$version"`). To match those we -# expand the per-file `prefix` constant before grepping. Other template -# variables resolve to literals already present in the source, so a plain -# grep finds them. -# Returns the path of the first matching file (or empty). -find_local_dep_file_for_artifact() { - local artifact="$1" - local repo - repo="$(consumer_repo_root)" || return 1 - local local_dir="$repo/buildSrc/src/main/kotlin/io/spine/dependency/local" - [ -d "$local_dir" ] || return 0 - - # Validate the artifact name against the Maven convention before - # building a regex from it. Reject anything we cannot guarantee is - # safe; this prevents shell-quoted regex metacharacters in - # caller-supplied input from being interpreted by `grep -E`. - case "$artifact" in - ''|*[!A-Za-z0-9._-]*) - log_warn "invalid artifact name (allowed: A-Z a-z 0-9 . _ -): $artifact" - return 1 - ;; - esac - local artifact_esc - artifact_esc="$(escape_ere "$artifact")" - - local file prefix expanded - for file in "$local_dir"/*.kt; do - [ -f "$file" ] || continue - prefix="$(sed -nE 's/.*const[[:space:]]+val[[:space:]]+prefix[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' \ - "$file" | head -1)" - if [ -n "$prefix" ]; then - expanded="$(sed -e 's|\$prefix|'"$prefix"'|g; s|\${prefix}|'"$prefix"'|g' "$file")" - else - expanded="$(cat -- "$file")" - fi - # Match the artifact as a complete coordinate component: - # delimited by `"`, `:`, or `-` on either side, never as a substring. - if printf '%s\n' "$expanded" | grep -qE "[\":-]${artifact_esc}[\":-]|[\":-]${artifact_esc}\$|^${artifact_esc}\$"; then - printf '%s\n' "$file" - return 0 - fi - done - return 0 -} - -# Emit a stderr line tagged with the scripts' identity, so the agent can -# distinguish them from unrelated noise. -log_warn() { - printf 'api-discovery: %s\n' "$*" >&2 -} diff --git a/.agents/scripts/api-discovery/update-sibling b/.agents/scripts/api-discovery/update-sibling deleted file mode 100755 index e145aaee..00000000 --- a/.agents/scripts/api-discovery/update-sibling +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bash -# -# Refresh a Spine sibling repo on disk so api-discovery returns sources -# matching the most recent published version. -# -# Safe by design: -# - Pulls only when the sibling is on its default branch (master or -# main) with a clean working tree and a tracked upstream. -# - On any other branch, treats the local state as intentional (the -# user is "advancing multiple subprojects at the same time") and -# exits 0 without touching anything. -# - Refuses on detached HEAD, uncommitted changes, or missing upstream. -# - Never switches branches, never `--rebase`, never `--force`, never -# fetches a branch the user does not already track. The strictest -# action it performs is `git pull --ff-only`. -# -# This script is intended to be invoked by the api-discovery skill -# after the agent has surfaced a STALE warning to the user AND received -# explicit consent. -# -# Usage: -# update-sibling # resolved under -# update-sibling # acts on that path directly -# -# Output: -# stdout: exactly one stable token on success (`pulled`, `up-to-date`, -# or `skipped-branch` β€” see Exit codes). Empty on any failure -# path so callers cannot misread an error as a result. -# stderr: human-facing diagnostics, including git's own pull output. -# -# Exit codes: -# 0 β€” succeeded; stdout token names the outcome: -# `pulled` β€” HEAD advanced to upstream tip. -# `up-to-date` β€” already at upstream tip; nothing to do. -# `skipped-branch` β€” on a non-default branch; left untouched. -# 1 β€” sibling not on disk. -# 2 β€” not a git repository. -# 3 β€” detached HEAD; refused. -# 4 β€” uncommitted tracked changes; refused. -# 5 β€” default branch has no upstream tracking; refused. -# 6 β€” `git pull --ff-only` failed (divergence, conflict, network, etc.). -# 64 β€” usage error (no/too many args). -set -euo pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -SPINE_API_DISCOVERY_DIR="$SCRIPT_DIR" -export SPINE_API_DISCOVERY_DIR -# shellcheck source=lib/common.sh -. "$SCRIPT_DIR/lib/common.sh" - -# Layered on top of the shared EX_OK / EX_FAIL / EX_NO_CACHE in common.sh. -readonly EX_NOT_GIT=2 -readonly EX_DETACHED=3 -readonly EX_DIRTY=4 -readonly EX_NO_UPSTREAM=5 -readonly EX_PULL_FAILED=6 -readonly EX_USAGE=64 # BSD sysexits(3) convention. - -usage() { - cat >&2 <<'EOF' -Usage: update-sibling - -Examples: - update-sibling base-libraries - update-sibling /Users/me/Projects/Spine/validation - -Pulls only when the sibling is on its default branch (master|main) -with a clean working tree and a tracked upstream. Otherwise it leaves -the sibling alone. - -On success, prints one of: pulled | up-to-date | skipped-branch. -EOF - exit "$EX_USAGE" -} - -[ "$#" -eq 1 ] || usage -arg="$1" - -# Resolve to an absolute sibling path. Accept either a bare repo name -# (looked up under ) or an absolute path. -case "$arg" in - /*) sibling="$arg" ;; - *) - ws="$(workspace_root)" || { - log_warn "cannot resolve workspace root" - exit "$EX_FAIL" - } - sibling="$ws/$arg" - ;; -esac - -if [ ! -d "$sibling" ]; then - log_warn "sibling not on disk: $sibling" - exit "$EX_FAIL" # distinct from EX_USAGE so callers can tell - # "you passed me a bad path" apart from - # "you didn't pass me anything". -fi - -# `.git` may be a directory (normal clone) or a file (worktree pointer). -if [ ! -e "$sibling/.git" ]; then - log_warn "not a git repository: $sibling" - exit "$EX_NOT_GIT" -fi - -branch="$(git -C "$sibling" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" -if [ -z "$branch" ]; then - log_warn "failed to read current branch in $sibling" - exit "$EX_FAIL" -fi -if [ "$branch" = "HEAD" ]; then - log_warn "$sibling is in detached HEAD; not pulling" - exit "$EX_DETACHED" -fi - -# Non-default branch: the user is intentionally on a feature branch. -# Use the current code as-is. -case "$branch" in - master|main) - ;; - *) - log_warn "$sibling is on '$branch' (not master/main); using local code as-is" - printf 'skipped-branch\n' - exit "$EX_OK" - ;; -esac - -# Dirty-tree guard: refuse on TRACKED modifications, since a fast-forward -# could conflict with them. Untracked files are tolerated: they don't -# conflict with HEAD movement on their own, and any genuine overwrite -# conflict (upstream adds a file whose path already exists untracked -# locally) still surfaces below as EX_PULL_FAILED via git's own check. -if [ -n "$(git -C "$sibling" status --porcelain --untracked-files=no 2>/dev/null)" ]; then - log_warn "$sibling has uncommitted tracked changes on '$branch'; not pulling" - exit "$EX_DIRTY" -fi - -# Upstream guard: --ff-only against an undefined upstream is meaningless. -if ! git -C "$sibling" rev-parse --abbrev-ref --symbolic-full-name '@{u}' \ - >/dev/null 2>&1; then - log_warn "$sibling/$branch has no upstream tracking; not pulling" - exit "$EX_NO_UPSTREAM" -fi - -# Capture HEAD before/after so we can report what changed. -before="$(git -C "$sibling" rev-parse HEAD)" -if ! git -C "$sibling" pull --ff-only >&2; then - log_warn "git pull --ff-only failed in $sibling" - exit "$EX_PULL_FAILED" -fi -after="$(git -C "$sibling" rev-parse HEAD)" - -if [ "$before" = "$after" ]; then - log_warn "$sibling already up-to-date on '$branch' ($after)" - printf 'up-to-date\n' -else - log_warn "$sibling pulled '$branch': $before -> $after" - printf 'pulled\n' -fi -exit "$EX_OK" diff --git a/.agents/scripts/pre-pr-gate.sh b/.agents/scripts/pre-pr-gate.sh deleted file mode 100755 index 88de51c2..00000000 --- a/.agents/scripts/pre-pr-gate.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -# -# PreToolUse hook: block `gh pr create` unless /pre-pr has successfully run -# for the current HEAD. The hook is intentionally unaware of the repository's -# versioning or build system; the /pre-pr skill decides which checks apply. -# -# Input: hook JSON on stdin (tool_name, tool_input.command). -# Exit: 0 to allow, 2 to block (stderr is surfaced to Claude). -# -set -eu - -command -v jq >/dev/null 2>&1 || exit 0 - -input=$(cat) -tool=$(printf '%s' "$input" | jq -r '.tool_name // empty') -[ "$tool" != "Bash" ] && exit 0 - -cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty') - -# Split the command on shell separators (`;`, `&`, `|` β€” `&&`/`||` collapse -# to repeated newlines, which is fine) and check each segment. Only block -# when a segment STARTS (after optional whitespace) with `gh pr create`. -# This avoids false positives like `echo "gh pr create"` or test fixtures -# that mention the string, while still catching `cd dir && gh pr create` -# and `cat body | gh pr create`. `tr` is used (not `sed s///`) because -# BSD `sed` on macOS does not interpret `\n` in the replacement string. -if ! printf '%s' "$cmd" \ - | tr ';&|' '\n\n\n' \ - | grep -qE '^[[:space:]]*gh[[:space:]]+pr[[:space:]]+create([[:space:]]|$)'; then - exit 0 -fi - -repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0 -sentinel="$repo_root/.git/pre-pr.ok" - -block() { - cat >&2 - exit 2 -} - -if [ ! -f "$sentinel" ]; then - block </dev/null 2>&1 || exit 0 - -input=$(cat) -file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty') -command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty') - -touches_version_file() { - if [ "$file" = "version.gradle.kts" ] || [ "${file%/version.gradle.kts}" != "$file" ]; then - return 0 - fi - - printf '%s\n' "$command" \ - | grep -qE '^\*\*\* (Add|Update|Delete) File: (.+/)?version\.gradle\.kts$' -} - -if touches_version_file; then - cat >&2 <<'EOF' -Direct edits to version.gradle.kts are blocked by a project hook. - -If this repository already has a root version.gradle.kts, use the bump-version -skill instead: - /bump-version [snapshot|minor|major] - -If this repository does not have a root version.gradle.kts, do not add one just -to satisfy /pre-pr; the version check is not applicable. - -See: - - .agents/version-policy.md - - .agents/skills/bump-version/SKILL.md -EOF - exit 2 -fi - -exit 0 diff --git a/.agents/scripts/publish-version-gate.sh b/.agents/scripts/publish-version-gate.sh deleted file mode 100755 index 996bd256..00000000 --- a/.agents/scripts/publish-version-gate.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -# -# PreToolUse hook: block any `./gradlew` invocation that could publish to -# Maven Local without a version bump on the current branch. Wraps the -# Layer-1 deterministic check at `version-bumped.sh`. -# -# This is intentionally broad: it fires on `build`, `publish`, -# `publishToMavenLocal`, and any `:publish*` task. Many repos in this -# constellation chain `publishToMavenLocal` into `build` because -# integration tests consume those local artifacts, so `build` itself is -# publish-risky. False positives (blocking a pure compile) are preferable -# to overwriting a previously published snapshot that consuming repos -# rely on. -# -# Input: hook JSON on stdin (tool_name, tool_input.command). -# Exit: 0 to allow, 2 to block (stderr is surfaced to Claude). -# -set -eu - -command -v jq >/dev/null 2>&1 || exit 0 - -input=$(cat) -tool=$(printf '%s' "$input" | jq -r '.tool_name // empty') -[ "$tool" != "Bash" ] && exit 0 - -cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty') - -# Split the command on shell separators (`;`, `&`, `|`) and inspect each -# segment. Only block when a segment, after optional whitespace, invokes -# `./gradlew` (or `./config/gradlew`) with a publish-risky task. Avoids -# false positives on `echo "./gradlew build"` or fixtures. -risky_segment() { - local seg="$1" - # Must start with a gradlew invocation. - printf '%s' "$seg" | grep -qE '^[[:space:]]*\.?/?(config/)?gradlew([[:space:]]|$)' || return 1 - # Must mention a publish-risky task. `build` is risky because it can - # finalize publishToMavenLocal in this config. The leading - # `(:[A-Za-z0-9_.-]+)*:?` covers qualified task paths - # (e.g. `:module:build`, `:a:b:publishToMavenLocal`) and a single - # leading-colon form (`:publishMavenJavaPublicationToMavenLocal`). - # `publish[^[:space:]]*` then catches every publish-task variant. - printf '%s' "$seg" | grep -qE '(^|[[:space:]])(:[A-Za-z0-9_.-]+)*:?(build|publish[^[:space:]]*|publishToMavenLocal|publishAllPublicationsToMavenLocal)([[:space:]]|$)' -} - -block_needed=0 -# `|| [ -n "$segment" ]` makes the loop process the final segment when the -# input has no trailing newline (which is the case for `printf '%s'`). -while IFS= read -r segment || [ -n "$segment" ]; do - if risky_segment "$segment"; then - block_needed=1 - break - fi -done < <(printf '%s' "$cmd" | tr ';&|' '\n\n\n') - -[ "$block_needed" -eq 0 ] && exit 0 - -repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0 -script="$repo_root/.agents/skills/version-bumped/scripts/version-bumped.sh" - -# If the helper is missing (e.g. partial clone), don't pretend we gated. -if [ ! -x "$script" ]; then - exit 0 -fi - -# `&& rc=0 || rc=$?` captures the exit code regardless of success/failure. -# After `if cmd; then ... fi`, $? reflects the if-fi structural exit (0), -# not the failed test's exit code β€” so we cannot use the if-fi form here. -err_file="/tmp/version-bumped.$$.err" -VERSION_BUMPED_QUIET=1 "$script" 2>"$err_file" && rc=0 || rc=$? -if [ "$rc" -eq 0 ]; then - rm -f "$err_file" - exit 0 -fi -err_payload=$(cat "$err_file" 2>/dev/null || true) -rm -f "$err_file" - -# Layer-1 returned a configuration error β€” do not block, surface the note. -if [ "$rc" -ne 1 ]; then - printf '%s\n' "$err_payload" >&2 - exit 0 -fi - -cat >&2 </dev/null 2>&1 || exit 0 - -input=$(cat) -file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty') -command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty') - -sanitize_file() { - local path="$1" - - [ -z "$path" ] && return 0 - [ ! -f "$path" ] && return 0 - - case "$path" in - *.java|*.kt|*.kts) ;; - *) return 0 ;; - esac - - tmp=$(mktemp) - awk ' - { sub(/[ \t]+$/, "") } - /^$/ { blank++; if (blank > 1) next; print; next } - { blank = 0; print } - ' "$path" > "$tmp" && mv "$tmp" "$path" -} - -if [ -n "$file" ]; then - sanitize_file "$file" - exit 0 -fi - -printf '%s\n' "$command" \ - | sed -nE 's/^\*\*\* (Add|Update) File: (.*)$/\2/p' \ - | sort -u \ - | while IFS= read -r path; do - sanitize_file "$path" - done diff --git a/.agents/scripts/update-copyright.sh b/.agents/scripts/update-copyright.sh deleted file mode 100755 index b25282fd..00000000 --- a/.agents/scripts/update-copyright.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# -# PostToolUse hook: refresh the copyright header of source files touched by -# Edit/Write/MultiEdit. Delegates to -# .agents/skills/update-copyright/scripts/update_copyright.py, which: -# - operates only on recognized source extensions, -# - never adds a header to a file that does not already have one, -# - rewrites `today.year` to the current year per the IntelliJ profile. -# -# Input: hook JSON on stdin. Claude Code passes `tool_input.file_path`; -# Codex `apply_patch` passes the patch text in `tool_input.command`. -# Exit: 0 always (post-tool-use; never block). -# -set -u - -# Required tools β€” silently no-op if either is missing so the hook never blocks. -command -v jq >/dev/null 2>&1 || exit 0 -command -v python3 >/dev/null 2>&1 || exit 0 - -input=$(cat) -file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true) -command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || true) - -root="${CLAUDE_PROJECT_DIR:-$(pwd)}" -script="$root/.agents/skills/update-copyright/scripts/update_copyright.py" - -[ -f "$script" ] || exit 0 - -update_path() { - local path="$1" - [ -z "$path" ] && return 0 - [ ! -f "$path" ] && return 0 - python3 "$script" --root "$root" "$path" >/dev/null 2>&1 || true -} - -if [ -n "$file" ]; then - update_path "$file" - exit 0 -fi - -printf '%s\n' "$command" \ - | sed -nE 's/^\*\*\* (Add|Update) File: (.*)$/\2/p' \ - | sort -u \ - | while IFS= read -r path; do - update_path "$path" - done - -exit 0 diff --git a/.agents/shared b/.agents/shared new file mode 160000 index 00000000..f3d8ac5b --- /dev/null +++ b/.agents/shared @@ -0,0 +1 @@ +Subproject commit f3d8ac5bed56b4695dbedd8a201ad7a0b81651e6 diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 00000000..f14734dd --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +shared/skills \ No newline at end of file diff --git a/.agents/skills/api-discovery/SKILL.md b/.agents/skills/api-discovery/SKILL.md deleted file mode 100644 index b1622ffd..00000000 --- a/.agents/skills/api-discovery/SKILL.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -name: api-discovery -description: > - Resolve the on-disk location of a Maven artifact's source code, - so you can `Grep`/`Read` it directly instead of running `unzip` - against JARs in the Gradle cache. Use this whenever you need to - inspect a library's API or implementation β€” definitions of public - types, method signatures, KDoc, internal helpers, etc. ---- - -# API discovery - -Before reading library source code, run the `discover` script in -`.agents/scripts/api-discovery/`. It returns a path you can hand -straight to `Grep`, `Read`, or `Glob`. - -Do **not** run `find ~/.gradle/caches` or `unzip` against cache JARs. -Each `unzip` decompresses the archive afresh β€” slow and token-heavy. - -## How to call it - -From the consumer repository root: - -```bash -.agents/scripts/api-discovery/discover -``` - -Where `` is one of: - -| Form | Example | Notes | -|---|---|---| -| `group:artifact:version` | `io.spine:spine-base:2.0.0-SNAPSHOT.390` | Most explicit | -| `group:artifact` | `io.spine:spine-base` | Version inferred from `buildSrc` | -| `artifact` | `spine-base` | Spine-only; group inferred from `buildSrc` | - -The script writes the absolute resolved path to **stdout**, and any -freshness/diagnostic warnings to **stderr**. Always read stderr β€” a -silent stdout means clean resolution; a noisy stderr means caveats -the user should know about. - -## Exit codes - -| Code | Meaning | What you do | -|---|---|---| -| `0` | Path on stdout is usable. | Pass it to `Grep`/`Read`/`Glob`. If stderr is non-empty, surface the warning to the user before relying on the path. | -| `1` | Unresolvable (no sibling AND no JAR). | Report the failure. **Do not** fall back to `unzip ~/.gradle/caches/...`. | -| `10` | Cache directory not initialized. | Run the **bootstrap flow** below. | - -## Bootstrap flow (exit 10) - -On the first run in a fresh workstation the per-workstation cache -directory does not yet exist. The script exits `10` and names the -path it would create. Ask the user: - -> The shared cache directory `/.agents/caches/api-discovery/` -> does not exist yet. How would you like to proceed? -> -> 1. **Approve** β€” create the directory at the default path. -> 2. **Alternative root** β€” pick a different parent for the shared -> `.agents/` directory (e.g., `~/SpineWorkspace`, `/srv/spine`). -> 3. **Non-cached** β€” skip the extraction cache. Sibling-first -> discovery still works for Spine artifacts; non-Spine deps will -> not be served by `api-discovery` in this repo. - -Then act on the user's reply: - -- **Approve** β†’ `mkdir -p /.agents/caches/api-discovery/sources`, - then re-run the original `discover` query. -- **Alternative root** β†’ ask for the absolute path ``, then: - ```bash - mkdir -p "/.agents/caches/api-discovery/sources" - printf '%s\n' "" \ - > .agents/scripts/api-discovery/.workspace-root - ``` - (the pointer file is gitignored). Then re-run `discover`. -- **Non-cached** β†’ record the choice in **per-developer auto-memory** - (project memory, type `feedback`), `name: api-discovery-cache-disabled`, - describing the user's choice and giving the "How to apply" rule: - *do not invoke `extract-sources` in this repo; for non-Spine deps - fall back to other investigation tools*. Then proceed with - sibling-first only. - -Check that memory at session start. If it exists, skip cache-touching -paths entirely. - -## Workflow - -1. **Always** call `discover` before reading library source. -2. Use the returned path with `Grep`/`Read`/`Glob` directly. Do **not** - `cd` into the directory β€” that adds path-prefix noise to tool calls - and makes line citations harder to read. -3. If stderr contains `STALE: ...`, the sibling on disk does not match - the version declared in `buildSrc`. Surface the warning AND offer - to refresh β€” see *Refreshing a stale sibling* below. -4. If the script exits `1`, report the failure with its stderr - message and stop. Do not try `unzip` as a workaround. - -## Refreshing a stale sibling - -The user keeps siblings cloned locally as the source of truth and -sometimes works across several siblings at once with a feature branch -checked out in each. So a `STALE` line has two possible meanings, and -they require different handling: - -- **Sibling is behind `master`/`main`.** A `git pull --ff-only` will - bring it up to date. -- **Sibling is on a feature branch.** This is *intentional* β€” the user - is staging changes across multiple subprojects. The local code is - the right code; **do not** pull. - -You cannot tell which case applies without inspecting the sibling. The -companion script `update-sibling` handles both safely: it pulls only -on the default branch with a clean tree and a tracked upstream, and -exits `0` without touching anything when on a feature branch. - -### Procedure - -When you see a `STALE: ...` line from `discover`: - -1. Surface the warning to the user. -2. Ask, in one short prompt: - > The sibling at `` is stale. Want me to try updating it? - > I'll only `git pull --ff-only` if it's on `master`/`main` with - > a clean working tree; if you have a feature branch checked out, - > I'll leave it as-is. -3. If the user agrees, run: - ```bash - .agents/scripts/api-discovery/update-sibling - ``` - `` is either the absolute path shown by - `discover` (preferred β€” unambiguous) or just the sibling repo name - (resolved under ``). -4. Read **stdout** to decide what to do next β€” it is a single stable - token, not free-form English: - - `pulled` β€” HEAD advanced. Re-run `discover` so the STALE warning - clears (or, more rarely, reports a different discrepancy). - - `up-to-date` β€” sibling was already at upstream tip. The STALE - warning is informational β€” the declared `buildSrc` version and - the sibling's `versionToPublish` simply disagree. Proceed - without re-running `discover`. - - `skipped-branch` β€” sibling is on a feature branch and was left - untouched. Use the local code as-is; proceed without re-running. - - stderr always carries the human-readable diagnostics; surface it - to the user, but do not parse it to drive control flow. -5. If the user declines, proceed without pulling. Do not ask again - for the same sibling in the same session unless the user revisits. - -### `update-sibling` exit codes - -Exit 0 is split into three outcomes by the **stdout token** β€” read -that, not the stderr text. - -| Code | stdout | Meaning | What you do | -|---|---|---|---| -| `0` | `pulled` | HEAD advanced to upstream tip. | Re-run `discover` so the STALE warning clears. | -| `0` | `up-to-date` | Already at upstream tip; nothing to do. | Proceed; surface the STALE warning to the user as informational. | -| `0` | `skipped-branch` | On a non-default branch; left untouched. | Use the local code as-is; proceed without re-running. | -| `1` | _(empty)_ | Sibling not on disk. | Report the error. | -| `2` | _(empty)_ | Not a git repository. | Report the error; do not retry. | -| `3` | _(empty)_ | Detached HEAD β€” refused. | Tell the user; do not retry. | -| `4` | _(empty)_ | Working tree dirty β€” refused. | Tell the user; do not retry. | -| `5` | _(empty)_ | No upstream tracking on default branch β€” refused. | Tell the user. | -| `6` | _(empty)_ | `git pull --ff-only` failed (divergence, network, etc.). | Surface the git error verbatim. | -| `64` | _(empty)_ | Usage error (no/too many arguments). | Fix the invocation; do not retry blindly. | - -Failure paths produce **empty stdout** so the agent can never misread -an error message as a result token. - -### "Don't ask me again" - -If the user says something like "stop offering" or "skip the prompt -this session", remember that for the rest of the conversation and do -not prompt on subsequent STALE warnings β€” just surface the warning -and move on. This is **per-session** state; do not write it to -auto-memory. - -## Anti-patterns - -Stop doing these β€” they are exactly what this skill exists to replace: - -- `find ~/.gradle/caches/modules-2/files-2.1/ -name '*-sources.jar'` -- `unzip -l ` to list classes -- `unzip -p path/in/jar` to read a file -- Any chain of `unzip` + `grep` against a Gradle-cache JAR - -If you find yourself wanting to do those, run `discover` instead. - -## Examples - -**Spine artifact, fresh sibling on disk:** - -```text -$ .agents/scripts/api-discovery/discover io.spine:spine-base -/Users//Projects/Spine/base-libraries/base -$ echo $? -0 -``` - -Tool calls then look like: - -- `Glob` pattern `**/*.kt`, path - `/Users//Projects/Spine/base-libraries/base`. -- `Grep` pattern `class Identifier`, path the same. - -**Spine artifact, stale sibling:** - -```text -$ .agents/scripts/api-discovery/discover io.spine.tools:validation-java -api-discovery: STALE: validation-java declared 2.0.0-SNAPSHOT.433 in Validation.kt but sibling publishes 2.0.0-SNAPSHOT.440 -api-discovery: sources at /Users//Projects/Spine/validation/java may differ from the published artifact -/Users//Projects/Spine/validation/java -``` - -Surface the `STALE` line, then offer to refresh β€” see *Refreshing a -stale sibling*. After the user agrees and the pull succeeds, re-run -`discover` and the warning clears. - -**Stale sibling, refresh on master:** - -```text -$ .agents/scripts/api-discovery/update-sibling /Users//Projects/Spine/validation -Updating abc1234..def5678 -Fast-forward - ... -api-discovery: /Users//Projects/Spine/validation pulled 'master': abc1234... -> def5678... -pulled -$ echo $? -0 -``` - -Stdout is `pulled` β€” re-run `discover` to clear the STALE warning. - -**Stale sibling, already at upstream tip:** - -```text -$ .agents/scripts/api-discovery/update-sibling /Users//Projects/Spine/validation -Already up to date. -api-discovery: /Users//Projects/Spine/validation already up-to-date on 'master' (def5678...) -up-to-date -$ echo $? -0 -``` - -Stdout is `up-to-date` β€” the sibling is fresh; the STALE warning -reflects a declared-version vs. `versionToPublish` discrepancy that -`git pull` cannot resolve. Surface it to the user as informational. - -**Stale sibling, feature branch (no-op):** - -```text -$ .agents/scripts/api-discovery/update-sibling /Users//Projects/Spine/validation -api-discovery: /Users//Projects/Spine/validation is on 'feature/new-rule' (not master/main); using local code as-is -skipped-branch -$ echo $? -0 -``` - -Stdout is `skipped-branch` β€” feature branch is intentional local -state. Use the code as-is. - -**Non-Spine artifact, first use (extraction):** - -```text -$ .agents/scripts/api-discovery/discover com.google.guava:guava:33.5.0-jre -api-discovery: extracted com.google.guava:guava:33.5.0-jre -> .../guava/33.5.0-jre -/Users//Projects/Spine/.agents/caches/api-discovery/sources/com.google.guava/guava/33.5.0-jre -``` - -Second call returns the same path with no stderr (already cached). - -**Unresolvable:** - -```text -$ .agents/scripts/api-discovery/discover io.spine:does-not-exist:9.9.9 -api-discovery: io.spine:does-not-exist:9.9.9 is not in the local Gradle cache -api-discovery: run './gradlew dependencies' (or rebuild) to fetch it, then retry -$ echo $? -1 -``` - -Report the failure verbatim; do not try `unzip` as a workaround. - -## Related - -- Implementation reference: `.agents/scripts/api-discovery/README.md`. -- Sibling refresh on STALE: `.agents/scripts/api-discovery/update-sibling`. -- Manual cache pruning: `.agents/scripts/api-discovery/clean-cache`. diff --git a/.agents/skills/bump-gradle/SKILL.md b/.agents/skills/bump-gradle/SKILL.md deleted file mode 100644 index 22f29578..00000000 --- a/.agents/skills/bump-gradle/SKILL.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -name: bump-gradle -description: > - Update the Gradle wrapper version used by this repository. Use when asked to - upgrade Gradle, bump the Gradle wrapper, move the project to the latest - Gradle release from the official release notes, run the Gradle build, and - commit Gradle wrapper and dependency report changes separately. ---- - -# Bump Gradle - -Use the official Gradle release notes as the source of truth for both the -latest version and the wrapper update command: - -https://docs.gradle.org/current/release-notes.html#upgrade-instructions - -Always check that page at task time. Do not rely on remembered Gradle versions. - -## Commit authorization - -This skill is authorized to run `git commit` **up to two times** per -invocation, under these constraints: - -1. **Gradle wrapper commit.** Stage only the Gradle wrapper files - (`gradle/wrapper/gradle-wrapper.properties`, - `gradle/wrapper/gradle-wrapper.jar`, `gradlew`, `gradlew.bat`, plus - files directly required by the wrapper update). Subject: - `` Bump Gradle -> `GRADLE_VERSION` `` with the actual version - substituted. Skip if no wrapper-owned file changed. - -2. **Dependency-report commit** (separate from the wrapper commit). Stage - only generated dependency-report files (`docs/dependencies/pom.xml`, - `docs/dependencies/dependencies.md`). Subject: - `Update dependency reports`. Skip if the build did not regenerate - those files. - -No `git push`, `git tag`, `git rebase`, `git commit --amend`, or any other -history-writing operation. Those require a separate authorization -(`.agents/safety-rules.md` β†’ *Commits and history-writing*). Do not create -empty commits, and do not bundle unrelated changes into either commit. - -## Checklist - -1. Work from the target repository root. - - Confirm `./gradlew` and `gradle/wrapper/gradle-wrapper.properties` exist - before changing anything. Inspect `git status --short` and preserve unrelated - user changes. If Gradle wrapper files are already modified, inspect the diff - and continue only when those edits are part of the same requested Gradle - bump; otherwise ask before overwriting or staging them. - -2. Read the latest Gradle version from the release notes. - - Open the Upgrade instructions section at the URL above. Use the version in - the release heading and the wrapper command shown there. They should agree; - if they do not, stop and report the mismatch. - -3. Run the wrapper update command. - - Substitute the version from the release notes: - - ```bash - ./gradlew wrapper --gradle-version=GRADLE_VERSION && ./gradlew wrapper - ``` - - For example, if the release notes say Gradle `9.5.1`, run: - - ```bash - ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew wrapper - ``` - -4. Run the build. - - ```bash - ./gradlew clean build - ``` - - If the wrapper update or build fails, do not commit partial changes. Report - the failing command and the relevant error output. - -5. Commit only Gradle-related files. - - Inspect `git status --short` and `git diff --name-only`. Stage only files - created or updated by the Gradle wrapper bump, normally: - - ```text - gradle/wrapper/gradle-wrapper.properties - gradle/wrapper/gradle-wrapper.jar - gradlew - gradlew.bat - ``` - - Include other Gradle-owned files only when they are directly required by the - wrapper update and are clearly part of the same change. Do not stage - dependency reports or unrelated build output in this commit. - - Commit with the exact subject, replacing `GRADLE_VERSION`: - - ```text - Bump Gradle -> `GRADLE_VERSION` - ``` - - Example: - - ```bash - git commit -m 'Bump Gradle -> `9.5.1`' - ``` - - If no Gradle-related files changed, do not create an empty commit; report - that the wrapper was already current after verification. - -6. Commit dependency reports separately when the build updates them. - - Stage only generated dependency report files. In repositories using this - config, the usual paths are: - - ```text - docs/dependencies/pom.xml - docs/dependencies/dependencies.md - ``` - - Include other changed files only when they are clearly generated dependency - reports from the build. Commit them separately with: - - ```text - Update dependency reports - ``` - -7. Verify the final branch state. - - Confirm the recent commit subjects and make sure no owned Gradle bump or - dependency report changes remain unstaged: - - ```bash - git log --format=%s -2 - git status --short - ``` - - Leave unrelated pre-existing user changes alone and mention them separately - in the final response. - -8. Ensure `version.gradle.kts` is bumped. - - Before this branch can be built or published locally, the project - version must be strictly greater than the version on the base ref. - Invoke `/version-bumped` β€” it is a no-op if a bump has already - happened earlier on the branch, and otherwise calls `/bump-version` - to perform the increment. diff --git a/.agents/skills/bump-gradle/agents/openai.yaml b/.agents/skills/bump-gradle/agents/openai.yaml deleted file mode 100644 index 6edf9787..00000000 --- a/.agents/skills/bump-gradle/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Bump Gradle" - short_description: "Update the Gradle wrapper safely" - default_prompt: "Use $bump-gradle to update this repository to the latest Gradle wrapper version from the official release notes, build, and split Gradle/report commits." diff --git a/.agents/skills/bump-version/SKILL.md b/.agents/skills/bump-version/SKILL.md deleted file mode 100644 index 3e1d3d65..00000000 --- a/.agents/skills/bump-version/SKILL.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -name: bump-version -description: > - Bump the project version in `version.gradle.kts` following the Spine SDK - versioning policy. Use when starting a new branch, before opening a PR, or - when CI rejects a branch for a missing/insufficient version increment. Covers - locating the published version value, choosing the increment, committing the - bump, rebuilding reports, and resolving version conflicts. ---- - -# Bump the project version - -The authoritative policy is [Spine SDK Versioning][version-policy]. In this -skill's target repository, CI runs the `Version Guard` workflow, which invokes -`checkVersionIncrement` through `IncrementGuard`. The task fails if the current -project version already exists in the Maven repository. It does not compare git -branches or inspect commit subjects; the checks below are agent-side guardrails. - -## Commit authorization - -This skill is authorized to run `git commit` **exactly once** per invocation, -under these constraints: - -- Stage only `version.gradle.kts`. Any other modified files are out of scope - for this skill's commit and must remain unstaged. -- Use the exact subject `` Bump version -> `` `` (see step 4 of the - Checklist) with the actual new version value substituted. -- No `git push`, `git tag`, `git rebase`, `git commit --amend`, or any other - history-writing operation. Those require a separate authorization - (`.agents/safety-rules.md` β†’ *Commits and history-writing*). - -If the bump cannot be performed cleanly (no diff to commit, conflicting -staged files, build failures preceding the commit), report and stop β€” do not -create the commit. - -## Checklist - -1. Work from the target repository root. - - Confirm `version.gradle.kts` exists before editing. If it is absent, stop and - report that this skill does not apply to the current checkout. - - Inspect `git status --short` before changing files. Preserve unrelated user - changes and stage only the version/report files this workflow owns. - -2. Locate `version.gradle.kts` and update the value that feeds - `versionToPublish`. - - The published version may be a literal: - - ```kotlin - val versionToPublish: String by extra("2.0.0-SNAPSHOT.182") - ``` - - Or it may come from another variable: - - ```kotlin - val compilerVersion: String by extra("2.0.0-SNAPSHOT.043") - val versionToPublish by extra(compilerVersion) - ``` - - In the second case, update the source value (`compilerVersion` here), not - only the `versionToPublish` alias. - -3. Choose the increment. - - For the normal snapshot-line PR, increment the trailing snapshot number by - one: `2.0.0-SNAPSHOT.182` -> `2.0.0-SNAPSHOT.183`. Preserve existing - zero-padding: `2.0.0-SNAPSHOT.009` -> `2.0.0-SNAPSHOT.010`. - - For a breaking snapshot-line PR, advance to the next multiple of 10 that is - strictly greater than the current value: `.187` -> `.190`, and `.180` -> - `.190`. - - For release-line work, follow the [policy][version-policy]: urgent fixes bump `PATCH`; - feature work or significant fixes bump `MINOR` and reset `PATCH` to `0`. - -4. Commit only the `version.gradle.kts` change with this subject: - - ```text - Bump version -> `2.0.0-SNAPSHOT.183` - ``` - - Use the actual new version in the subject. Do not include unrelated files in - this commit. - -5. Run the build to verify the bump and regenerate reports: - - ```bash - ./gradlew clean build - ``` - - Repos using this config commonly finalize `generatePom` and - `mergeAllLicenseReports` after `build`, which updates - `docs/dependencies/pom.xml` and `docs/dependencies/dependencies.md` when - those reports are configured. - -6. If `docs/dependencies/pom.xml` or `docs/dependencies/dependencies.md` changed, - commit those generated files separately: - - ```text - Update dependency reports - ``` - - If the PR has the `License Reports` workflow, make sure the branch modifies - `docs/dependencies/pom.xml` and `docs/dependencies/dependencies.md`. - -7. Validate the branch state. - - ```bash - BASE=master - git fetch --quiet origin "$BASE" - RANGE="$(git merge-base HEAD origin/$BASE)..HEAD" - git log --format=%s "$RANGE" | grep '^Bump version ->' - git diff --name-only "$RANGE" -- version.gradle.kts | grep '^version.gradle.kts$' - ``` - - Use the actual merge target for `BASE` when it is not `master`. - Also confirm `git status --short` has no uncommitted changes created by the - version bump or report regeneration. - -## Conflict Rule - -When merging a base branch into a feature branch: - -- If the base branch version is lower, keep the feature branch version. -- If the base branch version is greater than or equal to the feature branch - version, set the feature branch version to `base + 1`, or apply the breaking - change rounding rule. - -Do not require a completely clean worktree if unrelated user changes are -present. Instead, make sure no uncommitted changes were created by the version -bump or report regeneration. - -[version-policy]: https://github.com/SpineEventEngine/documentation/wiki/Versioning diff --git a/.agents/skills/bump-version/agents/openai.yaml b/.agents/skills/bump-version/agents/openai.yaml deleted file mode 100644 index 12f6e4f9..00000000 --- a/.agents/skills/bump-version/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Bump Version" - short_description: "Bump Spine project versions safely" - default_prompt: "Use $bump-version to bump the project version in version.gradle.kts, commit the version change, rebuild dependency reports, and verify the branch." diff --git a/.agents/skills/check-links/SKILL.md b/.agents/skills/check-links/SKILL.md deleted file mode 100644 index a4c61a0c..00000000 --- a/.agents/skills/check-links/SKILL.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -name: check-links -description: > - Validate the Hugo documentation site under `docs/` or `site/` for broken - links. Builds the site, starts the Hugo server locally, runs Lychee against - the rendered HTML using the repo's `lychee.toml`, and reports any broken URLs - grouped by source Markdown page. Use locally before pushing changes that - touch `docs/**` or `site/**`, when CI's `Check Links` job fails, or whenever - the user asks to "check doc links". Read-only with respect to the project - sources. Does **not** cover Javadoc/KDoc (out of scope for this skill). ---- - -# Check links in the Hugo docs (repo-specific) - -You are the documentation link checker for this Spine Event Engine project. -You build the site under `docs/` or `site/` (auto-detected; see step 0), serve -it locally on port `1414`, run Lychee against the rendered HTML, and report -broken URLs. You mirror what the `.github/workflows/check-links.yml` workflow -does in CI: same Hugo version, same Lychee version, same Hugo environment -(`development`), and the same `lychee.toml`. Two deliberate differences remain: -the skill serves on port `1414` (CI uses `1313`) to avoid clashing with a -developer's local `hugo server`, and the skill writes a local sentinel that CI -does not. Both differences are harmless because `--base-url` is rewritten to -match the local port and the sentinel is consumed only by the local `pre-pr` -skill. - -### Pinned versions - -`.github/workflows/check-links.yml` is the **single source of truth** for the -Hugo and Lychee pins. This file does not duplicate the current values -because duplicates inevitably drift; see the workflow's `env:` block for -the canonical `HUGO_VERSION` and `LYCHEE_VERSION_TAG`. The auto-download -step (Β§2) reads `LYCHEE_VERSION_TAG` out of the workflow at runtime, so a -workflow bump propagates automatically. Hugo is not auto-installed; the -skill uses whichever `hugo` is on `$PATH` and only warns (does not block) -if the installed version is older than the workflow's `HUGO_VERSION` β€” -Hugo's HTML output is stable enough across minor versions that a small -skew does not invalidate link-check results. - -The authoritative shared config is `lychee.toml` at the repo root. Do not -fork its exclude list β€” fix the source link or, if the failing URL is a known -flaky external endpoint, add it to `lychee.toml` once (the change applies to -both the skill and CI). - -## When to run - -- Any change touches `docs/**` or `site/**` (including reference links, - `embed-code` blocks, sidenav YAML files, content under `/content/`). -- A change touches `lychee.toml` itself. -- CI reported broken links and you want a fast local repro. -- The user asks to "check the doc links" or invokes `/check-links`. - -If none of the above is true, decline with a one-line note rather than -running the (~30 s) build+check. - -## Tooling - -The skill needs four binaries: - -| Tool | Purpose | Install hint | -|--------|------------------------------------------|-------------------------------| -| Hugo | Build and serve the site | `brew install hugo` (extended)| -| Node | Hugo theme dependencies (`npm ci`) | `brew install node` | -| npm | Same | bundled with Node | -| Lychee | Link checker | `brew install lychee` | - -For **Lychee**, prefer a pre-installed binary on `$PATH`. If none is found, -download the pinned release (see `LYCHEE_VERSION_TAG` in -`.github/workflows/check-links.yml` β€” the dynamic-read pattern in step 2 below -keeps this version in lock-step with CI) into -`.agents/skills/check-links/.cache/lychee/` and use that path. The pinned -version matches what the CI workflow uses, so behavior is identical. - -`.agents/skills/check-links/.cache/` is git-ignored (see `.gitignore`). - -## Procedure - -Execute the steps in order. On the first failure, stop, write a `FAIL` -sentinel (step 8), and report the failure with the next action. - -### 0. Detect site root and work directory - -Before any other step, determine `SITE_DIR` (the Hugo site root) and `WORK_DIR` -(the directory where `npm ci` / `hugo` commands run β€” mirrors `.github/workflows/check-links.yml`): - -```bash -SITE_DIR="" -for dir in docs site; do - for cfg in hugo.toml hugo.yaml \ - config/hugo.toml config/hugo.yaml \ - config/_default/hugo.toml config/_default/hugo.yaml; do - if [ -f "$dir/$cfg" ]; then - SITE_DIR="$dir" - break 2 - fi - done -done -if [ -z "$SITE_DIR" ]; then - echo "ERROR: No Hugo config found under docs/ or site/." >&2 - exit 1 -fi - -if [ -f "${SITE_DIR}/_preview/package-lock.json" ]; then - WORK_DIR="${SITE_DIR}/_preview" -elif [ -f "${SITE_DIR}/package-lock.json" ]; then - WORK_DIR="${SITE_DIR}" -else - echo "ERROR: No package-lock.json found under ${SITE_DIR}/_preview/ or ${SITE_DIR}/." >&2 - exit 1 -fi -``` - -Use `$SITE_DIR` for content paths and `$WORK_DIR` for build/serve operations in the steps below. - -### 1. Scope check - -Run `git diff ...HEAD --name-only` (default `` = `master` unless -the user provides another). If the change set has **no** files under -`$SITE_DIR/**` and no changes to `lychee.toml`, and the user did not -explicitly ask, decline and exit cleanly. - -### 2. Preflight binaries - -- `hugo version` β†’ must succeed; capture the version. If missing, stop with - Must-fix: "Install Hugo extended (`brew install hugo`)." If installed but - older than the workflow's `HUGO_VERSION` (parse with - `grep -E '^[[:space:]]+HUGO_VERSION:' .github/workflows/check-links.yml | sed -E 's/.*: *"?([^"]+)"?$/\1/'`), warn but - continue. -- `node -v` and `npm -v` β†’ must succeed. If missing, stop with Must-fix: - "Install Node (`brew install node`) at the major version pinned by - `node-version:` in `.github/workflows/check-links.yml`." -- `lychee --version` β†’ if it succeeds, record the path and version. -- If `lychee` is missing: - 1. Read the canonical pin from the workflow file so the skill cannot drift - from CI: - ```bash - LYCHEE_VERSION_TAG=$( - grep -E '^[[:space:]]+LYCHEE_VERSION_TAG:' .github/workflows/check-links.yml \ - | sed -E 's/.*: *"?([^"]+)"?$/\1/' - ) - ``` - Expected shape: `lychee-vX.Y.Z` (the leading `lychee-` is part of the - upstream release tag, not a typo). - 2. Determine platform via `uname -s` / `uname -m`. Map to the matching - Lychee asset (recent releases β€” `v0.24.2` and later β€” drop the - version from the asset filename): - - `Darwin` + `arm64` β†’ `lychee-aarch64-apple-darwin.tar.gz` - - `Darwin` + `x86_64` β†’ `lychee-x86_64-apple-darwin.tar.gz` - - `Linux` + `x86_64` β†’ `lychee-x86_64-unknown-linux-gnu.tar.gz` - - `Linux` + `aarch64` β†’ `lychee-aarch64-unknown-linux-gnu.tar.gz` - - any other combination (e.g. Windows, FreeBSD, 32-bit) β†’ stop with - Must-fix: "Unsupported platform for Lychee auto-download β€” install - Lychee manually (`brew install lychee` / `cargo install lychee`) - and rerun." - 3. Ensure the cache directory exists *before* the download β€” - `mkdir -p .agents/skills/check-links/.cache/lychee/` β€” - because the path is git-ignored and absent on a fresh clone, - and `tar -xzf … -C

` will fail with "no such file or - directory" if the target does not exist yet. This mirrors the - `mkdir -p lychee` that `check-links.yml` does before its own - extract step. - 4. Download from - `https://github.com/lycheeverse/lychee/releases/download/${LYCHEE_VERSION_TAG}/` - into `.agents/skills/check-links/.cache/lychee/` and extract - with `tar -xzf --strip-components=1 -C .agents/skills/check-links/.cache/lychee/` - so the binary lands at - `.agents/skills/check-links/.cache/lychee/lychee`. - 5. Use `.agents/skills/check-links/.cache/lychee/lychee` for the rest of this run. - 6. Print a one-line note: "Using auto-downloaded Lychee. For faster runs, - install with `brew install lychee`." - -### 3. Install Hugo deps - -Run `( cd ${WORK_DIR} && npm ci )`. We deliberately use `npm ci` -(matching the CI workflow's `Install Dependencies` step in `check-links.yml`) -rather than `npm install`: - -- `npm ci` installs exactly the versions pinned by `package-lock.json`; - `npm install` is allowed to update the lockfile and may resolve to - different transitive versions than CI, which defeats the "render - identical HTML to CI" goal. -- If `package.json` and `package-lock.json` drift out of sync, `npm ci` - fails fast with a clear error rather than silently healing the - lockfile β€” a divergence we want to surface, not paper over. - -### 4. Build the site - -Run `( cd ${WORK_DIR} && hugo -e development )`. -This emits `${WORK_DIR}/public/**/*.html`. The `-e development` flag matches -what CI uses in `check-links.yml` so the two builds render identical HTML. -(The helper `${SITE_DIR}/_script/hugo-build` exists for interactive use but -defaults to `production`; we invoke `hugo` directly to keep the env in -lock-step with CI.) - -### 5. Start the Hugo server in the background - -The server must survive across multiple `Bash` tool calls (steps 5 β†’ 6 β†’ 8 -typically run in separate shells), so we rely on `nohup` alone β€” a `trap … -EXIT` would fire when *this* shell exits and kill the server before Lychee -can query it. Teardown happens explicitly in step 8. - -Before launching, kill any leftover server from a previous crashed run so a -stale process does not hold port `1414`: - -```bash -pkill -F /tmp/check-links.hugo.pid 2>/dev/null || true -rm -f /tmp/check-links.hugo.pid - -( cd ${WORK_DIR} && nohup hugo server --environment development --port 1414 \ - > /tmp/check-links.hugo.out 2>&1 & echo $! > /tmp/check-links.hugo.pid ) -sleep 5 - -# Verify the captured PID is alive before relying on it. `$!` for -# `nohup foo &` is reliable on bash but not portable across shells; the -# pgrep check turns a silent "Lychee fetches an empty port" failure into -# a clear error. -if ! pgrep -F /tmp/check-links.hugo.pid > /dev/null 2>&1; then - echo "ERROR: Hugo server failed to start. Tail of log:" >&2 - tail -20 /tmp/check-links.hugo.out >&2 || true - exit 1 -fi -``` - -Port `1414` is chosen to avoid clashing with a developer's local `hugo server` -(default `1313`). The `--environment development` flag matches CI's build env. - -### 6. Run Lychee - -```bash - --config lychee.toml --timeout 60 \ - --base-url http://localhost:1414/ \ - "${WORK_DIR}/public/**/*.html" -``` - -Capture exit code. Any non-zero exit means at least one broken link. - -### 7. Report - -Group the broken URLs from Lychee's output by source page. To reverse-map -an HTML path to its Markdown source: - -`${WORK_DIR}/public/docs/
//index.html` -↔ `${SITE_DIR}/content/docs/
/.md` (or `/_index.md`). - -Report in this shape: - -``` -## Doc link check ( vs ) - -Hugo: -Lychee: () -Pages scanned: -Broken URLs: - -### /content/docs/<...>/.md -- β€” -- β€” ... - -### /content/docs/<...>/.md -- ... -``` - -If `K == 0`, report a single line: "All links OK." - -### 8. Tear down and sentinel - -- Kill the Hugo server (and clean up its pid file): - - ```bash - pkill -F /tmp/check-links.hugo.pid 2>/dev/null || true - rm -f /tmp/check-links.hugo.pid /tmp/check-links.hugo.out - ``` - - Run this even if Lychee failed β€” leaving a server on port `1414` would - poison the next invocation. -- Write `.git/check-links.ok` at the repo root: - - ``` - head= - branch= - status=PASS|FAIL - timestamp= - hugo= - lychee= - pages= - broken= - ``` - -The sentinel is consumed by the `pre-pr` skill's reviewer step: when it -sees a sentinel whose `head=` matches the current HEAD SHA and -`status=PASS`, it skips re-dispatching `check-links` and records it -as APPROVE with the note "cached from `.git/check-links.ok`". Any -HEAD advance (commit, amend, rebase) invalidates the cache automatically. - -## Notes - -- This skill does **not** modify tracked sources. It does, however, write - several git-ignored build artifacts during a run β€” listed here so a future - reader does not mistake them for unrelated side-effects: - - `.agents/skills/check-links/.cache/lychee/` β€” auto-downloaded - Lychee binary, when the system Lychee was unavailable. - - `${WORK_DIR}/node_modules/` β€” installed by `npm ci` in step 3. - - `${WORK_DIR}/public/` β€” Hugo's rendered HTML (the corpus Lychee scans). - - `${WORK_DIR}/resources/` β€” Hugo's asset-pipeline cache. - - `.lycheecache` at the repo root β€” Lychee's per-URL result cache - (honoured for `max_cache_age = "3d"` per `lychee.toml`). - - `/tmp/check-links.hugo.{pid,out}` β€” server PID file and log, both - removed in step 8's teardown. - - Every path above is matched by an existing `.gitignore` entry; none is - committed. -- The `lychee.toml` exclude list is the single source of truth for flaky - external endpoints. If a real link must be excluded, add it there and - explain why in a comment so CI and local runs stay in sync. -- The skill assumes the docs build succeeds. A Hugo build error is treated - the same as a link failure β€” surface it and stop. -- The `include_verbatim = false` setting in `lychee.toml` skips links inside - code blocks. That is intentional today; flip it on if you specifically need - to validate examples. - -## Related skills - -- `review-docs` β€” prose, KDoc/Javadoc, and Markdown style review. Runs in - parallel with `check-links` when invoked by `pre-pr`. -- `pre-pr` β€” composes the above and gates `gh pr create`. diff --git a/.agents/skills/dependency-audit/SKILL.md b/.agents/skills/dependency-audit/SKILL.md deleted file mode 100644 index 010c16bc..00000000 --- a/.agents/skills/dependency-audit/SKILL.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -name: dependency-audit -description: > - Audit changes to dependency declarations under - `buildSrc/src/main/kotlin/io/spine/dependency/` β€” catches accidental - version downgrades, BOM mismatches, missing deprecation markers when - artifacts are renamed or removed, copyright drift, and convention drift. - Use whenever a diff touches that directory, or when asked to "audit - this dependency bump". Read-only; does not run builds. ---- - -# Dependency audit (repo-specific) - -You are the dependency auditor for a Spine Event Engine repo. All managed -dependencies live under: - - buildSrc/src/main/kotlin/io/spine/dependency/ - -organized by sub-package: - -- `lib/` β€” third-party runtime libraries (Kotlin, Guava, Protobuf, gRPC, …). -- `local/` β€” Spine SDK artifacts (Base, CoreJvm, ModelCompiler, …). -- `test/` β€” testing libraries (JUnit, Kotest, AssertK, Truth, Jacoco, Kover). -- `build/` β€” static-analysis and build-time tools (Dokka, ErrorProne, Pmd, - CheckStyle, KSP, …). -- `kotlinx/` β€” Kotlin-ecosystem libraries (Coroutines, Serialization, - DateTime, AtomicFu). -- `boms/` β€” BOM declarations. - -Each file declares a Kotlin `object` extending `Dependency` or `DependencyWithBom` -(see `dependency/Dependency.kt`). The shape is: - - object Kotest { - const val version = "6.1.11" - const val group = "io.kotest" - const val assertions = "$group:kotest-assertions-core:$version" - // … - } - -## How to run an audit - -1. **Fetch the full diff once.** Default base is `origin/master`: - `git diff origin/master...HEAD -- 'buildSrc/src/main/kotlin/io/spine/dependency/**'` - (use `--staged` if the user is mid-commit, or a different base only if - the user names one). The unified diff already contains the old and new - lines you need for version-sanity and BOM checks β€” do not call `--stat` - first and then re-read each file. If the diff is empty, ask the user - which files to audit. - -2. **Lean on the diff; `Read` on demand.** Version, BOM, copyright, and - deprecation deltas are all visible in the unified diff. Only `Read` a - file when (a) it is newly added, or (b) a hunk references a - `version`/`group` constant defined outside the hunk and you need - surrounding context. **Budget:** if more than 5 files changed, do not - `Read` individual files β€” work from the diff and use targeted `Grep` - for cross-cutting questions. - -3. **Batch independent work into one turn.** Issue the version-sanity (A), - convention-drift (D), and cross-cutting (E) tool calls *in parallel* - within a single response. Collect every finding and emit the report - once β€” **do not stop at the first failure**. - -4. **Batch greps.** For deprecation/caller checks (C) and snapshot-pin - checks (A), build one ripgrep over the union of symbols instead of one - command per symbol. Examples: - - `rg -n '\b(name1|name2|name3)\b' --type kt` to find callers of any - removed `const val`. - - `rg -L 'Copyright \(c\) 2026' ` to flag every stale - header in one call. - - `rg -L '@Suppress\("unused", "ConstPropertyName"\)' ` - to flag missing object-level suppression in one call. - - `rg -n '(lib1:oldv1|lib2:oldv2)' --type kt --type gradle` β€” one - alternation across libraries, not one command per library. - -5. **Fast path for pure version bumps.** If every hunk only modifies an - existing `version` (or `bom`) string literal β€” no added/removed - `const val`, no new files, no renames β€” run only Checks A and D. - Skip B, C, and E entirely. This is the dominant `/dependency-update` - shape; do not waste tool calls re-validating naming or deprecation - discipline when nothing structural changed. - -## Checks - -### A. Version sanity -- **No silent downgrade.** Compare the old and new `version` value as semver. - A decrease (`2.0.0 -> 1.9.0`) or a snapshot regression (`-SNAPSHOT.183` -> - `.182`) is a Must-fix unless the commit message explicitly justifies it. -- **Snapshot vs. release consistency.** If `version` switches from a release - (`2.0.0`) to a snapshot (`2.0.1-SNAPSHOT.001`), confirm the consuming code - isn't pinned to the release elsewhere. Use the batched ripgrep recipe - in step 4 β€” one alternation across all switched libraries, not one - command per library. -- **BOM ↔ component agreement.** For objects extending `DependencyWithBom`, - check that `bom` references the same version as `version` (e.g. Kotlin's - `kotlin-bom:$runtimeVersion`). - -### B. Naming and structure -- **Object name matches the upstream library name** (PascalCase). New files - must follow the convention of neighbors (e.g. `lib/Foo.kt` declares - `object Foo`). -- **No type names in property names** (`fooList`, `barObject`) β€” this is in - `.agents/coding-guidelines.md`. -- **Module constants use `"$group::$version"`**, not hardcoded - Maven coordinates. Catch copy-paste like `"io.kotest:kotest-assertions-core:6.1.11"`. - -### C. Deprecation discipline -When an artifact is **renamed or removed**: -- The old `const val` must stay with `@Deprecated("…", ReplaceWith("…"))` - or `@Deprecated("…")` (see `Kotest.frameworkApi` and `Kotest.datatest` for - the established style). -- If the diff deletes one or more `const val`s outright, confirm no caller - is left behind. Use the batched ripgrep recipe in step 4 β€” one - alternation over all removed symbol names, not one `git grep` per - name. If any caller survives, this is a Must-fix. - -### D. Convention drift -- **Copyright header year.** Every changed file should have a current-year - copyright line. If a file was edited but its copyright says `2024`, flag it - (the user can run `/update-copyright` to fix). -- **GitHub URL comment.** New `lib/` and `kotlinx/` files conventionally - start with `// https://github.com//` above the object. - Recommend it if missing. -- **`@Suppress("unused", "ConstPropertyName")` on the object.** This is the - established style for constant-heavy declarations. - -### E. Cross-cutting checks -- **`local/` deps don't leak.** Spine SDK artifacts in `local/` should not be - declared in `lib/` or `test/` (and vice versa). -- **No mixing Groovy and Kotlin DSL.** All Gradle code in `buildSrc/` must be - `.kt` or `.gradle.kts`. Catch any `.gradle` file slipping in. - -## Output format - -Three sections, in this order: - -- **Must fix** β€” version downgrades, missing deprecation markers on removed - symbols, broken callers, BOM/version mismatches. -- **Should fix** β€” convention drift, missing deprecation `ReplaceWith`, - missing copyright update, missing URL comment, naming oddities. -- **Nits** β€” formatting, ordering, doc-comment polish. - -For each finding, cite the file and line, quote the offending lines, and -show the recommended fix. - -End with a one-line verdict: `APPROVE`, `APPROVE WITH CHANGES`, or -`REQUEST CHANGES`. diff --git a/.agents/skills/dependency-update/SKILL.md b/.agents/skills/dependency-update/SKILL.md deleted file mode 100644 index 7e70bc12..00000000 --- a/.agents/skills/dependency-update/SKILL.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -name: dependency-update -description: > - Walk every dependency declaration under - `buildSrc/src/main/kotlin/io/spine/dependency/`, discover the latest accepted - version of each artifact from the URL hinted in its file (or from Maven - metadata if no URL is present), and update the `version` constant in place. - External dependency scopes accept only released versions; the `local` scope - also accepts snapshots and pre-releases published from sibling Spine repos. - Use when asked to refresh dependency versions, bump libraries, run a - dependency audit, or "see what's stale". ---- - -# Update dependencies - -## Goal - -Bring every dependency object under -`buildSrc/src/main/kotlin/io/spine/dependency/` to its latest accepted version. -For every scope except `local/`, that means the latest **released** version: -snapshots, release candidates, milestones, alpha/beta, EAP, and `-dev` builds -are **excluded**. - -`local/` is the deliberate exception. It holds Spine SDK dependencies published -from sibling Spine repositories, and it may move to newer snapshots or -pre-releases such as `2.0.0-SNAPSHOT.388` or `2.1.0-RC1`. - -The authoritative version source for each artifact is the web page already -referenced in its file. When the file has no URL, use the Maven metadata -fallback described below. For non-`local/` artifacts, a discovered Maven -Central URL is **added back to the file** as a line comment so the next run has -a hint. - -## Inputs - -- No arguments β†’ scan all of `buildSrc/src/main/kotlin/io/spine/dependency/`. -- One or more paths or sub-package names (`lib`, `local`, `test`, `build`, - `kotlinx`, `boms`) β†’ restrict the scan to those. -- `--dry-run` β†’ discover and report, but do not edit. - -## Pre-flight - -1. Run `git status --short`. If the worktree is dirty in files this skill will - touch, stop and ask the user. Otherwise preserve unrelated changes. -2. Confirm `buildSrc/src/main/kotlin/io/spine/dependency/` exists. -3. Note the current branch β€” every change this skill makes is a candidate for - a single `chore(deps): refresh external versions` commit at the end; the - skill itself does NOT commit. The user decides. - -## Per-file workflow - -For each `*.kt` file in scope: - -### 1. Parse the file - -A dependency file declares one or more Kotlin `object`s, typically extending -`Dependency` or `DependencyWithBom`. The shape is: - - object Kotest { - const val version = "6.1.11" - const val group = "io.kotest" - const val assertions = "$group:kotest-assertions-core:$version" - // … - } - -Extract: - -- `objectName` β€” the outer `object` identifier. -- `version` β€” the literal version string. Some files have **multiple** version - constants (`runtimeVersion`, `embeddedVersion`, `annotationsVersion`); treat - each separately. The one driving the artifact is typically `override val - version = …` or the `const val version = …` declared at the top. -- `group` β€” the Maven group. -- `module` artifact names β€” each `const val foo = "$group:foo:$version"` line - contributes one artifact name. Use the first one to query Maven Central if - needed for non-`local/` artifacts, or Spine SDK Maven repositories for - `local/` artifacts. -- `versionUrl` β€” a URL hint. Look in this order: - 1. Line comments above the object: `^//\s*(https?://\S+)`. - 2. KDoc `@see …` inside the object's KDoc. - 3. Plain `@see https?://…` inside the KDoc. - 4. If none: leave `versionUrl` empty and use the Maven metadata fallback - below. - -Skip files that contain only abstract base classes or helpers (`Dependency.kt`, -`DependencyWithBom.kt`, `BomsPlugin.kt`, anything without a concrete artifact -declaration). - -### 2. Find the latest accepted version - -The discovery rule depends on the URL shape. For files under -`dependency/local/`, check the Spine SDK Maven metadata before GitHub, even -when the file has a GitHub URL; snapshots are usually visible in Maven -metadata, not in GitHub's latest-release redirect. - -**A. GitHub repository URL** (`https://github.com//`): - -- Outside `local/`, resolve - `https://github.com///releases/latest`. GitHub redirects to the - latest non-prerelease tag. Read the redirected location or the rendered HTML - to extract the tag. -- In `local/`, do **not** rely on `/releases/latest`, because it hides - pre-releases. Use GitHub releases and tags only after checking Spine SDK - Maven metadata. When you do use GitHub, include pre-release entries and keep - version-like tags that match the artifact. -- Tags often have a `v` prefix. Strip it. -- If the repo publishes per-component tags (e.g. - `kotlinx-coroutines-1.10.2`), prefer the tag whose name matches the - artifact's module identifier. Otherwise take the topmost release. - -**B. Maven Central artifact URL** -(`https://search.maven.org/artifact//` or -`https://repo1.maven.org/maven2///`): - -- Hit Maven Central's REST API: - `https://search.maven.org/solrsearch/select?q=g:+AND+a:&rows=20&core=gav` -- Outside `local/`, filter the `response.docs[].v` values by the pre-release - rule (below). -- In `local/`, keep snapshots and pre-releases in the candidate list. -- Take the highest by semver comparison. - -**C. Spine SDK Maven repositories for `local/` artifacts**: - -- For files under `dependency/local/`, query Maven metadata in the current - Spine SDK Artifact Registry repositories before falling back elsewhere: - - `https://europe-maven.pkg.dev/spine-event-engine/releases` - - `https://europe-maven.pkg.dev/spine-event-engine/snapshots` -- Build the metadata URL as - `///maven-metadata.xml`, where `groupPath` is the - Maven group after first resolving symbolic aliases used in dependency files - (for example, `Spine.group` -> `io.spine` and `Spine.toolsGroup` -> - `io.spine.tools`) and then replacing dots with slashes. -- Read `...` entries. For `local/`, do not - reject `SNAPSHOT`, RC, milestone, alpha, beta, EAP, pre, or dev versions. -- If both release and snapshot repositories have candidates, compare all of - them together and take the highest version. - -**D. Project homepage** (e.g. `https://kotest.io/`, `https://junit.org/`, -`https://www.detekt.dev/`): - -- Try to find a "latest release" or "download" link on the page. If the page - is a thin landing page with no usable version data, fall through to E. - -**E. No URL or unusable URL β€” Maven metadata fallback**: - -- Outside `local/`, query Maven Central as in B using the file's `group` and - the first module artifact name (the part after `$group:`). -- In `local/`, query the Spine SDK Maven metadata first. Use Maven Central only - if the artifact is absent from those repositories. -- If a non-`local/` Maven Central fallback query returns results, **also insert - a line comment** - `// https://search.maven.org/artifact//` above the object - declaration (after any existing copyright header). This back-fills the URL - hint for next time. Match the existing comment style (one line, no trailing - punctuation). -- If all fallback queries have no result, leave the file untouched and add it - to the `Manual review` section of the final report. - -### 3. Filter pre-releases outside `local/` - -Apply this filter only to files outside `dependency/local/`. - -For `local/` files, snapshots and pre-releases are accepted candidates. Do not -put them in `Filtered pre-releases`; put them in the `local/` confirmation -section of the final report instead. - -Reject any version string matching, case-insensitively: - - -SNAPSHOT$ - -RC[\d\-.]*$ e.g. -RC1, -RC.2 - -M\d+$ e.g. -M3 - -alpha[\d\-.]*$ - -beta[\d\-.]*$ - -EAP[\d\-.]*$ - -pre[\d\-.]*$ - -dev[\d\-.]*$ - \.Beta\d*$ Spring-style trailing tokens - \.Alpha\d*$ - \.RC\d*$ - \.M\d+$ - -Apply the regex to the **suffix after the numeric version**. The version -`2.0.0-SNAPSHOT.182` is a snapshot and must be rejected as a target outside -`local/`, but it is valid for `local/` dependency objects. This skill only -edits dependency files, never `version.gradle.kts` (that belongs to the -`bump-version` skill). - -### 4. Compare versions - -Use semver comparison: - -- Split on `.` and `-`. -- Numeric segments compare numerically; non-numeric segments compare - lexicographically. -- A version without any pre-release suffix is greater than one with the same - numeric prefix but a pre-release suffix. - -Only update when `latest > current`. Equal or lower β†’ no change. - -### 5. Apply the edit - -- Replace the `version` literal with the new value. Use a precise replacement - anchored on the full line (`const val version = ""` β†’ - `const val version = ""`). Do not blindly replace the version string, - because the same string can appear in module URLs constructed via - interpolation (`"$group:…:$version"`) β€” those will pick up the new value - automatically. -- If the file uses a renamed version constant (`runtimeVersion`, - `compilerVersion`, etc.) that feeds `override val version = compilerVersion`, - update the **source** constant, not the alias. -- For `DependencyWithBom` objects, verify the `bom` line still resolves - correctly. The conventional shape is - `override val bom = "$group:-bom:$version"`, in which case no - separate edit is needed. If the BOM version is hard-coded, update it too. -- Preserve indentation, comment style, and surrounding blank lines exactly. - -### 6. Watch for `local/` artifacts - -`local/` holds Spine SDK dependencies (Base, CoreJvm, ModelCompiler, …) that -are published from sibling Spine repos. This scope accepts snapshots and -pre-releases because these artifacts often advance through internal snapshot -builds before a stable SDK release. - -Still **flag every `local/` update in the report**, and note whether the target -is a release, snapshot, or pre-release. The user can then decide whether to -bump the SDK in lockstep with the rest of the project. Spine SDK artifacts -often need to move together; one-off bumps can cause runtime ABI mismatches. - -## Report - -When the run completes, emit a Markdown report with these sections: - -- **Updated** β€” table of `file | objectName | old β†’ new | source URL`. -- **Already current** β€” file/object pairs whose version was already the - newest accepted version. -- **Skipped (no URL, metadata empty)** β€” manual review needed. -- **Filtered pre-releases** β€” newer versions found but rejected because they - were RC/SNAPSHOT/alpha/etc. Applies only outside `local/`. -- **`local/` bumps to confirm** β€” every `local/` change called out separately, - including snapshot and pre-release targets. - -End with the suggested next steps: - -1. Review the diff (`git diff buildSrc/src/main/kotlin/io/spine/dependency/`). -2. Invoke `/version-bumped`. Every feature branch must advance - `version.gradle.kts` strictly above the base before any - `./gradlew build` (which may transitively `publishToMavenLocal`). The - skill is a no-op when a bump already happened earlier on the branch - and otherwise calls `/bump-version` to perform the increment. -3. Run `./gradlew build` (or `./gradlew clean build` if `.proto` files - participate). -4. Commit. Match the shape of the actual change: - - Single `local/` bump (most common): `` Bump Spine Base -> `2.0.0-SNAPSHOT.190` `` - - Coordinated external set: `Bump Protobuf and gRPC` (one commit; - mention both). - - Bulk external refresh (rare): `Refresh external dependencies`. - -## Safety - -- Do not commit. Do not push. Editing files is the limit of this skill's - authority. -- Never edit `version.gradle.kts` β€” that's the `bump-version` skill's - responsibility. -- Never auto-resolve a Maven Central query that returns multiple matching - artifacts with different groups (e.g. a library that exists under both - `io.netty` and `io.netty.incubator`). Ask the user. -- If a discovered "latest" version is more than one **major** ahead of the - current value (e.g. `1.x` β†’ `3.x`), flag it as a major bump in the report - and apply the edit only if the user confirms, or only when running - non-interactively with `--include-majors`. Major bumps frequently break - ABI. - -## Failure modes to expect - -- **GitHub rate limit** on the unauthenticated REST API. The `/releases/latest` - HTML page does not require auth and is the preferred fallback. -- **Per-component tags** in a monorepo. Match by artifact name, don't take the - topmost tag blindly. -- **Repositories that publish to JCenter only** β€” JCenter is sunset; if Maven - Central is empty, the dependency may need migration. Flag it. -- **Vendor-specific version schemes** (e.g. dates: `2025.10.01`) β€” the - semver comparator above will still order these correctly; just don't - mis-classify them as pre-releases. diff --git a/.agents/skills/dependency-update/agents/openai.yaml b/.agents/skills/dependency-update/agents/openai.yaml deleted file mode 100644 index a61198d3..00000000 --- a/.agents/skills/dependency-update/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Dependency Update" - short_description: "Refresh dependency versions, allowing snapshots only for local Spine SDK artifacts" - default_prompt: "Use $dependency-update to walk every dependency object under buildSrc/src/main/kotlin/io/spine/dependency/, find the latest accepted version for each, and update the version constants in place. External scopes use released non-snapshot versions only; dependency/local/ may use snapshots and pre-releases from sibling Spine repos. Use the URL referenced in each file as the source of truth; fall back to Maven metadata and back-fill missing hints when useful." diff --git a/.agents/skills/java-to-kotlin/SKILL.md b/.agents/skills/java-to-kotlin/SKILL.md deleted file mode 100644 index b9835f8f..00000000 --- a/.agents/skills/java-to-kotlin/SKILL.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -name: java-to-kotlin -description: > - Convert Java code to Kotlin, including Java API comments from Javadoc to KDoc. - Use when asked to migrate Java files, classes, methods, nullability semantics, - or common Java patterns into idiomatic Kotlin while preserving behavior. ---- - -# πŸͺ„ Converting Java code to Kotlin - -* Java code API comments are Javadoc format. -* Kotlin code API comments are in KDoc format. - -## Javadoc to KDoc conversion - -* The wording of original Javadoc comments must be preserved. - -## Treating nullability - -* Use nullable Kotlin type only if the type in Java is annotated as `@Nullable`. - -## Efficient Conversion Workflow - -* First, analyze the entire Java file structure before beginning conversion to understand dependencies and class relationships. -* Convert Java code to Kotlin systematically: imports first, followed by class definitions, methods, and finally expressions. -* Preserve all existing functionality and behavior during conversion. -* Maintain original code structure and organization to ensure readability. - -## Common Java to Kotlin Patterns - -* Convert Java getters/setters to Kotlin properties with appropriate visibility modifiers. -* Transform Java static methods to companion object functions or top-level functions as appropriate. -* Replace Java anonymous classes with Kotlin lambda expressions when possible. -* Convert Java interfaces with default methods to Kotlin interfaces with implementations. -* Transform Java builders to Kotlin DSL patterns when appropriate. - -## Error Prevention - -* Pay special attention to Java's checked exceptions versus Kotlin's unchecked exceptions. -* Be cautious with Java wildcards (`? extends`, `? super`) conversion to Kotlin's `out` and `in` type parameters. -* Ensure proper handling of Java static initialization blocks in Kotlin companion objects. -* Verify that Java overloaded methods convert correctly with appropriate default parameter values in Kotlin. -* Remember that Kotlin has smart casts which can eliminate explicit type casting needed in Java. - -## Documentation Conversion - -* Convert `@param` to `@param` with the same description. -* Convert `@return` to `@return` with the same description. -* Convert `@throws` to `@throws` with the same description. -* Convert `{@link}` to `[name][fully.qualified.Name]` format. -* Convert `{@code}` to inline code with backticks (`). - -## Final step: ensure the version is bumped - -After the conversion is verified, invoke `/version-bumped` so the branch -carries a strictly greater `version.gradle.kts` than the base ref before -any `./gradlew build` (which may transitively `publishToMavenLocal` and -overwrite the previously published snapshot consumer repos depend on). -The skill is a no-op when a bump already happened earlier on the branch. diff --git a/.agents/skills/java-to-kotlin/agents/openai.yaml b/.agents/skills/java-to-kotlin/agents/openai.yaml deleted file mode 100644 index 252920fe..00000000 --- a/.agents/skills/java-to-kotlin/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Java to Kotlin" - short_description: "Convert Java code to idiomatic Kotlin" - default_prompt: "Use $java-to-kotlin to convert Java code to Kotlin while preserving behavior, nullability, and API documentation wording." diff --git a/.agents/skills/kotlin-review/SKILL.md b/.agents/skills/kotlin-review/SKILL.md deleted file mode 100644 index c55c8c49..00000000 --- a/.agents/skills/kotlin-review/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: kotlin-review -description: > - Review Kotlin (and Java) changes in this repo against the Spine coding - guidelines, safety rules, and testing policy. Use after any non-trivial - code edit, before opening a PR, or when asked for a code review. - Read-only; does not run builds. ---- - -# Kotlin code review (repo-specific) - -You are the Kotlin reviewer for this repository. The authoritative standards -live in `.agents/`: - -- `.agents/coding-guidelines.md` β€” Kotlin idioms, formatting, what to prefer/avoid. -- `.agents/safety-rules.md` and `.agents/advanced-safety-rules.md` β€” hard constraints - (no reflection without approval, no analytics/telemetry, no blocking calls in - coroutines, no auto-updating external dependencies). -- `.agents/testing.md` β€” Kotest assertions preferred, stubs not mocks. -- `.agents/project-structure-expectations.md` β€” module/source-set layout. -- `.agents/version-policy.md` β€” version bumps are required only when the - repository has a root `version.gradle.kts`. - -## Review procedure - -1. Read the diff. Use `git diff --staged` or `git diff ...HEAD` depending on - what the user describes. Do NOT review the full repo β€” only what changed. -2. Read each affected file fully, not just the diff hunks. Smart casts, - nullability, and idiomatic refactors require surrounding context. -3. Check against `.agents/coding-guidelines.md`: - - Kotlin idioms (extension functions, `when`, smart casts, data/sealed classes). - - Immutability by default. - - No `!!` without justification. - - No type names in variable names. - - No string duplication β€” use companion-object constants. - - No mixing Groovy/Kotlin DSL in build logic. - - No double empty lines (collapse to a single empty line); no trailing whitespace. -4. Check safety rules: reflection, telemetry, blocking-in-coroutines, dependency - bumps that weren't requested. -5. Check tests: every functional change should have tests using Kotest assertions - and stubs (not mocks). -6. Check the version gate: - - If the repository has a root `version.gradle.kts`, confirm it was - incremented when the change is user-visible. - - If root `version.gradle.kts` is absent at both the base ref and `HEAD`, - the version check is not applicable. Do not report a missing version bump - or ask for the file to be created. - -## Output format - -Return three sections, in this order: - -- **Must fix** β€” violations of safety rules, broken builds, missing version - bump when the version gate applies, missing tests for functional changes. -- **Should fix** β€” coding-guideline violations and clearer idiomatic alternatives. - Cite the specific guideline. -- **Nits** β€” style and naming suggestions. - -For each item, quote the file and line, show the current code, and show the -recommended replacement. If there's nothing in a section, write "None." - -End with a one-line verdict: `APPROVE`, `APPROVE WITH CHANGES`, or `REQUEST CHANGES`. diff --git a/.agents/skills/move-files/SKILL.md b/.agents/skills/move-files/SKILL.md deleted file mode 100644 index b92b05d3..00000000 --- a/.agents/skills/move-files/SKILL.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: move-files -description: > - Move or rename any files/directories in a repo: preserve history, update all - references and build metadata, verify no stale paths remain. ---- - -# Move Files - -## Workflow - -1. Preflight. - - Run `git status --short`. - - Map each `source -> destination`. - - Classify scope: simple same-module moves stay targeted; package, module, or - cross-module moves need broader inspection. - - Ask before ambiguous mappings, destination conflicts, or unclear semantic - package/module changes. - -2. Search before moving. - - Search all old identifiers: paths, names, resource refs, doc links. - - For Gradle/module/source-set moves, check `settings.gradle.kts`, - `build.gradle.kts`, and `buildSrc`. - - For Kotlin/Java, update package declarations only when package intent - changes. - -3. Move safely. - - Always use `git mv` for tracked files in the repo. If sandboxing blocks - it, request approval; do not use delete/create as a fallback. - - Use filesystem moves only for untracked/generated/out-of-git files. - - Create parent directories first. - - For case-only renames, move through a temporary name. - -4. Repair references. - - Update all references: imports, build metadata, docs, resources, and scripts. - - Start search scope narrow: affected directory, then module, then repo-wide. - - Prefer precise edits; avoid broad replacements on generic names. - -5. Verify. - - Re-run targeted searches for old tokens. - - Run `git status --short` and confirm the delta matches the move. - - Run focused validation for moved files, or state what could not run. - -6. Ensure the version is bumped. - Invoke `/version-bumped` so the branch carries a strictly greater - `version.gradle.kts` than the base ref before any `./gradlew build` - (which can transitively `publishToMavenLocal` and overwrite - consumer-facing snapshots). The skill is a no-op if a bump already - happened earlier on the branch. - -## Repo Notes - -Follow `.agents/project-structure-expectations.md` for module/source-set/test moves. - -## Report - -Return: `Moved[]`, `UpdatedRefs[]`, `Verification[]`, `Risks[]`. diff --git a/.agents/skills/move-files/agents/openai.yaml b/.agents/skills/move-files/agents/openai.yaml deleted file mode 100644 index ba90a9f8..00000000 --- a/.agents/skills/move-files/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Move Files" - short_description: "Move files safely across a repo" - default_prompt: "Use $move-files to relocate files or directories in this repository while preserving history, updating references, and verifying the result." diff --git a/.agents/skills/pre-pr/SKILL.md b/.agents/skills/pre-pr/SKILL.md deleted file mode 100644 index e656b0b4..00000000 --- a/.agents/skills/pre-pr/SKILL.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -name: pre-pr -description: > - Run the pre-PR checklist for this repo: apply the version gate only when - the repository has a root `version.gradle.kts`, run a scope-dependent - build/check command per `.agents/running-builds.md` (docs-only β†’ `dokka`; - code/deps β†’ `build`; proto β†’ `clean build`; no documented command β†’ skipped), - and invoke the relevant reviewers (`kotlin-review`, `review-docs`, - `dependency-audit`, - `check-links`) against the branch diff. On success, write a sentinel file at - `.git/pre-pr.ok` so the `gh pr create` hook can verify the checklist ran - for the current HEAD. Use before opening a PR, or when CI rejected a - branch and you want a fast local repro. ---- - -# Pre-PR checklist (repo-specific) - -You are the pre-PR gate for this repository. You compose the existing -reviewers and the documented repository rules into a single pass that must -succeed before a pull request is opened. - -This skill supports both versioned Gradle Build Tools projects and repositories -that intentionally do not have `version.gradle.kts`. Do not create -`version.gradle.kts` just to satisfy this checklist. When the file is absent -from the project root, the version-bump check is **not applicable**. - -The authoritative standards live in `.agents/`: - -- `.agents/version-policy.md` β€” applies only when the repository has a root - `version.gradle.kts`. -- `.agents/running-builds.md` β€” which build/check command to run. -- `.agents/safety-rules.md` and `.agents/advanced-safety-rules.md` β€” hard - constraints checked by the reviewers. - -## Procedure - -Run steps 1–4 fully before aggregating. Collect all findings; do not stop at -the first failure. - -### 1. Determine scope and repository capabilities - -- Base ref: `master` unless the user provides a different one. -- Changed files: `git diff ...HEAD --name-only` -- Repository root: `git rev-parse --show-toplevel` -- Version gate: check only the repository-root `version.gradle.kts`. - - Absent at both sides β†’ `not-applicable`, continue. - - Present at `HEAD` β†’ enforce in step 2. - - Present at `` but missing at `HEAD` β†’ fail unless the user - explicitly asked to migrate away from Gradle Build Tools versioning. -- Classify changes: - - **proto** β€” any `*.proto` changed - - **code** β€” any `*.kt`, `*.kts`, or `*.java` changed - - **docs** β€” any `*.md` or doc-only source edits changed - - **deps** β€” any file under `buildSrc/src/main/kotlin/io/spine/dependency/` changed - - **site** β€” any file under `docs/**` or `lychee.toml` (triggers Hugo link - check; pure `README.md` or KDoc-only changes do *not* count) - -### 2. Version-bump check - -- Skip when version gate is `not-applicable`. -- Read `version.gradle.kts` at `HEAD`. Read `` only if the file exists - there; if it does not, the file is newly introduced β€” record the introduced - version and continue. -- When both sides have the file: if the version is not strictly greater (semver - + Spine snapshot rules in `.agents/version-policy.md`): if - `.agents/skills/bump-version/` exists, **auto-fix immediately** by invoking - `/bump-version` without asking; otherwise record a Must-fix and continue. - Re-read the file after the fix. If the version is still not strictly greater, - record a Must-fix and continue. If the auto-fix succeeded, recompute the - changed-file list (`git diff ...HEAD --name-only`) before proceeding to - Step 3 β€” the bump commit adds `version.gradle.kts` to the diff. - -### 3. Build or check - -Pick the target per `.agents/running-builds.md`: - -- **proto** changed β†’ `./gradlew clean build` -- Else **code** changed β†’ `./gradlew build` -- Else **docs**-only β†’ `./gradlew dokka` - -If `./gradlew` is absent, read `.agents/running-builds.md` for the -repository-specific command. If that file is also absent, or if none is -documented for the change type, record `build_status=skipped` with the -reason and continue. - -Run the chosen command. On failure, record the first failing task and -continue to step 4 β€” do not abort. Pass `build_status=FAIL` in the context -given to reviewers so they can discount false positives from non-compiling -code. - -### 4. Reviewers (run in parallel) - -Dispatch relevant reviewers concurrently; collect all verdicts before -aggregating. Before dispatching, check that the skill directory exists under -`.agents/skills/`; if a skill is absent, skip it with a note "not applicable -for this repo" rather than failing. - -- **code** changed β†’ `kotlin-review` -- **docs** or KDoc changed β†’ `review-docs` -- **deps** changed β†’ `dependency-audit` -- **site** changed β†’ `check-links` (unless the sentinel short-circuit below - applies) - -**`check-links` sentinel short-circuit.** Read `.git/check-links.ok` (if -present). If `head=` equals the current **full** HEAD SHA and `status=PASS`, skip -dispatch and record `APPROVE` with note "cached from `.git/check-links.ok`" -(caching its ~30 s rebuild+serve cycle; the result is deterministic for a given -HEAD). Otherwise dispatch normally. - -Pass each reviewer: base ref, changed-file list, build result, version result. -When the version check is `not-applicable`, say so explicitly so reviewers don't flag a -missing version bump. - -**Auto-fix policy for reviewer findings:** - -- Findings from `kotlin-review`, `review-docs`, or `dependency-audit` β†’ record - as Must-fix or Should-fix; do **not** auto-apply. Surface them and wait for - user action. -- If a reviewer reports a missing version bump after Step 2 already ran, the - auto-fix did not take β€” record a Must-fix and do not silently re-apply. -- `dependency-audit` reports a **version rollback** β†’ do **not** auto-fix. - Surface it as a Must-fix and wait for user confirmation, because a rollback - can be intentional. - -### 5. Aggregate - -- **PASS**: version check passed or `not-applicable`, build succeeded or - `build_status=skipped` (no documented command for the change type), every - reviewer returned `APPROVE` or `APPROVE WITH CHANGES`, and no unaddressed - Must-fix items remain. -- **FAIL**: anything else. - -### 6. Sentinel - -Write `.git/pre-pr.ok` at the repo root (never under `.claude/`). The `gh pr -create` hook (`.agents/scripts/pre-pr-gate.sh`) checks `head=` and `status=`; -field names in this block are part of that contract. - -``` -head= -branch= -status=PASS|FAIL -timestamp= -build= -build_status=PASS|FAIL|skipped -reviewers= -version=new, introduced:, or "not-applicable"> -``` - -## Output format - -**On PASS** β€” single line: - -``` -Pre-PR: PASS ( vs ) β€” ready to `gh pr create`. -``` - -**On FAIL** β€” header line, then only the items that need attention, each -prefixed with the source reviewer or check: - -``` -Pre-PR: FAIL ( vs ) - -Must fix: -- [kotlin-review] -- [review-docs] - -Should fix: -- [dependency-audit] -``` - -Report nothing about checks that passed. If auto-fixes were applied, list -them in one line before the verdict: `Auto-fixed: .` - -## Notes - -- This skill must NOT create the PR itself. -- This skill must NOT create `version.gradle.kts`. -- The sentinel lives under `.git/` β€” per-clone, never committed. -- Each reviewer is the source of truth for its own checks; this skill only - orchestrates and aggregates. -- This skill may auto-fix a missing version bump by invoking `/bump-version`; - all other fixes require explicit user confirmation. diff --git a/.agents/skills/review-docs/SKILL.md b/.agents/skills/review-docs/SKILL.md deleted file mode 100644 index d936fa28..00000000 --- a/.agents/skills/review-docs/SKILL.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -name: review-docs -description: > - Review documentation changes β€” KDoc/Javadoc inside Kotlin/Java sources and - Markdown docs (`README.md`, `docs/**`) β€” against Spine documentation - conventions. Use when a diff touches doc comments or Markdown, before - opening a doc-affecting PR, or when asked for a documentation review. - Read-only; does not run builds. ---- - -# Review documentation (repo-specific) - -You are the documentation reviewer for a Spine Event Engine project. You -focus strictly on documentation quality β€” prose, KDoc/Javadoc, and Markdown β€” -and deliberately do **not** duplicate the code-review skill (which owns -Kotlin idioms, safety rules, tests, and version-gate checks). - -The authoritative standards live in `.agents/`: - -- `.agents/documentation-guidelines.md` β€” commenting rules, TODO-comment - format, "file/dir names as code", widow/runt/orphan/river rule (with the - diagram at `.agents/widow-runt-orphan.jpg`). -- `.agents/documentation-tasks.md` β€” KDoc-example requirement on APIs; - Javadoc β†’ KDoc conversion rules (`

` removal, etc.). -- `.agents/skills/writer/SKILL.md` β€” Markdown conventions (footnote-style - reference links for external URLs, typographic quotes only on actual - page/section titles, sidenav-sync rules under `docs/`). -- `.agents/running-builds.md` β€” for doc-only Kotlin/Java changes the right - build is `./gradlew dokka` (no tests required). - -## Review procedure - -1. **Scope the diff.** Obtain the change set via `git diff --staged` or - `git diff ...HEAD` depending on what the user describes. Restrict - to files matching: - - `**/*.kt`, `**/*.kts`, `**/*.java` (for KDoc/Javadoc inside sources) - - `**/*.md` (Markdown docs) - Do **not** review the full repo β€” only what changed. - -2. **Read each affected file fully, not just the hunks.** Prose review - requires surrounding context β€” judging widows/runts/orphans, link - placement, and KDoc completeness needs the whole paragraph and the - surrounding declarations. - -3. **Stay in scope.** If you spot a code-quality issue (idiom, naming, - tests, version-gate applicability), note it briefly as a "for the code - reviewer" item under Nits β€” do not expand the review. - -## Checks - -### A. KDoc / Javadoc inside sources - -- **Public and internal APIs carry KDoc.** Per `documentation-tasks.md`, - KDoc should include at least one usage example for non-trivial APIs. - Missing KDoc on a new or modified public/internal symbol is a Should-fix. -- **No Javadoc residue in Kotlin.** When converting from Java: - - `

` tags on a text line removed (`"

This"` β†’ `"This"`). - - `

` on its own line replaced with a blank line. - - HTML entities (`&`, `<`, …) converted to literals where appropriate. -- **Inline comments in production code are minimized.** Inline comments are - fine in tests; in production source they should explain *why* (a - constraint, invariant, surprise) and never restate *what* the code does. -- **TODO comments follow the Spine format.** Linked from - `documentation-guidelines.md` to the wiki "TODO-comments" page. A bare - `// TODO: …` without owner/issue reference is a Should-fix. -- **File and directory names rendered as code.** Within KDoc/Javadoc prose, - `path/to/file.kt` and `module-name` must use backticks. - -### B. Markdown docs - -- **Footnote-style reference links** for external `https://` URLs (per the - `writer` skill). Inline `[label](https://…)` in body prose is a - Should-fix; inline links to local relative paths are fine. -- **Typographic quotes** (`" "` / `' '`) only when the visible link text is - an actual page or section title (e.g., the "Getting started" page). - Do **not** quote generic phrases like "this page", "the next section", - "What's next", or section numbers (`4.3`). -- **Sidenav sync.** If the diff adds/removes/renames/moves a page under - `docs/content/docs/

/`, the matching current-version - `sidenav.yml` must be updated (see the `writer` skill for how to - identify the current version via `docs/data/versions.yml`). A missing - sidenav update is a Must-fix. -- **Fenced code blocks** for commands and examples β€” no indented code - blocks for shell snippets (they swallow `$` prompts and hurt copy/paste). -- **Heading hierarchy.** No skipped levels (`#` β†’ `###`); exactly one `#` - per file. - -### C. Prose flow (Spine-specific) - -- **Avoid widows, runts, orphans, and rivers** β€” the rule from - `documentation-guidelines.md` with the diagram at - `.agents/widow-runt-orphan.jpg`. Operationally: - - **Widow / runt**: a paragraph's last line containing only one short - word (or a hyphenated fragment). Reflow the prior line. - - **Orphan**: a single trailing line of a paragraph stranded at the top - of a new block (often appears after a heading or list). Reflow. - - **River**: a vertical "gap" of aligned spaces running down justified - text. Rare in Markdown but possible in tables β€” reflow the table or - rewrite to break the alignment. - Quote the offending paragraph and propose a rewording that fixes it. - -### D. Terminology and tone - -- **Match code identifiers verbatim.** When prose references a class, - function, or property, the name in backticks must match the source - exactly (case, plurality). -- **Consistent terminology across the diff.** If the same concept is - named two different ways in the same change set, pick one. - -## Output format - -Three sections, in this order: - -- **Must fix** β€” broken/missing KDoc on a newly-introduced public API, - missing sidenav sync, broken cross-references, Javadoc residue - (`

` tags) left in Kotlin KDoc, broken Markdown links. -- **Should fix** β€” TODO format, inline-comment overuse in production, - inline external links that should be footnote-style, missing typographic - quotes (or unwanted ones), widow/runt/orphan/river paragraphs, - fenced-vs-indented code blocks. -- **Nits** β€” wording, terminology drift, code-identifier capitalization - in prose, "for the code reviewer" pointers if any code issues surfaced - incidentally. - -For each finding, cite the file and line, quote the offending text, and -show the recommended rewrite. If a section is empty, write "None." - -End with a one-line verdict: `APPROVE`, `APPROVE WITH CHANGES`, or -`REQUEST CHANGES`. diff --git a/.agents/skills/update-copyright/SKILL.md b/.agents/skills/update-copyright/SKILL.md deleted file mode 100644 index 6afc4c7c..00000000 --- a/.agents/skills/update-copyright/SKILL.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: update-copyright -description: > - Update source file copyright headers from the IntelliJ IDEA copyright profile, - replacing `today.year` with the current year. - Automatically apply when source files are modified in a change set. ---- - -# Copyright Update - -**Command:** `python3 .agents/skills/update-copyright/scripts/update_copyright.py` - -1. Scope: explicit files/dirs from the user, or all tracked source files if none given. -2. No explicit paths β†’ run with `--dry-run` first, then without. -3. Relay stdout (notice source, file count, changed paths) to the user. -4. Never add a copyright header to a file that does not already have one. diff --git a/.agents/skills/update-copyright/agents/openai.yaml b/.agents/skills/update-copyright/agents/openai.yaml deleted file mode 100644 index 246dd647..00000000 --- a/.agents/skills/update-copyright/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Copyright Update" - short_description: "Refresh source copyright headers" - default_prompt: "Use $update-copyright to refresh source file copyright headers from the IntelliJ IDEA copyright profile in this repository." diff --git a/.agents/skills/update-copyright/scripts/update_copyright.py b/.agents/skills/update-copyright/scripts/update_copyright.py deleted file mode 100755 index 2dbf8bbc..00000000 --- a/.agents/skills/update-copyright/scripts/update_copyright.py +++ /dev/null @@ -1,389 +0,0 @@ -#!/usr/bin/env python3 -"""Update source copyright headers from IntelliJ IDEA copyright profiles.""" - -from __future__ import annotations - -import argparse -import datetime as dt -import html -import re -import subprocess -import sys -from pathlib import Path -from xml.etree import ElementTree as ET - - -BLOCK_EXTENSIONS = { - ".c", - ".cc", - ".cpp", - ".cs", - ".css", - ".cxx", - ".dart", - ".go", - ".gradle", - ".groovy", - ".h", - ".hh", - ".hpp", - ".java", - ".js", - ".jsx", - ".kt", - ".kts", - ".less", - ".m", - ".mm", - ".proto", - ".rs", - ".scala", - ".scss", - ".swift", - ".ts", - ".tsx", -} -HASH_EXTENSIONS = { - ".bash", - ".bzl", - ".properties", - ".pl", - ".py", - ".rb", - ".sh", - ".toml", - ".yaml", - ".yml", - ".zsh", -} -XML_EXTENSIONS = { - ".fxml", - ".pom", - ".wsdl", - ".xml", - ".xsd", - ".xsl", - ".xslt", -} -EXCLUDED_DIRS = { - ".agents", - ".git", - ".gradle", - ".idea", - ".kotlin", - "build", - "generated", - "out", - "tmp", -} -EXCLUDED_FILES = { - "gradlew", - "gradlew.bat", -} - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description=( - "Update source copyright headers from " - ".idea/copyright/profiles_settings.xml." - ) - ) - parser.add_argument( - "paths", - nargs="*", - help="Files or directories to update. Defaults to tracked source files.", - ) - parser.add_argument( - "--root", - type=Path, - default=Path.cwd(), - help="Repository root. Defaults to the current working directory.", - ) - parser.add_argument( - "--year", - default=str(dt.date.today().year), - help="Year to substitute for today.year. Defaults to the current year.", - ) - parser.add_argument( - "--dry-run", - action="store_true", - help="Report files that would change without writing them.", - ) - parser.add_argument( - "--check", - action="store_true", - help="Exit with status 1 if any file would change; do not write files.", - ) - return parser.parse_args() - - -def profile_filename(profile_name: str) -> str: - stem = re.sub(r"[^A-Za-z0-9]+", "_", profile_name).strip("_") - if not stem: - raise ValueError("The default copyright profile name is empty.") - return f"{stem}.xml" - - -def load_notice(root: Path, year: str) -> tuple[str, Path]: - settings_path = root / ".idea" / "copyright" / "profiles_settings.xml" - if not settings_path.is_file(): - raise FileNotFoundError(f"Missing {settings_path}") - - settings_root = ET.parse(settings_path).getroot() - settings = settings_root.find(".//settings") - if settings is None: - raise ValueError(f"{settings_path} does not contain a settings tag.") - - default_profile = settings.get("default") - if not default_profile: - raise ValueError(f"{settings_path} settings tag has no default attribute.") - - profile_path = settings_path.parent / profile_filename(default_profile) - if not profile_path.is_file(): - raise FileNotFoundError( - f"Default profile {default_profile!r} resolves to missing {profile_path}" - ) - - profile_root = ET.parse(profile_path).getroot() - notice = None - for option in profile_root.findall(".//option"): - if option.get("name") == "notice": - notice = option.get("value") - break - if notice is None: - raise ValueError(f"{profile_path} has no option named 'notice'.") - - decoded = html.unescape(notice) - decoded = decoded.replace("${today.year}", year) - decoded = decoded.replace("$today.year", year) - decoded = decoded.replace("today.year", year) - return decoded.rstrip(), profile_path - - -def style_for(path: Path) -> str | None: - name = path.name - suffix = path.suffix.lower() - if name.endswith((".sh.template", ".bash.template", ".zsh.template")): - return "hash" - if suffix in BLOCK_EXTENSIONS: - return "block" - if suffix in HASH_EXTENSIONS: - return "hash" - if suffix in XML_EXTENSIONS: - return "xml" - return None - - -def is_excluded(path: Path) -> bool: - if path.name in EXCLUDED_FILES: - return True - parts = path.parts - if len(parts) >= 2 and parts[0] == "gradle" and parts[1] == "wrapper": - return True - return any(part in EXCLUDED_DIRS for part in parts) - - -def tracked_files(root: Path) -> list[Path]: - try: - result = subprocess.run( - ["git", "-C", str(root), "ls-files", "-z"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - except (FileNotFoundError, subprocess.CalledProcessError): - return [ - path.relative_to(root) - for path in root.rglob("*") - if path.is_file() and not is_excluded(path.relative_to(root)) - ] - - paths = [] - for item in result.stdout.decode("utf-8").split("\0"): - if not item: - continue - path = Path(item) - if (root / path).is_file(): - paths.append(path) - return paths - - -def expand_requested_paths(root: Path, requested: list[str]) -> list[Path]: - if not requested: - paths = tracked_files(root) - else: - paths = [] - for item in requested: - path = (root / item).resolve() - if not path.exists(): - raise FileNotFoundError(f"Path does not exist: {item}") - if not path.is_relative_to(root): - raise ValueError( - f"Path is outside the repository root: {item!r} " - f"(resolved to {path}, root is {root})" - ) - if path.is_dir(): - for child in path.rglob("*"): - if child.is_file(): - paths.append(child.relative_to(root)) - else: - paths.append(path.relative_to(root)) - - unique = sorted(set(paths), key=lambda p: p.as_posix()) - return [ - path - for path in unique - if style_for(path) is not None and not is_excluded(path) - ] - - -def newline_for(text: str) -> str: - return "\r\n" if "\r\n" in text else "\n" - - -def build_header(notice: str, style: str, newline: str) -> str: - lines = notice.splitlines() - if style == "block": - body = newline.join(f" * {line}" if line else " *" for line in lines) - return f"/*{newline}{body}{newline} */{newline}{newline}" - if style == "hash": - body = newline.join(f"# {line}" if line else "#" for line in lines) - return f"{body}{newline}{newline}" - if style == "xml": - body = newline.join(f" ~ {line}" if line else " ~" for line in lines) - return f"{newline}{newline}" - raise ValueError(f"Unsupported comment style: {style}") - - -def split_leading_directive(text: str, style: str, newline: str) -> tuple[str, str]: - if style == "hash" and text.startswith("#!"): - line_end = text.find("\n") - if line_end == -1: - return text + newline + newline, "" - prefix = text[: line_end + 1] + newline - return prefix, strip_leading_blank_lines(text[line_end + 1 :]) - - if style == "xml" and text.startswith("") - if close != -1: - line_end = text.find("\n", close) - if line_end == -1: - return text + newline + newline, "" - prefix = text[: line_end + 1] + newline - return prefix, strip_leading_blank_lines(text[line_end + 1 :]) - - return "", strip_leading_blank_lines(text) - - -def strip_leading_blank_lines(text: str) -> str: - return re.sub(r"^(?:[ \t]*\r?\n)+", "", text) - - -def strip_existing_header(text: str, style: str) -> tuple[str, bool]: - if style == "block" and text.startswith("/*"): - close = text.find("*/") - if close != -1: - candidate = text[: close + 2] - if is_copyright_header(candidate): - return strip_leading_blank_lines(text[close + 2 :]), True - - if style == "xml" and text.startswith("") - if close != -1: - candidate = text[: close + 3] - if is_copyright_header(candidate): - return strip_leading_blank_lines(text[close + 3 :]), True - - if style == "hash": - lines = text.splitlines(keepends=True) - end = 0 - for line in lines: - stripped = line.strip() - if stripped == "" or stripped.startswith("#"): - end += len(line) - continue - break - candidate = text[:end] - if candidate and is_copyright_header(candidate): - return strip_leading_blank_lines(text[end:]), True - - return text, False - - -def is_copyright_header(text: str) -> bool: - limited = text[:5000] - return "Copyright" in limited and ( - "Licensed under" in limited or "All rights reserved" in limited - ) - - -def updated_text(text: str, notice: str, style: str) -> str: - original = text - bom = "\ufeff" if text.startswith("\ufeff") else "" - if bom: - text = text[1:] - newline = newline_for(text) - prefix, body = split_leading_directive(text, style, newline) - body, had_header = strip_existing_header(body, style) - if not had_header: - return original - return bom + prefix + build_header(notice, style, newline) + body - - -def update_file(root: Path, path: Path, notice: str, dry_run: bool) -> bool: - absolute = root / path - style = style_for(path) - if style is None: - return False - - try: - text = absolute.read_text(encoding="utf-8") - except FileNotFoundError: - print(f"Skipping missing file: {path}", file=sys.stderr) - return False - except UnicodeDecodeError: - print(f"Skipping non-UTF-8 file: {path}", file=sys.stderr) - return False - - next_text = updated_text(text, notice, style) - if next_text == text: - return False - - if not dry_run: - with absolute.open("w", encoding="utf-8", newline="") as file: - file.write(next_text) - return True - - -def main() -> int: - args = parse_args() - root = args.root.resolve() - notice, profile_path = load_notice(root, args.year) - try: - paths = expand_requested_paths(root, args.paths) - except (FileNotFoundError, ValueError) as exc: - print(f"error: {exc}", file=sys.stderr) - return 2 - dry_run = args.dry_run or args.check - - changed = [ - path - for path in paths - if update_file(root, path, notice, dry_run=dry_run) - ] - - rel_profile = profile_path.relative_to(root) - action = "Would update" if dry_run else "Updated" - print(f"Notice source: {rel_profile}") - print(f"{action} {len(changed)} file(s).") - for path in changed: - print(path.as_posix()) - - if args.check and changed: - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.agents/skills/update-copyright/tests/test_update_copyright.py b/.agents/skills/update-copyright/tests/test_update_copyright.py deleted file mode 100644 index 8770b327..00000000 --- a/.agents/skills/update-copyright/tests/test_update_copyright.py +++ /dev/null @@ -1,130 +0,0 @@ -from __future__ import annotations - -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path - - -SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "update_copyright.py" - - -class UpdateCopyrightTest(unittest.TestCase): - def test_default_run_leaves_plain_source_without_header_unchanged(self) -> None: - with tempfile.TemporaryDirectory() as temp_dir: - root = Path(temp_dir) - self.write_profile(root) - source = root / "Foo.java" - original = "class Foo {}\n" - source.write_text(original, encoding="utf-8") - - subprocess.run(["git", "init", "-q"], cwd=root, check=True) - subprocess.run(["git", "add", "Foo.java"], cwd=root, check=True) - - result = self.run_script(root) - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn("Updated 0 file(s).", result.stdout) - self.assertEqual(result.stderr, "") - self.assertEqual(source.read_text(encoding="utf-8"), original) - - def test_existing_header_is_updated(self) -> None: - with tempfile.TemporaryDirectory() as temp_dir: - root = Path(temp_dir) - self.write_profile(root) - source = root / "Foo.java" - source.write_text( - "/*\n" - " * Copyright 2024 ACME\n" - " * All rights reserved\n" - " */\n" - "\n" - "class Foo {}\n", - encoding="utf-8", - ) - - result = self.run_script(root, "--year", "2026", "Foo.java") - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn("Updated 1 file(s).", result.stdout) - self.assertIn("Foo.java", result.stdout) - self.assertEqual(result.stderr, "") - self.assertEqual( - source.read_text(encoding="utf-8"), - "/*\n" - " * Copyright 2026 ACME\n" - " * All rights reserved\n" - " */\n" - "\n" - "class Foo {}\n", - ) - - def test_default_run_skips_tracked_files_deleted_from_working_tree(self) -> None: - with tempfile.TemporaryDirectory() as temp_dir: - root = Path(temp_dir) - self.write_profile(root) - source = root / "Foo.java" - source.write_text("class Foo {}\n", encoding="utf-8") - - subprocess.run(["git", "init", "-q"], cwd=root, check=True) - subprocess.run(["git", "add", "Foo.java"], cwd=root, check=True) - source.unlink() - - result = subprocess.run( - [ - sys.executable, - str(SCRIPT), - "--root", - str(root), - "--dry-run", - ], - check=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn("Would update 0 file(s).", result.stdout) - self.assertEqual(result.stderr, "") - - @staticmethod - def run_script(root: Path, *args: str) -> subprocess.CompletedProcess[str]: - return subprocess.run( - [ - sys.executable, - str(SCRIPT), - "--root", - str(root), - *args, - ], - check=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - - @staticmethod - def write_profile(root: Path) -> None: - copyright_dir = root / ".idea" / "copyright" - copyright_dir.mkdir(parents=True) - (copyright_dir / "profiles_settings.xml").write_text( - '' - '' - "\n", - encoding="utf-8", - ) - (copyright_dir / "Default.xml").write_text( - '' - "" - '" - "\n", - encoding="utf-8", - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/.agents/skills/version-bumped/SKILL.md b/.agents/skills/version-bumped/SKILL.md deleted file mode 100644 index 86ca53df..00000000 --- a/.agents/skills/version-bumped/SKILL.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -name: version-bumped -description: > - Verify the current branch has bumped `version.gradle.kts` strictly above - the base ref; invoke `/bump-version` to auto-recover if not. Composable: - other modifying skills (`dependency-update`, `bump-gradle`, - `java-to-kotlin`, `move-files`) call this as their final step so a - `./gradlew build` or `publishToMavenLocal` can never overwrite a - previously published Maven Local artifact that integration tests in - consumer repos depend on. ---- - -# Ensure version is bumped - -This skill is the agent-facing wrapper around -`.agents/skills/version-bumped/scripts/version-bumped.sh`. The script is the source of truth for -"has this branch advanced the version vs base?"; this skill just runs it -and, if it fails, invokes `/bump-version` and re-runs to confirm. - -The same logic is enforced as a hook -(`.agents/scripts/publish-version-gate.sh`) that fires before any -`./gradlew … (build|publish|publishToMavenLocal)` invocation, so even -direct gradle calls cannot bypass it. This skill exists for the -cooperative path β€” other skills calling it before they finish, so the -user is never surprised by a blocked gradle command later. - -The premise is simple: any feature branch is a candidate for publishing, -even when the only change is the version bump itself (sometimes the bump -is the entire change, used to retry a publish that failed because Maven -repositories were overloaded). So if the branch differs from base at all, -the version must advance. - -## When to use - -- Automatically: as the final step of any skill that may change files on - the branch. -- Manually (`/version-bumped`): before running `./gradlew build` or - `./gradlew publishToMavenLocal` on a feature branch when you are not - sure whether the version has already been bumped. - -## Procedure - -1. Run the deterministic check: - - ```bash - .agents/skills/version-bumped/scripts/version-bumped.sh - ``` - - Honor `VERSION_BUMPED_BASE` if the user has set a non-default base ref - (e.g. `origin/master`, or a release branch). - -2. Interpret the exit code: - - - **0** β€” Done. Either the repository has no root `version.gradle.kts` - (the version check is `N/A`), the branch has no diff vs base, or the - version is already strictly greater. Report a one-line confirmation - and stop. - - **1** β€” Block. The script's stderr explains which check failed. - Proceed to step 3. - - **2** β€” Configuration error (no merge-base, parse failure on - `version.gradle.kts`). Do **not** invoke `/bump-version` - automatically. Surface the script's stderr to the user and stop. - -3. On exit 1, invoke `/bump-version` to perform the actual bump. That - skill owns the policy (snapshot numbering, the commit subject, the - rebuild, dependency-report regeneration, and the conflict rule). Do - not duplicate its work here. - -4. After `/bump-version` finishes, re-run the deterministic check. If it - now passes, report the new version on the branch. If it still fails, - surface the stderr unchanged and stop β€” do not loop. - -## Why this skill is separate from `/bump-version` - -`/bump-version` is the **action** (it edits `version.gradle.kts`, -commits, rebuilds, may commit reports). `/version-bumped` is the -**guard** (read-only check, optional auto-recovery). Skills that want to -say "make sure the branch has a bumped version" should call -`/version-bumped`, not `/bump-version`, because the guard is a no-op when -the bump is already done β€” calling `/bump-version` unconditionally would -double-bump on every chained skill invocation. - -## Relationship to `checkVersionIncrement` - -The Gradle task `checkVersionIncrement` (in `buildSrc/.../publish/`) -asks a different question: *"is this version already in the remote -Maven metadata?"* It runs on GitHub Actions feature-branch pushes and -fetches the Spine SDK Artifact Registry. The two checks are -complementary β€” neither subsumes the other. - -## See also - -- `.agents/version-policy.md` β€” when the version gate applies. -- `.agents/skills/bump-version/SKILL.md` β€” the bump procedure itself. -- `.agents/skills/pre-pr/SKILL.md` β€” uses the same check at PR time - (step 2). -- `.agents/skills/version-bumped/scripts/version-bumped.sh` β€” the deterministic check. -- `.agents/scripts/publish-version-gate.sh` β€” the hook that enforces the - rule on `./gradlew` invocations. diff --git a/.agents/skills/version-bumped/scripts/version-bumped.sh b/.agents/skills/version-bumped/scripts/version-bumped.sh deleted file mode 100755 index f050a5b7..00000000 --- a/.agents/skills/version-bumped/scripts/version-bumped.sh +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env bash -# -# Verifies that a feature branch which differs from the base ref also -# bumps `version.gradle.kts` strictly above the base version. Mirrors the -# universal "every branch advances the version" policy: a branch with any -# changes is a candidate for publishing β€” sometimes the only change is the -# bump itself, used to retry a publish that failed because Maven -# repositories were overloaded. -# -# Exit codes: -# 0 β€” OK: repo has no root `version.gradle.kts`, OR branch has no diff -# vs base, OR working-tree version is strictly greater than base -# version. -# 1 β€” Block: branch differs from base but version is unchanged or -# decreased. Stderr points to `/bump-version`. -# 2 β€” Configuration error (bad base ref, parse failure). Stderr explains. -# -# Inputs (env, all optional): -# VERSION_BUMPED_BASE Base ref to compare against. Default: master, -# then main if master is absent. -# VERSION_BUMPED_KEY Name of the `extra` property holding the -# publishing version (e.g. `versionToPublish`, -# `validationVersion`, `bootstrapVersion`). When -# set, bypasses auto-discovery. Useful for repos -# that don't follow the `version = extra["…"]` -# pattern in `build.gradle.kts`. -# VERSION_BUMPED_QUIET When `1`, suppress the "OK" line on stdout. -# The publish-version-gate hook sets this. -# -# Publishing-key discovery: -# The publishing version's variable name varies across Spine repos -# (`versionToPublish`, `validationVersion`, `compilerVersion`, …). -# `version.gradle.kts` may also declare other `val xxxVersion by extra(...)` entries -# that are *dependency* versions of other Spine modules β€” not this -# project's own publishing version β€” so the key cannot be picked by -# inspecting `version.gradle.kts` alone. -# -# The canonical source is `build.gradle.kts`, which assigns -# `version = extra["KEY"]!!`. This script scans for that pattern, -# picks the unique key, and parses its value from `version.gradle.kts`. -# If `build.gradle.kts` does not contain such a line, the script falls -# back to `versionToPublish`. Set `VERSION_BUMPED_KEY` to override. -# -# Notes: -# * Companion to the Gradle task `checkVersionIncrement` (see -# `buildSrc/.../publish/CheckVersionIncrement.kt`). The Gradle task -# asks "is this version already in remote Maven metadata?" β€” this -# script asks the simpler local question "has this branch advanced -# the version vs base?". The two checks are complementary; neither -# subsumes the other. -# * The working tree is included in the change-detection so the gate -# reflects what `./gradlew build` would actually publish. -# -set -eu - -repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { - echo "version-bumped: not inside a git repository" >&2 - exit 2 -} -cd "$repo_root" - -version_file="version.gradle.kts" - -# --- N/A: not a versioned project ---------------------------------------- -if [ ! -f "$version_file" ]; then - [ "${VERSION_BUMPED_QUIET:-0}" = "1" ] || echo "version-bumped: N/A (no root version.gradle.kts)" - exit 0 -fi - -# --- Resolve base ref ---------------------------------------------------- -base="${VERSION_BUMPED_BASE:-}" -if [ -z "$base" ]; then - if git show-ref --verify --quiet refs/heads/master; then - base=master - elif git show-ref --verify --quiet refs/heads/main; then - base=main - else - echo "version-bumped: no master or main branch found; set VERSION_BUMPED_BASE" >&2 - exit 2 - fi -fi - -if ! git rev-parse --verify --quiet "$base" >/dev/null; then - echo "version-bumped: base ref '$base' does not resolve" >&2 - exit 2 -fi - -# When we are on the base branch itself, there is nothing to gate. -current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") -if [ "$current_branch" = "$base" ] || [ "$current_branch" = "${base##*/}" ]; then - [ "${VERSION_BUMPED_QUIET:-0}" = "1" ] || echo "version-bumped: on base branch ($current_branch); nothing to gate" - exit 0 -fi - -merge_base=$(git merge-base HEAD "$base" 2>/dev/null) || { - echo "version-bumped: cannot find merge-base of HEAD and '$base'" >&2 - exit 2 -} - -# --- Detect any branch divergence vs base (committed/worktree/untracked) - -committed=$(git diff --name-only "$merge_base"..HEAD 2>/dev/null || true) -worktree=$(git diff --name-only HEAD 2>/dev/null || true) -untracked=$(git ls-files --others --exclude-standard 2>/dev/null || true) - -if [ -z "$committed" ] && [ -z "$worktree" ] && [ -z "$untracked" ]; then - [ "${VERSION_BUMPED_QUIET:-0}" = "1" ] || echo "version-bumped: no changes vs $base" - exit 0 -fi - -# --- Discover the publishing-version key --------------------------------- -# Source of truth is `build.gradle.kts` (or `build.gradle`). Two shapes are -# recognised, in order: -# -# a) version = extra["KEY"] -# b) version = IDENTIFIER (with `val IDENTIFIER ... by extra` nearby) -# -# Single or double quotes are accepted in shape (a). If multiple distinct -# keys appear across shapes, the script refuses to guess and asks the user -# to set VERSION_BUMPED_KEY. -# -# Return codes: -# 0 β€” printed a unique key on stdout -# 1 β€” no candidates found (caller should fall back) -# 2 β€” ambiguous; diagnostic already on stderr -discover_key() { - local files keys_a keys_b keys count - files="" - [ -f build.gradle.kts ] && files="build.gradle.kts" - [ -f build.gradle ] && files="$files build.gradle" - [ -z "$files" ] && return 1 - # Shape (a): version = extra["KEY"] - # Anchored to start-of-line (modulo leading whitespace) so that comments - # like `// version = extra["x"]` and identifiers like `fooversion = ...` - # don't produce false matches. - # shellcheck disable=SC2086 - keys_a=$(grep -hE '^[[:space:]]*version[[:space:]]*=[[:space:]]*extra[[:space:]]*\[[[:space:]]*["'"'"'][^"'"'"']+["'"'"']' $files 2>/dev/null \ - | sed -nE 's/.*extra[[:space:]]*\[[[:space:]]*["'"'"']([^"'"'"']+)["'"'"'].*/\1/p') - # Shape (b): version = IDENTIFIER (bare Kotlin identifier, no '[' or '"'). - # Only accept the identifier if the same file also declares - # `val IDENTIFIER[: String]? by extra` β€” otherwise it's a plain local - # variable (common in Groovy `build.gradle`), not an `extra` property we - # can resolve in `version.gradle.kts`. - local candidates_b cand - # shellcheck disable=SC2086 - candidates_b=$(grep -hE '^[[:space:]]*version[[:space:]]*=[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*$' $files 2>/dev/null \ - | sed -nE 's/^[[:space:]]*version[[:space:]]*=[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*$/\1/p') - keys_b="" - for cand in $candidates_b; do - # shellcheck disable=SC2086 - if grep -hE "^[[:space:]]*val[[:space:]]+${cand}([[:space:]]*:[[:space:]]*String)?[[:space:]]+by[[:space:]]+extra([^A-Za-z0-9_]|\$)" $files >/dev/null 2>&1; then - keys_b="${keys_b}${cand} -" - fi - done - keys=$(printf '%s\n%s' "$keys_a" "$keys_b" | sed '/^$/d' | sort -u) - [ -z "$keys" ] && return 1 - count=$(printf '%s\n' "$keys" | wc -l | tr -d ' ') - if [ "$count" -gt 1 ]; then - { - echo "version-bumped: ambiguous publishing key in build scripts:" - while IFS= read -r k; do printf ' %s\n' "$k"; done <<< "$keys" - echo " Set VERSION_BUMPED_KEY to disambiguate." - } >&2 - return 2 - fi - printf '%s' "$keys" -} - -key="${VERSION_BUMPED_KEY:-}" -if [ -z "$key" ]; then - set +e - key=$(discover_key) - rc=$? - set -e - if [ "$rc" = "2" ]; then - exit 2 - fi - if [ "$rc" != "0" ] || [ -z "$key" ]; then - key="versionToPublish" - fi -fi - -# --- Parse a `val KEY by extra(...)` from a Gradle file content ---------- -# Handles three shapes (per .agents/skills/bump-version/SKILL.md step 2): -# 1. val KEY[: String]? by extra("X") β€” literal extra -# 2. val SRC[: String]? by extra("X") β€” alias chain via extra -# val KEY[: String]? by extra(SRC) -# 3. val SRC[: String]? = "X" β€” alias chain via plain val -# val KEY[: String]? by extra(SRC) -# The key name is parameterized so that any project-specific name works -# (versionToPublish, validationVersion, bootstrapVersion, botVersion, …). -parse_version() { - local content="$1" name="$2" - local v varName - # Shape 1: literal. - v=$(printf '%s' "$content" \ - | grep -E "val[[:space:]]+${name}([[:space:]]*:[[:space:]]*String)?[[:space:]]+by[[:space:]]+extra\(\"" \ - | head -n1 \ - | sed -nE 's/.*extra\("([^"]+)".*/\1/p') - if [ -n "$v" ]; then - printf '%s' "$v" - return 0 - fi - # Shapes 2 & 3: extract the alias source identifier. - varName=$(printf '%s' "$content" \ - | grep -E "val[[:space:]]+${name}([[:space:]]*:[[:space:]]*String)?[[:space:]]+by[[:space:]]+extra\(" \ - | head -n1 \ - | sed -nE 's/.*extra\(([A-Za-z_][A-Za-z0-9_]*)\).*/\1/p') - if [ -n "$varName" ]; then - # Shape 2: source is `val SRC ... by extra("X")`. - v=$(printf '%s' "$content" \ - | grep -E "val[[:space:]]+${varName}([[:space:]]*:[[:space:]]*String)?[[:space:]]+by[[:space:]]+extra\(\"" \ - | head -n1 \ - | sed -nE 's/.*extra\("([^"]+)".*/\1/p') - if [ -n "$v" ]; then - printf '%s' "$v" - return 0 - fi - # Shape 3: source is `val SRC[: String]? = "X"`. - v=$(printf '%s' "$content" \ - | grep -E "val[[:space:]]+${varName}([[:space:]]*:[[:space:]]*String)?[[:space:]]*=[[:space:]]*\"" \ - | head -n1 \ - | sed -nE 's/.*=[[:space:]]*"([^"]+)".*/\1/p') - if [ -n "$v" ]; then - printf '%s' "$v" - return 0 - fi - fi - return 1 -} - -head_content=$(cat "$version_file" 2>/dev/null || true) -head_version=$(parse_version "$head_content" "$key" || true) -if [ -z "$head_version" ]; then - echo "version-bumped: cannot parse '$key' from working-tree $version_file" >&2 - exit 2 -fi - -# Base content may legitimately not exist (file newly introduced). -base_content=$(git show "$base:$version_file" 2>/dev/null || true) -if [ -z "$base_content" ]; then - [ "${VERSION_BUMPED_QUIET:-0}" = "1" ] || echo "version-bumped: $version_file newly introduced at $head_version; treating as bumped" - exit 0 -fi - -base_version=$(parse_version "$base_content" "$key" || true) -if [ -z "$base_version" ]; then - echo "version-bumped: cannot parse '$key' from $base:$version_file" >&2 - exit 2 -fi - -# --- Strict-greater comparison via `sort -V` ----------------------------- -if [ "$head_version" = "$base_version" ]; then - cmp="equal" -elif [ "$(printf '%s\n%s\n' "$base_version" "$head_version" | sort -V | tail -n1)" = "$head_version" ]; then - cmp="greater" -else - cmp="lesser" -fi - -if [ "$cmp" = "greater" ]; then - [ "${VERSION_BUMPED_QUIET:-0}" = "1" ] || echo "version-bumped: OK ($key: $base_version -> $head_version)" - exit 0 -fi - -cat >&2 < - Write, edit, and restructure user-facing and developer-facing documentation. - Use when asked to create/update docs such as `README.md`, `docs/**`, and - other Markdown documentation, including keeping docs navigation data in sync; - when drafting tutorials, guides, troubleshooting pages, or migration notes; and - when improving inline API documentation (KDoc) and examples. ---- - -# Write documentation (repo-specific) - -## Decide the target and audience - -- Identify the target reader: end user, contributor, maintainer, or tooling/automation. -- Identify the task type: new doc, update, restructure, or documentation audit. -- Identify the acceptance criteria: β€œwhat is correct when the reader is done?” - -## Choose where the content should live - -- Prefer updating an existing doc over creating a new one. -- Place content in the most discoverable location: - - `README.md`: project entry point and β€œwhat is this?”. - - `docs/`: longer-form docs (follow existing conventions in that tree). - - Source KDoc: API usage, examples, and semantics that belong with the code. - -## Keep docs navigation in sync - -- When adding, removing, moving, or renaming a page under - `docs/content/docs/

/`, keep the current version's matching - `sidenav.yml` in sync. -- Use `docs/data/versions.yml` to identify the current documentation version for - that section. The current version is the entry with `is_main: true`; its - `version_id` maps to `docs/data/docs/
//sidenav.yml`. -- Do not update historical version entries or their navigation files unless the - user explicitly asks to edit that historical version. -- Map page files to `file_path` values relative to the current version's - `content_path`, without `.md`; `_index.md` maps to its directory path, such as - `01-getting-started/_index.md` -> `01-getting-started`. -- Keep each `page` label aligned with the page frontmatter `title` unless the - existing navigation intentionally uses a shorter reader-facing label. -- Preserve the existing ordering, nesting, keys, comments, and YAML quoting - style. Remove nav entries for deleted pages and update `file_path` values for - moved pages. -- If a docs content change should not appear in navigation, say so explicitly in - the final response. - -## Follow local documentation conventions - -- Follow `.agents/documentation-guidelines.md` and `.agents/documentation-tasks.md`. -- Use fenced code blocks for commands and examples; format file/dir names as code. -- When referencing a documentation page or section in body prose, use typographic - double quotation marks only if the visible reference text is the actual page or - section title, such as the β€œGetting started” page or the β€œTroubleshooting” - section. The title normally starts with a capital letter. Do not add these - quotes around generic or descriptive links such as β€œthis page”, β€œthe next - section”, β€œdeclaring constraints”, or `4.3`, even if they point to a page or - section. Do not add these quotes in β€œWhat’s next” sections or navigation - elements. Keep file paths, identifiers, frontmatter values, navigation labels, - and Markdown link labels in their expected syntax. -- In Markdown files, prefer footnote-style reference links for external `https://` - targets instead of inline links. Write readable body text like - `[label][short-id]`, then place the URL definition near the end of the file, - such as `[short-id]: https://example.com/long/path`. Keep reference IDs short - and descriptive. Inline links are still fine for local relative paths. -- Avoid widows, runts, orphans, and rivers by reflowing paragraphs when needed. - -## Make docs actionable - -- Prefer steps the reader can execute (commands + expected outcome). -- Prefer concrete examples over abstract descriptions. -- Include prerequisites (versions, OS, environment) when they are easy to miss. -- Use consistent terminology (match code identifiers and existing docs). - -## KDoc-specific guidance - -- For public/internal APIs, include at least one example snippet demonstrating common usage. -- When converting from Javadoc/inline comments to KDoc: - - Remove HTML like `

` and preserve meaning. - - Prefer short paragraphs and blank lines over HTML formatting. - -## Validate changes - -- For code changes, follow `.agents/running-builds.md`. -- For documentation-only changes in Kotlin/Java sources, prefer `./gradlew dokka`. diff --git a/.agents/skills/writer/agents/openai.yaml b/.agents/skills/writer/agents/openai.yaml deleted file mode 100644 index 44eaa4e2..00000000 --- a/.agents/skills/writer/agents/openai.yaml +++ /dev/null @@ -1,5 +0,0 @@ -interface: - display_name: "Writer" - short_description: "Write and update user/developer docs" - default_prompt: "Write or revise documentation in this repository (for example: README.md, docs/**, CONTRIBUTING.md, and API documentation/KDoc). Follow local documentation guidelines in .agents/*.md, keep changes concise and actionable, and include concrete examples and commands where appropriate." - diff --git a/.agents/skills/writer/assets/templates/doc-page.md b/.agents/skills/writer/assets/templates/doc-page.md deleted file mode 100644 index f405b71e..00000000 --- a/.agents/skills/writer/assets/templates/doc-page.md +++ /dev/null @@ -1,23 +0,0 @@ -# Title - -## Goal - -State what the reader will accomplish. - -## Prerequisites - -- List versions/tools the reader needs. - -## Steps - -1. Do the first thing. -2. Do the next thing. - -## Verify - -Show how the reader can confirm success. - -## Troubleshooting - -- Common failure: likely cause β†’ fix. - diff --git a/.agents/skills/writer/assets/templates/kdoc-example.md b/.agents/skills/writer/assets/templates/kdoc-example.md deleted file mode 100644 index fdbd9b6a..00000000 --- a/.agents/skills/writer/assets/templates/kdoc-example.md +++ /dev/null @@ -1,11 +0,0 @@ -````kotlin -/** - * Explain what this API does in one sentence. - * - * ## Example - * ```kotlin - * // Show the typical usage pattern. - * val result = doThing() - * ``` - */ -```` diff --git a/.agents/skills/writer/assets/templates/kotlin-java-example.md b/.agents/skills/writer/assets/templates/kotlin-java-example.md deleted file mode 100644 index 5517516f..00000000 --- a/.agents/skills/writer/assets/templates/kotlin-java-example.md +++ /dev/null @@ -1,13 +0,0 @@ -{{< code-tabs langs="Kotlin, Java">}} - -{{< code-tab lang="Kotlin" >}} -```kotlin -``` -{{< /code-tab >}} - -{{< code-tab lang="Java" >}} -```java -``` -{{< /code-tab >}} - -{{< /code-tabs >}} diff --git a/.agents/testing.md b/.agents/testing.md deleted file mode 100644 index f81bdbf3..00000000 --- a/.agents/testing.md +++ /dev/null @@ -1,8 +0,0 @@ -# πŸ§ͺ Testing - -- Do not use mocks, use stubs. -- Prefer [Kotest assertions][kotest-assertions] over assertions from JUnit or Google Truth. -- Generate unit tests for APIs (handles edge cases/scenarios). -- Supply scaffolds for typical Kotlin patterns (`when`, sealed classes). - -[kotest-assertions]: https://kotest.io/docs/assertions/assertions.html diff --git a/.agents/version-policy.md b/.agents/version-policy.md deleted file mode 100644 index 3e8abd54..00000000 --- a/.agents/version-policy.md +++ /dev/null @@ -1,19 +0,0 @@ -# Version policy - -When a repository has `version.gradle.kts` at the project root, it follows the -[Spine SDK Versioning policy][wiki-versioning]. The version is kept in that -file and follows [Semantic Versioning 2.0.0][semver] with Spine-specific -extensions (snapshot `NUMBER`, patch, and flavor suffixes). - -For repositories with root `version.gradle.kts`, PRs without a version bump -fail CI. Repositories without that file are not versioned Gradle Build Tools -projects; their version check is not applicable, and agents must not create -`version.gradle.kts` just to satisfy `/pre-pr`. - -For the bump procedure in repositories that have root `version.gradle.kts` β€” -version-number selection, the commit-message convention, the rebuild, -dependency-report updates, and conflict resolution β€” use the -[`bump-version`](skills/bump-version/SKILL.md) skill. - -[semver]: https://semver.org/ -[wiki-versioning]: https://github.com/SpineEventEngine/documentation/wiki/Versioning diff --git a/.agents/widow-runt-orphan.jpg b/.agents/widow-runt-orphan.jpg deleted file mode 100644 index 284b02a47d57121b3fa0356a6805428ad2030c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54071 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 KDoc. -argument-hint: "" -allowed-tools: Read, Edit, Write, Bash(./gradlew:*), Bash(git status:*), Grep, Glob ---- - -Follow the `java-to-kotlin` skill exactly: - -- Skill: `.agents/skills/java-to-kotlin/SKILL.md` -- Target: $ARGUMENTS -- Preserve behavior. Convert Javadoc to KDoc, `@Nullable` to nullable Kotlin types, getters/setters to properties, static methods to companion objects or top-level functions. -- After each file, run `./gradlew compileKotlin` (or the relevant module's compile task) to verify. -- Honor `.agents/coding-guidelines.md` for Kotlin idioms. diff --git a/.claude/commands/move-files.md b/.claude/commands/move-files.md deleted file mode 100644 index 25885f9d..00000000 --- a/.claude/commands/move-files.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: Move or rename files/directories, updating all references and build metadata. -argument-hint: " " -allowed-tools: Read, Edit, Bash(git mv:*), Bash(git status:*), Bash(git ls-files:*), Grep, Glob ---- - -Follow the `move-files` skill exactly: - -- Skill: `.agents/skills/move-files/SKILL.md` -- Operation: $ARGUMENTS -- Preflight (run `git status --short`, classify scope) -> Search for all old identifiers -> Move with `git mv` -> Repair references (imports, build metadata, docs) -> Verify. -- Report: Moved[], UpdatedRefs[], Verification[], Risks[]. diff --git a/.claude/commands/pre-pr.md b/.claude/commands/pre-pr.md deleted file mode 100644 index 24499cc5..00000000 --- a/.claude/commands/pre-pr.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -description: Run the applicable pre-PR checklist (version gate, build/check, reviewers) and write a sentinel so `gh pr create` is unblocked. -argument-hint: "[base-ref]" -allowed-tools: Read, Write, Grep, Glob, Agent, Bash ---- - -Follow the `pre-pr` skill exactly: - -- Skill: `.agents/skills/pre-pr/SKILL.md` -- Base ref: $ARGUMENTS (treat empty as `master`). -- Detect whether the repository-root `version.gradle.kts` exists. If it is - absent at both the base ref and `HEAD`, the version check is `N/A`; do not - create the file and do not ask for `/bump-version`. -- Run the build/check command selected by the skill and - `.agents/running-builds.md`. The command may be Gradle or non-Gradle. -- Dispatch the reviewers as Claude subagents in parallel β€” send a single - message with multiple Agent tool uses: - - `kotlin-review` when `.kt|.kts|.java` files changed. - - `review-docs` when `.md` files or KDoc inside sources changed. - - `dependency-audit` when any file under - `buildSrc/src/main/kotlin/io/spine/dependency/` changed. -- Pass the version-check status to reviewers. If it is `N/A`, tell them: - "This repository has no root `version.gradle.kts`; a version bump is not - applicable and must not be reported as missing." -- Each reviewer is read-only; do not pass it edit tools. -- On any reviewer returning `REQUEST CHANGES`, treat the overall result - as `FAIL` and stop before writing the sentinel as `PASS`. -- Sentinel location: `$(git rev-parse --show-toplevel)/.git/pre-pr.ok`, - format per the skill (`head=`, `branch=`, `status=`, `timestamp=`, - `build=`, `reviewers=`, `version=`). Use `git rev-parse HEAD` for the - SHA and `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. -- Do NOT run `gh pr create`. That is the user's next step. diff --git a/.claude/commands/review-docs.md b/.claude/commands/review-docs.md deleted file mode 100644 index f8043f0e..00000000 --- a/.claude/commands/review-docs.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Review documentation changes (KDoc/Javadoc and Markdown) against Spine documentation conventions. -argument-hint: "[base-ref | --staged | paths...]" -allowed-tools: Read, Grep, Glob, Bash(git diff:*), Bash(git log:*), Bash(git status:*), Bash(git rev-parse:*), Bash(git ls-files:*) ---- - -Follow the `review-docs` skill exactly: - -- Skill: `.agents/skills/review-docs/SKILL.md` -- Scope / flags: $ARGUMENTS - - Empty: review the current branch's diff against `master` (`git diff master...HEAD`). - - `--staged`: review staged changes only (`git diff --staged`). - - A base ref (e.g. `master`, `origin/master`, a commit SHA): review `git diff ...HEAD`. - - Explicit paths: limit the review to those paths in addition to the diff scope. -- The skill owns the procedure, the per-area checks (KDoc/Javadoc, Markdown, - prose flow, terminology), and the output format (Must fix / Should fix / - Nits + one-line verdict). -- Stay in scope: documentation only. If a code-quality issue surfaces, - note it briefly as a Nit pointing at `/review` (or the `kotlin-review` - agent) β€” do not expand the review. -- Read-only: do not edit files, do not run builds. diff --git a/.claude/commands/run-build.md b/.claude/commands/run-build.md deleted file mode 100644 index 8a8d84ca..00000000 --- a/.claude/commands/run-build.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: Build the project the right way based on what changed (proto vs. Kotlin/Java vs. docs). -allowed-tools: Bash(./gradlew:*), Bash(git status:*), Bash(git diff:*) ---- - -Decide which build to run by looking at `git status --short` and `git diff --name-only`: - -- If any `.proto` files changed: `./gradlew clean build` -- Else if Kotlin or Java source changed: `./gradlew build` -- Else if only docs/comments changed (KDoc / Javadoc / Markdown): `./gradlew dokka`. Tests are NOT required for doc-only changes. - -Report the chosen command and its result. See `.agents/running-builds.md`. diff --git a/.claude/commands/update-copyright.md b/.claude/commands/update-copyright.md deleted file mode 100644 index 076fb613..00000000 --- a/.claude/commands/update-copyright.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: Refresh copyright headers from the IntelliJ profile, replacing today.year with the current year. -argument-hint: "[paths...]" -allowed-tools: Bash(python3 .agents/skills/update-copyright/scripts/update_copyright.py:*), Read ---- - -Follow the `update-copyright` skill exactly: - -- Skill: `.agents/skills/update-copyright/SKILL.md` -- Run: `python3 .agents/skills/update-copyright/scripts/update_copyright.py $ARGUMENTS` -- If $ARGUMENTS is empty, run once with `--dry-run`, show the output to the user, then run without `--dry-run`. -- Never add a header to a file that doesn't already have one. diff --git a/.claude/commands/write-docs.md b/.claude/commands/write-docs.md deleted file mode 100644 index b9b9a742..00000000 --- a/.claude/commands/write-docs.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -description: Write or update Markdown / KDoc documentation per Spine documentation conventions. -argument-hint: "" -allowed-tools: Read, Edit, Write, Grep, Glob ---- - -Follow the `writer` skill exactly: - -- Skill: `.agents/skills/writer/SKILL.md` -- Topic / target: $ARGUMENTS -- Decide audience first (end user, contributor, maintainer, tooling). -- Prefer updating an existing doc over creating a new one. -- Keep `docs/data/docs/

//sidenav.yml` in sync when adding, removing, moving, or renaming pages under `docs/content/docs/
/`. -- Honor `.agents/documentation-guidelines.md` and `.agents/documentation-tasks.md`. diff --git a/.claude/settings.json b/.claude/settings.json index f7bbfb98..337c904b 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,9 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", + "plansDirectory": ".claude/plans", "permissions": { "allow": [ - "Bash(./gradlew:*)", - "Bash(./config/gradlew:*)", "Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)", @@ -15,11 +14,11 @@ "Bash(git restore:*)", "Bash(git stash:*)", "Bash(git fetch:*)", - "Bash(git push:*)", "Bash(git rev-parse:*)", "Bash(git ls-files:*)", "Bash(git mv:*)", "Bash(git submodule status:*)", + "Bash(hugo:*)", "Bash(ls:*)", "Bash(cat:*)", "Bash(head:*)", @@ -32,9 +31,12 @@ "Bash(touch:*)", "Bash(python3 .agents/skills/update-copyright/scripts/update_copyright.py:*)", "Bash(./config/pull)", - "Bash(./config/migrate)" + "Bash(./config/migrate)", + "Skill(pre-pr)", + "Skill(pre-pr:*)" ], "deny": [ + "Bash(git push:*)", "Bash(git reset --hard:*)", "Bash(git clean -fdx:*)", "Bash(rm -rf /:*)", @@ -46,33 +48,27 @@ "Bash(git commit:*)", "Bash(git rebase:*)", "Bash(git merge:*)", - "Bash(git cherry-pick:*)", - "Bash(./gradlew publish:*)", - "Bash(./gradlew uploadArtifacts:*)", - "Bash(./gradlew clean:*)" + "Bash(git cherry-pick:*)" ] }, "hooks": { - "PreToolUse": [ + "SessionStart": [ { - "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", - "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/protect-version-file.sh" + "command": "$CLAUDE_PROJECT_DIR/init-submodules" } ] - }, + } + ], + "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", - "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/pre-pr-gate.sh" - }, - { - "type": "command", - "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/publish-version-gate.sh" + "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/secret-scan-gate.sh" } ] } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 81c8d500..039657be 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,8 +10,27 @@ repository root β€” read it first. If `.agents/project.md` exists, read it before reviewing. It provides the language, architecture, role, and code review checklist for this specific repo. -Additional guidelines are in `.agents/` β€” see `.agents/_TOC.md` for the index -(if present; Hugo repos do not include this file). +Additional guidelines are in `.agents/guidelines/` β€” see +`.agents/guidelines/_TOC.md` for the index. + +## Do not review + +Never review `gradlew` or `gradlew.bat` in any repository, including `config`. +These files are provided by Gradle and are not edited manually. + +If the current repository is `config`, review its files normally unless noted +above: they are authoritative there. In other repositories, the following files are managed by +the `config` submodule and must be reviewed in the `config` repository, not +here. In those consumer repositories, skip them without comment: + +- `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` +- `.agents/**` (except `.agents/project.md`) +- `.claude/**`, `.idea/**`, `.junie/**` +- `.github/copilot-instructions.md` +- `buildSrc/**` (except `buildSrc/src/main/kotlin/module.gradle.kts`) +- `gradle/`, `gradlew`, `gradlew.bat` +- `.codecov.yml`, `.gitignore`, `gradle.properties`, `lychee.toml` +- `.github/workflows/` β€” unless the workflow was introduced by this repo ## Universal rules diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index a6779543..6755ff91 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -33,15 +33,15 @@ jobs: cancel-in-progress: true steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Detect the Hugo site root (`docs/` or `site/`) by looking for a Hugo # config file. Hugo config may live directly in the site root or in a # `config/` or `config/_default/` subdirectory (both layouts are valid). - # Outputs `present=true|false`, `site_dir=docs|site`, and `work_dir` - # (the directory where `npm ci` / `hugo` commands should run β€” either - # `$site_dir/_preview` for repos that use a separate preview sub-tree, - # or `$site_dir` for repos whose Node/Hugo setup lives at the root). + # Outputs `present=true|false` and `work_dir` (the directory where + # `npm ci` / `hugo` commands should run β€” either `$dir/_preview` for + # repos that use a separate preview sub-tree, or `$dir` for repos whose + # Node/Hugo setup lives at the site root). # When neither directory has a Hugo config, the job short-circuits to a # success so that this shared workflow stays green on repos that do not # host a Hugo site at all. @@ -49,9 +49,10 @@ jobs: id: docs run: | for dir in docs site; do - for cfg in hugo.toml hugo.yaml config/hugo.toml config/_default/hugo.toml; do + for cfg in hugo.toml hugo.yaml \ + config/hugo.toml config/hugo.yaml \ + config/_default/hugo.toml config/_default/hugo.yaml; do if [ -f "$dir/$cfg" ]; then - echo "site_dir=$dir" >> "$GITHUB_OUTPUT" if [ -f "$dir/_preview/package-lock.json" ]; then echo "work_dir=$dir/_preview" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 50eb05eb..00000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Gradle Wrapper validation -on: - push: - branches: - - main - pull_request: - branches: - - '**' - -jobs: - validation: - name: Validate Gradle Wrapper - runs-on: ubuntu-latest - steps: - - name: Checkout latest code - uses: actions/checkout@v4 - - - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v4 diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 00000000..6f0130e2 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,70 @@ +name: Secret scan + +# Defense-in-depth behind the local `secret-scan` pre-commit hook and the +# `.gitignore` secret patterns: if a credential is committed despite those, this +# fails the pull request before it can merge. Distributed to every Spine repo by +# `./config/pull`. + +on: + pull_request: + push: + branches: + - master + - main + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + env: + # Pinned gitleaks version β€” bump through the usual dependency-update process. + GITLEAKS_VERSION: "8.21.2" + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # Full history so a pull request's commit range can be scanned. + fetch-depth: 0 + + - name: Install gitleaks + # Run gitleaks as the runner user against the checkout it owns β€” no + # container, so no "dubious ownership" git error and no GitHub Action + # org-licence requirement. + run: | + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xzf - gitleaks + ./gitleaks version + + - name: Scan + env: + EVENT: ${{ github.event_name }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + BEFORE: ${{ github.event.before }} + AFTER: ${{ github.sha }} + run: | + if [ "$EVENT" = pull_request ]; then + # Scan the PR's own commit RANGE: a secret added in one commit and + # deleted in a later commit of the same PR is still caught (a + # working-tree scan would miss it, yet merging keeps the secret-bearing + # commit reachable), while already-rotated secrets in older history + # outside base..head are not re-flagged. + ./gitleaks git --log-opts="$BASE..$HEAD" --redact --verbose --exit-code=1 . + else + # Push to a default branch: scan the pushed commit RANGE (before..after) + # so an add-then-remove batch is caught here too, not only on PRs β€” the + # leaked commit would otherwise stay reachable on the default branch. A + # branch's first push reports an all-zero `before` (no range); fall back + # to a working-tree scan then. + if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ]; then + ./gitleaks git --log-opts="$BEFORE..$AFTER" --redact --verbose --exit-code=1 . + else + # Branch's first push (all-zero `before`): no range to diff against, so + # scan the whole history reachable from the pushed tip as the initial + # import β€” an add-then-remove within those commits is still caught. + ./gitleaks git --log-opts="$AFTER" --redact --verbose --exit-code=1 . + fi + fi diff --git a/.gitignore b/.gitignore index 19c333c0..96972ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,15 @@ +# >>> shared config (managed by ./config/pull -- do not edit inside this block) >>> +# Copyright 2026 CodeMatters, Lda. # -# Copyright 2025, TeamDev. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # -# Redistribution and use in source and/or binary forms, with or without -# modification, must retain the above copyright notice and the following -# disclaimer. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -# Hugo cache files -/site/resources -public - -# Cache folder for Hugo Modules -/site/_vendor - -# Node modules -node_modules - -# Used to control concurrency between multiple Hugo instances -/site/.hugo_build.lock - -# Needed for navigation help inside code editors -jsconfig.json +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +# either express or implied. See the License for the specific language governing permissions +# and limitations under the License. # # This file is used for two purposes: @@ -69,14 +40,20 @@ jsconfig.json .idea/modules .idea/shelf +# `.idea/misc.xml` is intentionally NOT re-included below. It is project-local β€” +# it holds the per-project JDK name and IDEA's own churn (entry-point list +# indices, external-storage toggles) β€” so `.idea/*.xml` above keeps it ignored. +# `./config/pull` (via `migrate`) untracks any copy an earlier pull committed. + +# `.idea/kotlinc.xml` is likewise NOT re-included. It records the Kotlin compiler +# settings IDEA rewrites on its own (JVM target, bundled plugin version), so it +# churns per machine and per IDE build β€” `.idea/*.xml` above keeps it ignored. + # Do not ignore the following IDEA settings -!.idea/misc.xml !.idea/codeStyleSettings.xml !.idea/codeStyles/ !.idea/copyright/ -.DS_Store - # Ignore IDEA config files under `tests` /tests/.idea/** @@ -121,14 +98,48 @@ gradle-app.setting # Spine internal directory for storing intermediate artifacts **/.spine/** -# Login details to Maven repository. -# Each workstation should have developer's login defined in this file. +# --------------------------------------------------------------------------- +# Secrets β€” NEVER commit these. +# +# Encrypted credentials live under `.github/keys/*.gpg` and ARE committed. +# `config/scripts/decrypt.sh` turns each into its PLAINTEXT twin at build / CI / +# publish time (e.g. `spine-dev-framework-ci.json.gpg` -> `spine-dev.json`). The +# decrypted twins below β€” and any private key or service-account file β€” must stay +# out of Git. The shared `secret-scan` pre-commit hook is the backstop if one ever +# slips past these patterns. +# --------------------------------------------------------------------------- + +# Maven repository login details; each workstation defines its own. credentials.tar credentials.properties cloudrepo.properties deploy_key_rsa gcs-auth-key.json +# Decrypted Google / GCP service-account keys (plaintext twins of *.gpg). +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json + +# Decrypted credential property files and portal / publisher secrets. +*.secret.properties + +# Private SSH keys (public keys are *.pub and remain committable). +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 + +# ...but always keep the committed ENCRYPTED forms. +!*.gpg + # Log files *.log @@ -148,18 +159,24 @@ pubspec.lock # Ignore the `tmp` directory used for building dependant repositories. /tmp +.gradle-test-kit/ + # Python cache __pycache__/ *.pyc # Claude working files /.claude/worktrees/ +# Ephemeral plan-mode scratch (durable task docs live in `.agents/tasks/`). +/.claude/plans/ + +# Personal, per-developer Claude Code settings overrides (never committed; +# the distributed `.claude/settings.json` is the shared, committed layer). +/.claude/settings.local.json # Auto-downloaded Lychee binary used by the `check-links` skill. /.agents/skills/check-links/.cache/ -/.sass-cache/ - # Lychee link-checker cache (created by the `check-links` skill and # the `Check Links` workflow when run locally). .lycheecache @@ -170,3 +187,68 @@ __pycache__/ docs/_preview/node_modules/ docs/_preview/public/ docs/_preview/resources/ +# <<< shared config <<< + +# >>> repo-local entries (preserved across ./config/pull) >>> +# Copyright 2025, TeamDev. All rights reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# Redistribution and use in source and/or binary forms, with or without +# modification, must retain the above copyright notice and the following +# disclaimer. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# Hugo cache files +/site/resources +public +# Cache folder for Hugo Modules +/site/_vendor +# Node modules +node_modules +# Used to control concurrency between multiple Hugo instances +/site/.hugo_build.lock +# Needed for navigation help inside code editors +jsconfig.json +!.idea/codeStyleSettings.xml +!.idea/codeStyles/ +!.idea/copyright/ +.DS_Store +!**/src/**/build/** +!gradle-wrapper.jar +# Login details to Maven repository. +# Each workstation should have developer's login defined in this file. +/.sass-cache/ +# <<< repo-local entries <<< + +# >>> secret ignores re-asserted last (managed by ./config/pull -- do not edit) >>> +credentials.tar +credentials.properties +cloudrepo.properties +deploy_key_rsa +gcs-auth-key.json +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json +*.secret.properties +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 +# <<< secret ignores <<< diff --git a/.gitmodules b/.gitmodules index 94e8664d..1978333b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "config"] path = config url = https://github.com/SpineEventEngine/config +[submodule ".agents/shared"] + path = .agents/shared + url = https://github.com/SpineEventEngine/agents.git + branch = master + update = merge + ignore = all diff --git a/.idea/copyright/CodeMatters_Open_Source.xml b/.idea/copyright/CodeMatters_Open_Source.xml new file mode 100644 index 00000000..cf5d7135 --- /dev/null +++ b/.idea/copyright/CodeMatters_Open_Source.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/live-templates/README.md b/.idea/live-templates/README.md index 58394ba0..267008ad 100644 --- a/.idea/live-templates/README.md +++ b/.idea/live-templates/README.md @@ -10,7 +10,7 @@ This directory contains two live template groups: Live templates are not picked up by IDEA automatically. They should be added manually. In order to add these templates, perform the following steps: -1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA +1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA [settings folder][settings_folder]. 2. Restart IntelliJ IDEA: `File -> Invalidate Caches -> Just restart`. 3. Go to `Preferences -> Editor -> Live Templates`. @@ -22,6 +22,6 @@ In order to add these templates, perform the following steps: 1. Open the corresponding template: `Preferences -> Editor -> Live Templates -> User.todo`. 2. Click on `Edit variables`. -3. Set `USER` variable to your domain email address without `@teamdev.com` ending. For example, +3. Set `USER` variable to your domain email address without `@teamdev.com` ending. For example, for `jack.sparrow@teamdev.com` use the following expression `"jack.sparrow"`. 4. Verify that the template generates expected comments: `// TODO:2022-11-03:jack.sparrow: <...>`. diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index d4de1cb4..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 5160f499..7c1f8665 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -1,6 +1,6 @@ # Guidelines for Junie and AI Agent from JetBrains -Read the `../.agents/_TOC.md` file to understand: +Read the `../.agents/guidelines/_TOC.md` file to understand: - the agent responsibilities, - project overview, - coding guidelines, diff --git a/AGENTS.md b/AGENTS.md index 1268e51a..8c5f6198 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,16 +4,34 @@ If `.agents/project.md` exists in this repository, read it first β€” it describes the language, architecture, and role of this specific repo within the Spine SDK -organisation. To create one, copy `.agents/project.template.md` (or the -relevant language template) and fill it in. If `project.md` links to a shared -requirements file (e.g. `jvm-project.md`), read that too. +organisation. It is a symlink to `docs/project.md`; to create one, copy +`.agents/guidelines/project.template.md` to `docs/project.md` and fill it in. If it +links to a shared requirements file (e.g. `jvm-project.md`), read that too. -- Start every session by reading `.agents/quick-reference-card.md` (if present). +- Start every session by reading `.agents/guidelines/quick-reference-card.md` (if present). - For specific tasks (code review, PR prep, dependency updates, docs, etc.), prefer the matching skill from `.agents/skills/`. -- Full standards reference: `.agents/_TOC.md` (if present) β€” consult when a +- Full standards reference: `.agents/guidelines/_TOC.md` (if present) β€” consult when a skill doesn't cover the needed context. +Shared skills, scripts, and guidelines come from the `.agents/shared` submodule (the +[`agents`][agents-repo] repository) exposed via symlinks. +`./config/pull` initializes and floats them automatically. But a fresh `git worktree` +(and some shallow clones / cloud checkouts) start with NO submodules checked out, so +those symlinks dangle and no skills are found. Bootstrap such a tree with +**`./init-submodules`** β€” a root script that materializes the missing +*config-managed* submodules at their pinned commits: `config` itself, plus every +submodule that declares a tracked `branch` in `.gitmodules` (`.agents/shared`, and +any shared submodule added later) β€” the same rule `./config/pull` uses to decide +what it floats. Submodules the consumer owns (a Hugo theme, a vendored library, +doc-example submodules, …) declare no tracked branch and are left untouched, so the +automatic `SessionStart` run never tries to clone β€” or fail on credentials for β€” a +submodule this project does not manage. It depends on no pre-existing `config` +submodule, so it works before `./config/pull` (which lives inside the `config` +submodule) can. Claude Code runs it automatically via a `SessionStart` hook; other +agents and humans run it by hand, then `./config/pull` to float the shared submodules +to their branch tips. + ## Commit and history safety **Do not commit, push, tag, rebase, merge, cherry-pick, or otherwise write to git history** @@ -26,7 +44,7 @@ unless one of the following is true *right now*: Authorization does not carry over between turns or sessions. When in doubt: stage changes, show the diff, and stop β€” let the user commit. -See [`.agents/safety-rules.md`](.agents/safety-rules.md) β†’ *Commits and history-writing*. +See [`.agents/guidelines/safety-rules.md`](.agents/guidelines/safety-rules.md) β†’ *Commits and history-writing*. ## Other safety rules @@ -35,7 +53,7 @@ See [`.agents/safety-rules.md`](.agents/safety-rules.md) β†’ *Commits and histor - No analytics, telemetry, or tracking code. - No reflection or unsafe code without explicit approval. -See [`.agents/safety-rules.md`](.agents/safety-rules.md) for the full list. +See [`.agents/guidelines/safety-rules.md`](.agents/guidelines/safety-rules.md) for the full list. ## Moving files @@ -55,6 +73,17 @@ See `.agents/memory/README.md` for layout and write protocol. Review `.agents/memory/MEMORY.md` at the start of every session. Ruthlessly iterate until mistakes stop repeating. +## Asking questions + +- Ask at most one question per message. If a decision has a small set of + options, include those options as part of that one question. +- Do not bundle unrelated clarification questions. Ask the next question only + after the user answers the previous one. +- Apply this rule both when the agent needs clarification and when the user's + prompt means "ask questions". +- Prefer a reasonable assumption over another question when the answer would not + materially change the next step. + ## Verification & Quality - Never mark a task done without proof (tests, logs, diff vs main). @@ -77,3 +106,30 @@ Ruthlessly iterate until mistakes stop repeating. - Verify changes before marking a task done. - Update memory if lessons emerged. - Delete the task file on merge to master. + +## Code review + +Never review `gradlew` or `gradlew.bat` in any repository, including `config`. +These files are provided by Gradle and are not edited manually. + +When reviewing a pull request or diff in a consumer repository, skip any +file that the `config` module distributes. Those files belong in a review +of the `config` repo, not the consumer repo β€” reviewing them there adds +noise without value. + +Do **not** apply this skip rule when reviewing the `config` repository +itself. In `config`, these files are source files owned by the current +repo and must be reviewed normally, except `gradlew` and `gradlew.bat`. + +In consumer repositories, skip without comment any path matching: + +- `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` +- `.agents/**` (except `.agents/project.md`) +- `.claude/**`, `.idea/**`, `.junie/**` +- `.github/copilot-instructions.md` +- `buildSrc/**` (except `buildSrc/src/main/kotlin/module.gradle.kts`) +- `gradle/`, `gradlew`, `gradlew.bat`, `init-submodules` +- `.codecov.yml`, `.gitignore`, `gradle.properties`, `lychee.toml` +- `.github/workflows/` β€” unless the workflow was introduced by this repo + +[agents-repo]: https://github.com/SpineEventEngine/agents diff --git a/config b/config index 56b5c907..3210ca6c 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 56b5c9070ad0efcadc3a96256e4c4937b0528e4e +Subproject commit 3210ca6cc05c77ee59961721cf5c1c08327aa86b diff --git a/docs/project.md b/docs/project.md new file mode 100644 index 00000000..8cf191de --- /dev/null +++ b/docs/project.md @@ -0,0 +1,96 @@ +# Project: spine.io + +## Overview + +This repository is the source of the public [spine.io](https://spine.io) website β€” +the umbrella surface for the Spine SDK organisation. It serves a dual role: +a **documentation portal** (pulling the canonical content from the +[`documentation`][documentation] repository as a Hugo Module) and a +**marketing site** with landing pages, examples, and a `getting-help` page that +sells products through a Paygate-backed checkout. The site is built with +[Hugo Extended][hugo] and hosted on GitHub Pages. + +## Architecture + +**Role in the org:** application (a static website). Not a library, tool, or +Gradle plugin. The Gradle build present in the repo is a **convenience +wrapper only** β€” no JVM compilation happens here. + +**Composition** + +- `site/` β€” the Hugo project (config, layouts, assets, content shell). +- `config/` β€” the only git submodule, pointing at + [`SpineEventEngine/config`][config] (shared Spine build conventions). +- `build.gradle.kts` β€” exposes shell-backed tasks: + `:runSite`, `:buildSite`, and `:checkLinks`. + +**External pieces (consumed, not vendored)** + +- [`documentation`][documentation] β€” pulled in as a Hugo Module; provides the + documentation content. Code snippets shown on the rendered site are + embedded *there* (via `embed-code`) before being pulled in as a module. +- [`site-commons`][site-commons] β€” Hugo theme module shared across Spine + public sites (anchor icons, snackbars, etc.). + +**CI / deployment** (`.github/workflows/`) + +- `gh-pages.yml` β€” builds the Hugo site and deploys to GitHub Pages on push + to the default branch. This is the deploy path; treat changes that affect + it with care. +- `check-links.yml` β€” validates rendered-site links (mirrors the local + `checkLinks` task and the `check-links` skill). +- `gradle-wrapper-validation.yml` β€” guards the Gradle wrapper checksum on + PRs that touch the wrapper. + +## Constraints & guardrails + +- **Hugo Extended version is pinned.** Use `v0.161.1` or higher of the + *Extended* build. A mismatched/non-extended Hugo will break the theme + pipeline and SCSS. +- **The `config/` submodule is pinned to a specific commit.** Do not bump + it outside a dedicated update task β€” drive-by pointer changes silently + alter shared Spine build conventions. +- **Documentation content lives in the [`documentation`][documentation] + repo, not here.** Edits to documentation pages must be made there (or in + the relevant doc module). This repo only owns the site shell, landing + pages, and Spine-SDK-wide marketing surfaces. + +## Local development + +Two equivalent ways to run the site locally: + +1. Via Gradle (preferred β€” handles working directory and Hugo flags): + ```shell + ./gradlew :runSite # build + serve on http://localhost:1313 + ./gradlew :buildSite # build only, output under site/public + ./gradlew :checkLinks # validate links against the built site + ``` +2. Directly with Hugo, from inside `site/`: + ```shell + hugo server + ``` + +If Hugo Module fetches misbehave (stale cache, theme not updating), clear +the module cache and retry: + +```shell +hugo mod clean --all +``` + +The `getting-help` page integrates with **Paygate**. In dev it points at the +staging Paygate server; test the checkout flow with +[LHV sandbox cards](https://merchant.lhv.ee/help/en/articles/12807566-test-cards). +Changes touching that page have a real third-party integration surface β€” +verify the flow end-to-end, not just the rendered markup. + +## Authoring + +- Content authoring conventions: [`AUTHORING.md`](../AUTHORING.md). +- Embedding code samples into pages happens in the + [`documentation`][documentation] repo, not here β€” see its + [`EMBEDDING.md`](https://github.com/SpineEventEngine/documentation/blob/master/EMBEDDING.md). + +[hugo]: https://gohugo.io/getting-started/quick-start/#step-1-install-hugo +[documentation]: https://github.com/SpineEventEngine/documentation +[site-commons]: https://github.com/SpineEventEngine/site-commons +[config]: https://github.com/SpineEventEngine/config diff --git a/init-submodules b/init-submodules new file mode 100755 index 00000000..2ae143d8 --- /dev/null +++ b/init-submodules @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +################################################################################ +# +# Materialize the *config-managed* submodules a fresh working tree is missing, so +# agent assets resolve. +# +# `git worktree add` β€” and some shallow CI / cloud checkouts β€” populate only the +# superproject's own tracked files; registered submodules are left UNinitialized. +# In a Spine repo that means the `config` and `.agents/shared` submodules are +# empty, the `.agents/skills` -> `.agents/shared/skills` symlink dangles, and no +# agent skills, scripts, or guidelines can be found. +# +# This script is the bootstrap that has to run BEFORE `./config/pull`: `pull` +# lives inside the `config` submodule, so on a fresh worktree it does not yet +# exist. `init-submodules`, by contrast, is a plain tracked file at the repo root +# (distributed by `config`), so `git worktree add` always checks it out β€” it can +# therefore bring `config` itself into existence. +# +# It initializes ONLY submodules that are BOTH: +# +# * not yet checked out β€” those `git submodule status` marks with a leading `-`, +# at the commit the branch pins; and +# +# * config-managed β€” `config` itself (the bootstrap target `pull` lives inside, +# which carries no tracked `branch` in a consumer's `.gitmodules`), plus every +# submodule that declares a tracked `branch` in `.gitmodules`. This is exactly +# the rule `./config/pull` uses to decide what it floats, so the two scripts +# can never disagree about what is shared. +# +# Consumer-owned submodules (a Hugo theme, a vendored library, documentation +# examples, ...) declare no tracked branch and are deliberately left untouched. +# Because a `SessionStart` hook runs this script automatically on every session, +# initializing them would mean trying to clone β€” or failing on credentials for β€” +# a submodule this project does not manage, on every single start. They are +# skipped (noted on stderr). +# +# Submodules already present are left exactly as they are, so a tree that floated +# `config` / `.agents/shared` to a branch tip via `./config/pull` is never +# silently rewound to the pin. That makes the script idempotent and safe to run on +# every session start. +# +# It does NOT float submodules to their branch tips β€” run `./config/pull` +# afterwards for that. Unlike `pull`, it depends on no pre-existing `config` +# submodule, so it can bootstrap a bare worktree where `./config/pull` does not +# yet exist. +# +################################################################################ + +set -u + +root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0 +cd "$root" || exit 0 + +# Nothing to do in a repo without submodules. +[ -f .gitmodules ] || exit 0 + +# The set of config-managed submodule paths: `config` itself (handled specially β€” +# it carries no tracked branch, exactly as in `./config/pull`), plus every +# submodule declaring a tracked `branch` in `.gitmodules`. Mirrors `pull`'s rule. +config_managed_paths() { + printf '%s\n' 'config' + git config -f .gitmodules --get-regexp '^submodule\..*\.branch$' 2>/dev/null \ + | while read -r key _branch; do + name=${key#submodule.}; name=${name%.branch} + git config -f .gitmodules --get "submodule.$name.path" 2>/dev/null + done +} + +managed=$(config_managed_paths | sort -u) + +# `git submodule status` prefixes each uninitialized submodule with `-`; an +# initialized one starts with a space (at the pinned commit) or `+` (ahead of it). +# Act only on the `-` lines, taking the path from the second field, and only when +# that path is config-managed. +git submodule status 2>/dev/null | awk '$1 ~ /^-/ { print $2 }' | while read -r path; do + [ -n "$path" ] || continue + if printf '%s\n' "$managed" | grep -qxF -- "$path"; then + echo "init-submodules: initializing '$path'" + git submodule update --init --recursive -- "$path" \ + || echo "init-submodules: WARNING β€” could not initialize '$path' (offline?)." >&2 + else + echo "init-submodules: skipping consumer-owned '$path' (not config-managed)." >&2 + fi +done + +# Route Git hooks to the shared hooks directory so the secret-scan `pre-commit` +# hook is active even in a brand-new worktree, before `./config/pull` runs. The +# path floats with the `.agents/shared` submodule; until that submodule is +# initialized the hook simply does not fire (Git skips a missing hook). Set only +# when unset or already ours β€” never override a repo's own `core.hooksPath`. +desired_hooks=".agents/scripts/git-hooks" +current_hooks=$(git config --local --get core.hooksPath 2>/dev/null || true) +if [ -z "$current_hooks" ] || [ "$current_hooks" = "$desired_hooks" ]; then + git config --local core.hooksPath "$desired_hooks" \ + && echo "init-submodules: Git hooks routed to '$desired_hooks' (secret-scan pre-commit active)." +fi + +exit 0 diff --git a/lychee.toml b/lychee.toml index e544f9dc..6b7d3097 100644 --- a/lychee.toml +++ b/lychee.toml @@ -1,37 +1,66 @@ +# Lychee configuration for the `check-links` skill and the +# `Check Links` GitHub workflow. +# +# Mirrors the configuration used by the sibling +# `SpineEventEngine/SpineEventEngine.github.io` repository, with the same +# exclude list of flaky external endpoints. + # Exclude URLs and mail addresses from checking (supports regex). +# +# The entries are interpreted as Rust regexes, NOT shell globs: +# - `.` is escaped (`\.`) because an unescaped dot matches any character +# and would silently over-match (e.g. `fonts.googleapis.com` would +# also match `fontsXgoogleapisYcomZ`, masking real broken links). +# - `/.*` replaces the shell-style `/*` so a trailing path of any length +# matches (zero-or-more of any character, not zero-or-more slashes). +# TOML literal strings (single-quoted) are used so backslashes stay +# literal β€” basic strings (double quotes) would treat `\.` as an unknown +# escape and either error out or strip the backslash. exclude = [ # Links that return errors during checks, but work for the user. - "fonts.googleapis.com/*", - "fonts.gstatic.com/*", - "chromium.googlesource.com/*", - "chromereleases.googleblog.com/*", - "clients4.google.com/", - "ssl.gstatic.com/", - "googletagmanager.com/*", - "x.com/*", - - "stackoverflow.com/questions/*", - "openjdk.org/*", - "npmjs.com/*", - "raw.githubusercontent.com/SpineEventEngine/*", - "medium.com/*", - "levelup.gitconnected.com/*" + 'fonts\.googleapis\.com/.*', + 'fonts\.gstatic\.com/.*', + 'chromium\.googlesource\.com/.*', + 'chromereleases\.googleblog\.com/.*', + 'clients4\.google\.com/.*', + 'ssl\.gstatic\.com/.*', + 'googletagmanager\.com/.*', + 'x\.com/.*', + + 'stackoverflow\.com/questions/.*', + 'openjdk\.org/.*', + 'npmjs\.com/.*', + 'medium\.com/.*', + 'levelup\.gitconnected\.com/.*', ] +# Deliberately NOT excluded: `raw.githubusercontent.com/SpineEventEngine/*`. +# Catching broken refs into our own raw GitHub content is part of the reason +# this skill exists. If rate-limit flake appears, prefer narrowing the +# pattern to the specific failing path rather than re-adding the broad rule. +# Existing `max_retries`/`retry_wait_time` below should absorb transient 429s. # Exclude these filesystem paths from getting checked. exclude_path = [] -# Check links inside `` and `
` blocks as well
-# as Markdown code blocks.
+# Do NOT check links inside `` and `
` blocks or
+# Markdown code blocks (verbatim/code sections are excluded).
 include_verbatim = false
 
 # Verbose program output
 verbose = "error"
 
-# Don't show interactive progress bar while checking links.
-no_progress = false
+# Don't show the interactive progress bar while checking links. Both
+# consumers (CI and the `check-links` skill) run non-interactively, so
+# the progress bar adds noise to logs without value.
+no_progress = true
 
 # Comma-separated list of accepted status codes for valid links.
+# `429` (Too Many Requests) is accepted as a tradeoff against CI flake: some
+# providers rate-limit unauthenticated link probes from CI runners even when
+# the URL is healthy. Combined with `max_retries`/`retry_wait_time` below,
+# this avoids spurious failures on healthy-but-rate-limited URLs. The
+# downside: a URL that is genuinely broken AND returns `429` (rare) will
+# pass. Revisit this if false negatives accumulate.
 accept = ["200..=204", "429"]
 
 # Link caching to avoid checking the same links on multiple runs.

From 7c86e3ace81997f2686edca392ae382d584538d0 Mon Sep 17 00:00:00 2001
From: alexander-yevsyukov 
Date: Wed, 30 Sep 2026 21:48:05 +0100
Subject: [PATCH 5/8] Select CodeMatters (c) profile

---
 .idea/copyright/profiles_settings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
index 0b8f9a19..4f463a81 100644
--- a/.idea/copyright/profiles_settings.xml
+++ b/.idea/copyright/profiles_settings.xml
@@ -1,7 +1,7 @@
 
-  
+  
     
       
     
   
-
+
\ No newline at end of file

From b6b685228b4fc09b5b90decb1d48a2d77f69b59e Mon Sep 17 00:00:00 2001
From: alexander-yevsyukov 
Date: Wed, 30 Sep 2026 21:50:38 +0100
Subject: [PATCH 6/8] Apply CodeMatters (c) profile

---
 build.gradle.kts                              | 26 +++++-----------
 settings.gradle.kts                           | 28 +++++------------
 site/assets/js/main.js                        | 28 +++++------------
 site/assets/js/modules/forms/phone-number.js  | 26 +++++-----------
 site/assets/js/modules/go-top-button.js       | 28 +++++------------
 .../assets/js/modules/navbar/mobile-navbar.js | 28 +++++------------
 site/assets/js/modules/navbar/navbar.js       | 28 +++++------------
 .../assets/js/modules/navbar/sticky-navbar.js | 28 +++++------------
 site/assets/js/modules/paygate/purchases.js   | 28 +++++------------
 .../js/pages/checkout/charge-controller.js    | 26 +++++-----------
 .../js/pages/checkout/charge-request.js       | 26 +++++-----------
 .../js/pages/checkout/completed-page-url.js   | 26 +++++-----------
 site/assets/js/pages/checkout/completed.js    | 26 +++++-----------
 site/assets/js/pages/checkout/countries.js    | 26 +++++-----------
 .../checkout/delayed-request-controller.js    | 28 +++++------------
 site/assets/js/pages/checkout/dom.js          | 26 +++++-----------
 .../js/pages/checkout/form-controller.js      | 26 +++++-----------
 site/assets/js/pages/checkout/index.js        | 26 +++++-----------
 site/assets/js/pages/checkout/navigation.js   | 26 +++++-----------
 site/assets/js/pages/checkout/order-id.js     | 26 +++++-----------
 .../assets/js/pages/checkout/vat-countries.js | 26 +++++-----------
 .../js/pages/checkout/view-controller.js      | 26 +++++-----------
 site/assets/js/pages/faq.js                   | 28 +++++------------
 site/assets/js/pages/oss-licenses.js          | 26 +++++-----------
 site/assets/js/pages/pricing.js               | 26 +++++-----------
 site/assets/scss/base/_colors.scss            | 30 ++++++-------------
 site/assets/scss/base/_common.scss            | 30 ++++++-------------
 site/assets/scss/main.scss                    | 30 ++++++-------------
 site/assets/scss/modules/_buttons.scss        | 28 +++++------------
 site/assets/scss/modules/_call-to-action.scss | 30 ++++++-------------
 site/assets/scss/modules/_checkbox.scss       | 30 ++++++-------------
 site/assets/scss/modules/_feature-cards.scss  | 30 ++++++-------------
 site/assets/scss/modules/_footer.scss         | 28 +++++------------
 site/assets/scss/modules/_forms.scss          | 28 +++++------------
 site/assets/scss/modules/_go-top-button.scss  | 30 ++++++-------------
 site/assets/scss/modules/_hero.scss           | 30 ++++++-------------
 site/assets/scss/modules/_loader.scss         | 30 ++++++-------------
 site/assets/scss/modules/_message-modal.scss  | 30 ++++++-------------
 site/assets/scss/modules/_nav-search.scss     | 30 ++++++-------------
 site/assets/scss/modules/_navbar.scss         | 30 ++++++-------------
 .../scss/modules/_numbered-headings.scss      | 28 +++++------------
 .../assets/scss/modules/_redirect-screen.scss | 30 ++++++-------------
 site/assets/scss/modules/_result-panel.scss   | 28 +++++------------
 .../scss/modules/_subscribe-container.scss    | 30 ++++++-------------
 site/assets/scss/pages/_about.scss            | 30 ++++++-------------
 site/assets/scss/pages/_checkout.scss         | 28 +++++------------
 site/assets/scss/pages/_faq.scss              | 30 ++++++-------------
 site/assets/scss/pages/_getting-help.scss     | 28 +++++------------
 site/assets/scss/pages/_landing.scss          | 30 ++++++-------------
 site/assets/scss/pages/_licenses.scss         | 30 ++++++-------------
 site/assets/scss/pages/_privacy.scss          | 30 ++++++-------------
 .../scss/pages/blog/_blog-container.scss      | 30 ++++++-------------
 site/assets/scss/pages/blog/_blog.scss        | 30 ++++++-------------
 site/assets/scss/pages/blog/_index.scss       | 30 ++++++-------------
 site/assets/scss/pages/blog/_post-info.scss   | 30 ++++++-------------
 .../scss/pages/blog/_post-navigation.scss     | 30 ++++++-------------
 site/assets/scss/pages/blog/_post.scss        | 30 ++++++-------------
 .../pages/release-notes/_release-notes.scss   | 30 ++++++-------------
 .../scss/pages/release-notes/_sidenav.scss    | 30 ++++++-------------
 59 files changed, 483 insertions(+), 1191 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 5f2f24d4..4dc7a07f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 tasks.register("runSite") {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b10de4e9..fc931cce 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 rootProject.name = "spine.io"
diff --git a/site/assets/js/main.js b/site/assets/js/main.js
index d9199362..47b67ea6 100644
--- a/site/assets/js/main.js
+++ b/site/assets/js/main.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 import './modules/navbar/navbar';
diff --git a/site/assets/js/modules/forms/phone-number.js b/site/assets/js/modules/forms/phone-number.js
index 66bc4de0..ccaa75ab 100644
--- a/site/assets/js/modules/forms/phone-number.js
+++ b/site/assets/js/modules/forms/phone-number.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/modules/go-top-button.js b/site/assets/js/modules/go-top-button.js
index 0b34be66..7fc79d94 100644
--- a/site/assets/js/modules/go-top-button.js
+++ b/site/assets/js/modules/go-top-button.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/modules/navbar/mobile-navbar.js b/site/assets/js/modules/navbar/mobile-navbar.js
index 5f8001b5..7eebcf8b 100644
--- a/site/assets/js/modules/navbar/mobile-navbar.js
+++ b/site/assets/js/modules/navbar/mobile-navbar.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/modules/navbar/navbar.js b/site/assets/js/modules/navbar/navbar.js
index 50b92b23..4f2cafc5 100644
--- a/site/assets/js/modules/navbar/navbar.js
+++ b/site/assets/js/modules/navbar/navbar.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 import {toggleMobileNavbar} from './mobile-navbar';
diff --git a/site/assets/js/modules/navbar/sticky-navbar.js b/site/assets/js/modules/navbar/sticky-navbar.js
index 35552e56..0c88dc24 100644
--- a/site/assets/js/modules/navbar/sticky-navbar.js
+++ b/site/assets/js/modules/navbar/sticky-navbar.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/modules/paygate/purchases.js b/site/assets/js/modules/paygate/purchases.js
index a1b73be8..9e0237a1 100644
--- a/site/assets/js/modules/paygate/purchases.js
+++ b/site/assets/js/modules/paygate/purchases.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/charge-controller.js b/site/assets/js/pages/checkout/charge-controller.js
index 47944ca7..5a3002a8 100644
--- a/site/assets/js/pages/checkout/charge-controller.js
+++ b/site/assets/js/pages/checkout/charge-controller.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/charge-request.js b/site/assets/js/pages/checkout/charge-request.js
index e813ac1a..2187e620 100644
--- a/site/assets/js/pages/checkout/charge-request.js
+++ b/site/assets/js/pages/checkout/charge-request.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/completed-page-url.js b/site/assets/js/pages/checkout/completed-page-url.js
index 02544d7b..11e69d9b 100644
--- a/site/assets/js/pages/checkout/completed-page-url.js
+++ b/site/assets/js/pages/checkout/completed-page-url.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/completed.js b/site/assets/js/pages/checkout/completed.js
index fc473164..bbae1cc7 100644
--- a/site/assets/js/pages/checkout/completed.js
+++ b/site/assets/js/pages/checkout/completed.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/countries.js b/site/assets/js/pages/checkout/countries.js
index a3716411..edff45f7 100644
--- a/site/assets/js/pages/checkout/countries.js
+++ b/site/assets/js/pages/checkout/countries.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/delayed-request-controller.js b/site/assets/js/pages/checkout/delayed-request-controller.js
index 7ce7ad8b..5c1d34da 100644
--- a/site/assets/js/pages/checkout/delayed-request-controller.js
+++ b/site/assets/js/pages/checkout/delayed-request-controller.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/dom.js b/site/assets/js/pages/checkout/dom.js
index d14a34b8..e369e670 100644
--- a/site/assets/js/pages/checkout/dom.js
+++ b/site/assets/js/pages/checkout/dom.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/form-controller.js b/site/assets/js/pages/checkout/form-controller.js
index 84ce4461..8b80d132 100644
--- a/site/assets/js/pages/checkout/form-controller.js
+++ b/site/assets/js/pages/checkout/form-controller.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/index.js b/site/assets/js/pages/checkout/index.js
index 886646d4..1b59c40e 100644
--- a/site/assets/js/pages/checkout/index.js
+++ b/site/assets/js/pages/checkout/index.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/navigation.js b/site/assets/js/pages/checkout/navigation.js
index 9d29ed19..ce8b520a 100644
--- a/site/assets/js/pages/checkout/navigation.js
+++ b/site/assets/js/pages/checkout/navigation.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/order-id.js b/site/assets/js/pages/checkout/order-id.js
index 5a93bd20..0a3b8800 100644
--- a/site/assets/js/pages/checkout/order-id.js
+++ b/site/assets/js/pages/checkout/order-id.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/vat-countries.js b/site/assets/js/pages/checkout/vat-countries.js
index 3caf64fe..29aa5cd7 100644
--- a/site/assets/js/pages/checkout/vat-countries.js
+++ b/site/assets/js/pages/checkout/vat-countries.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/checkout/view-controller.js b/site/assets/js/pages/checkout/view-controller.js
index e97aa751..8614d886 100644
--- a/site/assets/js/pages/checkout/view-controller.js
+++ b/site/assets/js/pages/checkout/view-controller.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/faq.js b/site/assets/js/pages/faq.js
index bb13f613..aad9aead 100644
--- a/site/assets/js/pages/faq.js
+++ b/site/assets/js/pages/faq.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/oss-licenses.js b/site/assets/js/pages/oss-licenses.js
index 45e74576..1300f75f 100644
--- a/site/assets/js/pages/oss-licenses.js
+++ b/site/assets/js/pages/oss-licenses.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/js/pages/pricing.js b/site/assets/js/pages/pricing.js
index ee2d7aa4..9200c4a9 100644
--- a/site/assets/js/pages/pricing.js
+++ b/site/assets/js/pages/pricing.js
@@ -1,27 +1,15 @@
 /*
- * Copyright 2026, TeamDev. All rights reserved.
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 'use strict';
diff --git a/site/assets/scss/base/_colors.scss b/site/assets/scss/base/_colors.scss
index eecd9989..5f222922 100755
--- a/site/assets/scss/base/_colors.scss
+++ b/site/assets/scss/base/_colors.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 /* Extends colors from `site-commons`. */
diff --git a/site/assets/scss/base/_common.scss b/site/assets/scss/base/_common.scss
index 74380b83..ee4a0406 100755
--- a/site/assets/scss/base/_common.scss
+++ b/site/assets/scss/base/_common.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .intro-section {
diff --git a/site/assets/scss/main.scss b/site/assets/scss/main.scss
index e1c4c4f7..ac9a9dda 100644
--- a/site/assets/scss/main.scss
+++ b/site/assets/scss/main.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 /* Styles from `SpineEventEngine/site-commons`. */
diff --git a/site/assets/scss/modules/_buttons.scss b/site/assets/scss/modules/_buttons.scss
index cafcf833..19e55837 100644
--- a/site/assets/scss/modules/_buttons.scss
+++ b/site/assets/scss/modules/_buttons.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $btn-font-size: 14px;
diff --git a/site/assets/scss/modules/_call-to-action.scss b/site/assets/scss/modules/_call-to-action.scss
index 229e5a95..f2d41b52 100644
--- a/site/assets/scss/modules/_call-to-action.scss
+++ b/site/assets/scss/modules/_call-to-action.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .call-to-action {
diff --git a/site/assets/scss/modules/_checkbox.scss b/site/assets/scss/modules/_checkbox.scss
index 67805f41..599498e1 100644
--- a/site/assets/scss/modules/_checkbox.scss
+++ b/site/assets/scss/modules/_checkbox.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $checkbox-size: 18px;
diff --git a/site/assets/scss/modules/_feature-cards.scss b/site/assets/scss/modules/_feature-cards.scss
index 9a4788e8..97a7a5b7 100644
--- a/site/assets/scss/modules/_feature-cards.scss
+++ b/site/assets/scss/modules/_feature-cards.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $feature-card-shadow: 0 1px 3px 0 rgba($footer-color, .08);
diff --git a/site/assets/scss/modules/_footer.scss b/site/assets/scss/modules/_footer.scss
index 81ff1d47..49628f43 100755
--- a/site/assets/scss/modules/_footer.scss
+++ b/site/assets/scss/modules/_footer.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $footer-header-color: #54595c;
diff --git a/site/assets/scss/modules/_forms.scss b/site/assets/scss/modules/_forms.scss
index 6d78882e..c83493f4 100644
--- a/site/assets/scss/modules/_forms.scss
+++ b/site/assets/scss/modules/_forms.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $form-error-color: $red;
diff --git a/site/assets/scss/modules/_go-top-button.scss b/site/assets/scss/modules/_go-top-button.scss
index 2da9aba3..2d995a2c 100644
--- a/site/assets/scss/modules/_go-top-button.scss
+++ b/site/assets/scss/modules/_go-top-button.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 #go-top-button {
diff --git a/site/assets/scss/modules/_hero.scss b/site/assets/scss/modules/_hero.scss
index 60526c1c..949bebdc 100755
--- a/site/assets/scss/modules/_hero.scss
+++ b/site/assets/scss/modules/_hero.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .nav-hero-container {
diff --git a/site/assets/scss/modules/_loader.scss b/site/assets/scss/modules/_loader.scss
index b5751c64..01320b8a 100644
--- a/site/assets/scss/modules/_loader.scss
+++ b/site/assets/scss/modules/_loader.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .loader-container {
diff --git a/site/assets/scss/modules/_message-modal.scss b/site/assets/scss/modules/_message-modal.scss
index f0818b17..42983a3e 100644
--- a/site/assets/scss/modules/_message-modal.scss
+++ b/site/assets/scss/modules/_message-modal.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .message-modal {
diff --git a/site/assets/scss/modules/_nav-search.scss b/site/assets/scss/modules/_nav-search.scss
index 395e9ddf..44f5b407 100644
--- a/site/assets/scss/modules/_nav-search.scss
+++ b/site/assets/scss/modules/_nav-search.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $docsearch-icon-color: rgba(white, .54);
diff --git a/site/assets/scss/modules/_navbar.scss b/site/assets/scss/modules/_navbar.scss
index 1e04fa85..515a4b7a 100755
--- a/site/assets/scss/modules/_navbar.scss
+++ b/site/assets/scss/modules/_navbar.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .navigation-opened {
diff --git a/site/assets/scss/modules/_numbered-headings.scss b/site/assets/scss/modules/_numbered-headings.scss
index b3dbd781..31916850 100644
--- a/site/assets/scss/modules/_numbered-headings.scss
+++ b/site/assets/scss/modules/_numbered-headings.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 /*
diff --git a/site/assets/scss/modules/_redirect-screen.scss b/site/assets/scss/modules/_redirect-screen.scss
index 513786f8..29550b8f 100644
--- a/site/assets/scss/modules/_redirect-screen.scss
+++ b/site/assets/scss/modules/_redirect-screen.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .redirect-screen {
diff --git a/site/assets/scss/modules/_result-panel.scss b/site/assets/scss/modules/_result-panel.scss
index cad773db..9f6facfe 100644
--- a/site/assets/scss/modules/_result-panel.scss
+++ b/site/assets/scss/modules/_result-panel.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .error-pages {
diff --git a/site/assets/scss/modules/_subscribe-container.scss b/site/assets/scss/modules/_subscribe-container.scss
index ce178a92..09f12e08 100644
--- a/site/assets/scss/modules/_subscribe-container.scss
+++ b/site/assets/scss/modules/_subscribe-container.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .subscribe-container {
diff --git a/site/assets/scss/pages/_about.scss b/site/assets/scss/pages/_about.scss
index 25c110a9..84d08d80 100755
--- a/site/assets/scss/pages/_about.scss
+++ b/site/assets/scss/pages/_about.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .about {
diff --git a/site/assets/scss/pages/_checkout.scss b/site/assets/scss/pages/_checkout.scss
index e22a7b24..a97fd887 100644
--- a/site/assets/scss/pages/_checkout.scss
+++ b/site/assets/scss/pages/_checkout.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 @import 'libs/country-select/select2';
diff --git a/site/assets/scss/pages/_faq.scss b/site/assets/scss/pages/_faq.scss
index b32bcc9a..f34688d6 100644
--- a/site/assets/scss/pages/_faq.scss
+++ b/site/assets/scss/pages/_faq.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $icon-right-offset: 48px;
diff --git a/site/assets/scss/pages/_getting-help.scss b/site/assets/scss/pages/_getting-help.scss
index 01fa9200..9a23c0e5 100644
--- a/site/assets/scss/pages/_getting-help.scss
+++ b/site/assets/scss/pages/_getting-help.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 :root {
diff --git a/site/assets/scss/pages/_landing.scss b/site/assets/scss/pages/_landing.scss
index 13e228d6..f6b3ce1c 100644
--- a/site/assets/scss/pages/_landing.scss
+++ b/site/assets/scss/pages/_landing.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .landing-content-container {
diff --git a/site/assets/scss/pages/_licenses.scss b/site/assets/scss/pages/_licenses.scss
index 72b44b91..3d7ba138 100644
--- a/site/assets/scss/pages/_licenses.scss
+++ b/site/assets/scss/pages/_licenses.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 $panel-padding: 16px 56px;
diff --git a/site/assets/scss/pages/_privacy.scss b/site/assets/scss/pages/_privacy.scss
index 5810c566..4c4cd9cc 100644
--- a/site/assets/scss/pages/_privacy.scss
+++ b/site/assets/scss/pages/_privacy.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2026, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .privacy {
diff --git a/site/assets/scss/pages/blog/_blog-container.scss b/site/assets/scss/pages/blog/_blog-container.scss
index c34b6c66..a30bb5de 100644
--- a/site/assets/scss/pages/blog/_blog-container.scss
+++ b/site/assets/scss/pages/blog/_blog-container.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .blog-container {
diff --git a/site/assets/scss/pages/blog/_blog.scss b/site/assets/scss/pages/blog/_blog.scss
index 065bfeb2..b4f3bc1d 100755
--- a/site/assets/scss/pages/blog/_blog.scss
+++ b/site/assets/scss/pages/blog/_blog.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .blog {
diff --git a/site/assets/scss/pages/blog/_index.scss b/site/assets/scss/pages/blog/_index.scss
index 1d6f8dc2..1f988b54 100644
--- a/site/assets/scss/pages/blog/_index.scss
+++ b/site/assets/scss/pages/blog/_index.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 @import "blog-container";
diff --git a/site/assets/scss/pages/blog/_post-info.scss b/site/assets/scss/pages/blog/_post-info.scss
index a780ed1b..097e7637 100644
--- a/site/assets/scss/pages/blog/_post-info.scss
+++ b/site/assets/scss/pages/blog/_post-info.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .post-info {
diff --git a/site/assets/scss/pages/blog/_post-navigation.scss b/site/assets/scss/pages/blog/_post-navigation.scss
index 4674cf55..93b514e4 100644
--- a/site/assets/scss/pages/blog/_post-navigation.scss
+++ b/site/assets/scss/pages/blog/_post-navigation.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .post-navigation {
diff --git a/site/assets/scss/pages/blog/_post.scss b/site/assets/scss/pages/blog/_post.scss
index ff77a450..888b7407 100644
--- a/site/assets/scss/pages/blog/_post.scss
+++ b/site/assets/scss/pages/blog/_post.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .post {
diff --git a/site/assets/scss/pages/release-notes/_release-notes.scss b/site/assets/scss/pages/release-notes/_release-notes.scss
index eed069eb..6fc11785 100644
--- a/site/assets/scss/pages/release-notes/_release-notes.scss
+++ b/site/assets/scss/pages/release-notes/_release-notes.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 .release-notes {
diff --git a/site/assets/scss/pages/release-notes/_sidenav.scss b/site/assets/scss/pages/release-notes/_sidenav.scss
index 13e7b803..fbd6d463 100644
--- a/site/assets/scss/pages/release-notes/_sidenav.scss
+++ b/site/assets/scss/pages/release-notes/_sidenav.scss
@@ -1,27 +1,15 @@
-/*!
- * Copyright 2025, TeamDev. All rights reserved.
+/*
+ * Copyright 2026 CodeMatters, Lda.
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
  */
 
 // Extends styles from `scss/docs/modules/_sidenav.scss`.

From ae5f4fe93babf50152dc802661453342fa3db942 Mon Sep 17 00:00:00 2001
From: alexander-yevsyukov 
Date: Wed, 30 Sep 2026 21:59:12 +0100
Subject: [PATCH 7/8] Apply CodeMatters (c) profile

---
 site/layouts/_partials/about/clients.html     | 28 ++++++-------------
 site/layouts/_partials/about/intro.html       | 28 ++++++-------------
 site/layouts/_partials/about/libraries.html   | 28 ++++++-------------
 site/layouts/_partials/about/principles.html  | 28 ++++++-------------
 site/layouts/_partials/about/tools.html       | 26 +++++------------
 .../blog/functions/filter-posts.html          | 28 ++++++-------------
 site/layouts/_partials/blog/post-board.html   | 28 ++++++-------------
 site/layouts/_partials/blog/post-info.html    | 28 ++++++-------------
 site/layouts/_partials/blog/post-nav.html     | 28 ++++++-------------
 site/layouts/_partials/blog/post-tile.html    | 28 ++++++-------------
 .../_partials/components/call-to-action.html  | 28 ++++++-------------
 .../_partials/components/footer/columns.html  | 28 ++++++-------------
 .../components/footer/copyright.html          | 28 ++++++-------------
 .../_partials/components/footer/footer.html   | 28 ++++++-------------
 .../_partials/components/footer/logo.html     | 28 ++++++-------------
 .../_partials/components/go-top-button.html   | 28 ++++++-------------
 .../_partials/components/iso-date.html        | 28 ++++++-------------
 site/layouts/_partials/components/loader.html | 28 ++++++-------------
 .../_partials/components/nav-hero.html        | 28 ++++++-------------
 .../components/navbar/github-icon.html        | 28 ++++++-------------
 .../components/navbar/item-link.html          | 28 ++++++-------------
 .../_partials/components/navbar/logo.html     | 28 ++++++-------------
 .../components/navbar/mobile-subnav.html      | 28 ++++++-------------
 .../_partials/components/navbar/nav-item.html | 28 ++++++-------------
 .../components/navbar/nav-search.html         | 28 ++++++-------------
 .../_partials/components/navbar/navbar.html   | 28 ++++++-------------
 .../components/navbar/navigation.html         | 28 ++++++-------------
 .../_partials/components/redirect-screen.html | 28 ++++++-------------
 .../_partials/components/result-panel.html    | 28 ++++++-------------
 .../components/subscribe-container.html       | 28 ++++++-------------
 site/layouts/_partials/faq/item.html          | 28 ++++++-------------
 .../_partials/functions/get-root-section.html | 28 ++++++-------------
 .../comparable-services-footer.html           | 28 ++++++-------------
 .../_partials/getting-help/mailto-button.html | 28 ++++++-------------
 .../getting-help/proof-of-concept.html        | 28 ++++++-------------
 .../_partials/getting-help/services.html      | 28 ++++++-------------
 .../getting-help/support-section.html         | 28 ++++++-------------
 site/layouts/_partials/head/canonical.html    | 28 ++++++-------------
 .../functions/get-canonical-custom-page.html  | 28 ++++++-------------
 site/layouts/_partials/head/head.html         | 28 ++++++-------------
 .../_partials/head/microdata/get-address.html | 28 ++++++-------------
 .../_partials/head/microdata/get-emails.html  | 28 ++++++-------------
 .../_partials/head/microdata/get-faqs.html    | 28 ++++++-------------
 .../_partials/head/microdata/get-socials.html | 28 ++++++-------------
 .../head/microdata/get-structured-data.html   | 28 ++++++-------------
 .../head/microdata/structured-data.html       | 28 ++++++-------------
 site/layouts/_partials/head/opengraph.html    | 28 ++++++-------------
 site/layouts/_partials/head/robots.html       | 28 ++++++-------------
 site/layouts/_partials/head/seo.html          | 28 ++++++-------------
 .../_partials/head/styles/docsearch.html      | 28 ++++++-------------
 site/layouts/_partials/head/stylesheet.html   | 28 ++++++-------------
 site/layouts/_partials/head/title.html        | 28 ++++++-------------
 .../_partials/landing/feature-card.html       | 28 ++++++-------------
 site/layouts/_partials/landing/features.html  | 28 ++++++-------------
 site/layouts/_partials/landing/hero.html      | 28 ++++++-------------
 .../_partials/landing/steps/code-toggle.html  | 28 ++++++-------------
 .../landing/steps/hide-code-link.html         | 28 ++++++-------------
 .../_partials/landing/steps/step-1.html       | 28 ++++++-------------
 .../_partials/landing/steps/step-2.html       | 28 ++++++-------------
 .../_partials/landing/steps/step-3.html       | 28 ++++++-------------
 .../_partials/landing/steps/step-4.html       | 28 ++++++-------------
 .../_partials/landing/steps/steps.html        | 28 ++++++-------------
 .../layouts/_partials/oss-licenses/intro.html | 28 ++++++-------------
 .../_partials/oss-licenses/licenses.html      | 28 ++++++-------------
 .../functions/get-grouped-releases.html       | 28 ++++++-------------
 .../functions/get-release-years.html          | 28 ++++++-------------
 .../release-notes/functions/get-releases.html | 28 ++++++-------------
 .../_partials/release-notes/sidenav.html      | 28 ++++++-------------
 .../_partials/release-notes/three-column.html | 28 ++++++-------------
 .../_partials/scripts/body-scripts.html       | 28 ++++++-------------
 .../_partials/scripts/body/docsearch.html     | 28 ++++++-------------
 site/layouts/_partials/scripts/body/gtm.html  | 28 ++++++-------------
 .../_partials/scripts/body/recaptcha.html     | 28 ++++++-------------
 .../_partials/scripts/head-scripts.html       | 28 ++++++-------------
 .../_partials/scripts/head/cookie-notice.html | 28 ++++++-------------
 .../_partials/scripts/head/docsearch.html     | 28 ++++++-------------
 site/layouts/_partials/scripts/head/gtm.html  | 28 ++++++-------------
 77 files changed, 615 insertions(+), 1539 deletions(-)

diff --git a/site/layouts/_partials/about/clients.html b/site/layouts/_partials/about/clients.html
index b142717c..f5afd309 100644
--- a/site/layouts/_partials/about/clients.html
+++ b/site/layouts/_partials/about/clients.html
@@ -1,27 +1,15 @@
 
 
 
diff --git a/site/layouts/_partials/about/intro.html b/site/layouts/_partials/about/intro.html index 21a73f94..c2d90cf1 100644 --- a/site/layouts/_partials/about/intro.html +++ b/site/layouts/_partials/about/intro.html @@ -1,27 +1,15 @@
diff --git a/site/layouts/_partials/about/libraries.html b/site/layouts/_partials/about/libraries.html index 5a837772..70314453 100644 --- a/site/layouts/_partials/about/libraries.html +++ b/site/layouts/_partials/about/libraries.html @@ -1,27 +1,15 @@ {{ with .Page.GetPage "about/libraries.md" }} diff --git a/site/layouts/_partials/about/principles.html b/site/layouts/_partials/about/principles.html index fed138bd..ad6d31e7 100644 --- a/site/layouts/_partials/about/principles.html +++ b/site/layouts/_partials/about/principles.html @@ -1,27 +1,15 @@ {{ with .Page.GetPage "about/principles.md" }} diff --git a/site/layouts/_partials/about/tools.html b/site/layouts/_partials/about/tools.html index 554e1d2b..51d0f0f0 100644 --- a/site/layouts/_partials/about/tools.html +++ b/site/layouts/_partials/about/tools.html @@ -1,27 +1,15 @@
diff --git a/site/layouts/_partials/blog/functions/filter-posts.html b/site/layouts/_partials/blog/functions/filter-posts.html index c6ad59d6..7585dcc3 100644 --- a/site/layouts/_partials/blog/functions/filter-posts.html +++ b/site/layouts/_partials/blog/functions/filter-posts.html @@ -1,27 +1,15 @@ {{ $posts := where site.RegularPages "Type" "blog/post" }} diff --git a/site/layouts/_partials/blog/post-board.html b/site/layouts/_partials/blog/post-board.html index aebc977c..2bd1388a 100644 --- a/site/layouts/_partials/blog/post-board.html +++ b/site/layouts/_partials/blog/post-board.html @@ -1,27 +1,15 @@ {{ $posts := partial "blog/functions/filter-posts.html" . }} diff --git a/site/layouts/_partials/blog/post-info.html b/site/layouts/_partials/blog/post-info.html index b173f2b4..3e46ad50 100644 --- a/site/layouts/_partials/blog/post-info.html +++ b/site/layouts/_partials/blog/post-info.html @@ -1,27 +1,15 @@ {{ $context := .context }} diff --git a/site/layouts/_partials/blog/post-nav.html b/site/layouts/_partials/blog/post-nav.html index b4bf9260..48046316 100644 --- a/site/layouts/_partials/blog/post-nav.html +++ b/site/layouts/_partials/blog/post-nav.html @@ -1,27 +1,15 @@ {{ $posts := partial "blog/functions/filter-posts.html" . }} diff --git a/site/layouts/_partials/blog/post-tile.html b/site/layouts/_partials/blog/post-tile.html index 1e396a9c..8f796be8 100644 --- a/site/layouts/_partials/blog/post-tile.html +++ b/site/layouts/_partials/blog/post-tile.html @@ -1,27 +1,15 @@ {{ $postUrl := .RelPermalink }} diff --git a/site/layouts/_partials/components/call-to-action.html b/site/layouts/_partials/components/call-to-action.html index 530b328e..59f69ca2 100644 --- a/site/layouts/_partials/components/call-to-action.html +++ b/site/layouts/_partials/components/call-to-action.html @@ -1,27 +1,15 @@
diff --git a/site/layouts/_partials/components/footer/columns.html b/site/layouts/_partials/components/footer/columns.html index 65e64636..a4fa5747 100644 --- a/site/layouts/_partials/components/footer/columns.html +++ b/site/layouts/_partials/components/footer/columns.html @@ -1,27 +1,15 @@ {{ $columns := hugo.Data.footer.columns }} diff --git a/site/layouts/_partials/components/footer/copyright.html b/site/layouts/_partials/components/footer/copyright.html index dae5e4c5..b5ab9509 100644 --- a/site/layouts/_partials/components/footer/copyright.html +++ b/site/layouts/_partials/components/footer/copyright.html @@ -1,27 +1,15 @@ {{ $currentYear := now.Year }} diff --git a/site/layouts/_partials/components/footer/footer.html b/site/layouts/_partials/components/footer/footer.html index aea1a8f3..1be24290 100644 --- a/site/layouts/_partials/components/footer/footer.html +++ b/site/layouts/_partials/components/footer/footer.html @@ -1,27 +1,15 @@