From 950fe40d75295fcb1a4ec45e0c5d741188a783e5 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 21:11:55 +0100 Subject: [PATCH 1/9] Remove outdated task documents --- .agents/tasks/archive/api-discovery.md | 156 ------ .../archive/prohibit-automatic-commits.md | 92 ---- .agents/tasks/archive/prompt-caching-org.md | 165 ------- .../archive/raise-coverage-kover-migration.md | 449 ------------------ .agents/tasks/archive/raise-coverage.md | 283 ----------- .../setup-cross-tool-agent-instructions.md | 138 ------ .../validating-builder-validate.md | 0 .../tasks/buildsrc-gradle-review-findings.md | 303 ------------ .../tasks/cross-agent-skill-best-practices.md | 100 ---- .agents/tasks/enforce-max-line-length.md | 279 ----------- .agents/tasks/gradle-caching-plan.md | 200 -------- .agents/tasks/spine-task-group-constant.md | 104 ---- 12 files changed, 2269 deletions(-) delete mode 100644 .agents/tasks/archive/api-discovery.md delete mode 100644 .agents/tasks/archive/prohibit-automatic-commits.md delete mode 100644 .agents/tasks/archive/prompt-caching-org.md delete mode 100644 .agents/tasks/archive/raise-coverage-kover-migration.md delete mode 100644 .agents/tasks/archive/raise-coverage.md delete mode 100644 .agents/tasks/archive/setup-cross-tool-agent-instructions.md rename .agents/tasks/{ => archive}/validating-builder-validate.md (100%) delete mode 100644 .agents/tasks/buildsrc-gradle-review-findings.md delete mode 100644 .agents/tasks/cross-agent-skill-best-practices.md delete mode 100644 .agents/tasks/enforce-max-line-length.md delete mode 100644 .agents/tasks/gradle-caching-plan.md delete mode 100644 .agents/tasks/spine-task-group-constant.md diff --git a/.agents/tasks/archive/api-discovery.md b/.agents/tasks/archive/api-discovery.md deleted file mode 100644 index 1671f5eac1..0000000000 --- a/.agents/tasks/archive/api-discovery.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -slug: api-discovery -branch: improve-api-discovery -owner: claude -status: in-review -started: 2026-05-21 ---- - -## Goal - -Make Spine API discovery fast and token-efficient by directing agents -to read library sources from local sibling clones first, and from a -one-time-extracted sources-JAR cache otherwise — never via repeated -`unzip` against Gradle-cache JARs. - -## Context - -Investigation transcripts (see -`~/Desktop/gradle-caches-scanning-by-claude.png`) show agents running -dozens of `find ~/.gradle/caches` + `unzip -l` + `unzip -p` calls per -query. Each call decompresses the JAR; token usage is dominated by -path noise and JAR listings. The user keeps every Spine repo cloned -as a sibling under `/Users/sanders/Projects/Spine/`, so the raw -sources are already on disk for ~14 of the 16 Spine local deps and -just need to be reached directly. - -Detailed design in `~/.claude/plans/mellow-juggling-yeti.md`. - -## Plan - -- [x] Draft plan + design review (Plan agent) -- [x] Write task file -- [x] Implement `lib/common.sh` (shared bash helpers) -- [x] Implement `discover` (main entry) -- [x] Implement `extract-sources` (one-shot JAR extraction, race-safe) -- [x] Implement `clean-cache` (manual pruning) -- [x] Write `README.md` + `.gitignore` for `.agents/scripts/api-discovery/` -- [x] Write `SKILL.md` for `.agents/skills/api-discovery/` -- [x] Add one bullet to `CLAUDE.md` under Workflow Rules -- [x] Smoke tests (#1–#7 from plan) -- [x] Code-review fixes (six findings, see Log) -- [x] **Follow-up:** `update-sibling` script + skill workflow for STALE -- [ ] Human review / merge; delete file on merge to master - -## Follow-up: sibling auto-update on STALE - -Originally deferred under "Out of scope" in the plan. User asked for -it: when STALE fires, the agent should offer to refresh the sibling -clone so api-discovery returns up-to-date sources. Constraints from -the user's reply: - -- Pull only when the sibling is on its default branch (`master` or - `main`) — they explicitly use checked-out feature branches as a - staging area for "advancing multiple subprojects at the same time", - so a feature branch is *intentional* local state and must be left - alone. -- The action must be confirmed by the user, never autonomous. - -Design: - -- New script `update-sibling`: - - Resolves a sibling by bare name (under ``) or by - absolute path. - - Branch ∈ {`master`,`main`} + clean tree + tracked upstream - → `git pull --ff-only`. - - Any other branch → no-op, exit 0 with "using local code as-is". - - Detached HEAD / dirty tree / no upstream → distinct exit codes - (3 / 4 / 5) with descriptive stderr. - - Pull failure → exit 6. - - Never switches branches, never `--rebase`, never `--force`, never - fetches a branch the user does not track. -- SKILL.md gains a "When STALE fires" section: surface the warning, - ask the user, run `update-sibling` on consent, re-run `discover` - if a pull happened. -- README.md documents the new script + adds it to the Layout table. - -## Log - -- 2026-05-21 — drafted plan; Plan-agent reviewed and pushed back on - over-engineering (manifest, sibling repo, exit codes). Adopted - simplifications. -- 2026-05-21 — cache location: `/.agents/caches/api-discovery/` - with first-use bootstrap prompt (approve / alt root / non-cached). - Scripts and skill live in the consumer repo's `.agents/`. -- 2026-05-21 — implementation begins. -- 2026-05-21 — implementation complete. All 7 smoke tests pass. - Resolved all 24 Spine artifacts end-to-end (Base/Change/Logging KMP/ - multi-module ProtoData/Validation/Tool-base/Mc-java). Extension-cache - path tested with Jackson and Guava — concurrent extractions race - safely on atomic `mv` with no `.tmp.*` leftovers. `STALE` warning - fires for validation-java (declared .433 vs sibling .440). KMP - source sets (`src/commonMain`, `src/jvmMain`, …) recognized in - addition to plain `src/main`. Status flipped to `in-review` for - human merge; task file will be deleted on merge to `master`. -- 2026-05-21 — added `update-sibling` follow-up: a guarded - `git pull --ff-only` for stale Spine siblings. Branch ∈ - {`master`,`main`} + clean tracked tree + tracked upstream → pull; - any other branch → no-op exit 0 ("intentional local state"); - detached / dirty / no-upstream → distinct refusals (exit 3/4/5); - pull failure → exit 6. Uses `--untracked-files=no` so build - artifacts and editor scratch don't block pulls. SKILL.md and - scripts/README.md document the workflow; the agent must ask the - user before invoking. Verified all 8 paths: successful FF on a - synthetic master + upstream, no-op on `validation` (`address-issues` - branch), exit 4 on `base-libraries` (dirty), exit 1 on missing, - exit 2 on non-repo, exit 3 on detached HEAD, exit 5 on no upstream, - exit 1 on missing args. The `main` default branch is also accepted. -- 2026-05-21 — `update-sibling` code-review pass applied five fixes: - (a) README exit-0 row was missing the `already up-to-date` outcome. - (b) `usage()` exited `EX_FAIL` (1), conflating "bad invocation" with - "sibling not on disk". Added `EX_USAGE=64` (BSD `sysexits(3)`) - and routed `usage()` to it; `sibling not on disk` keeps exit 1. - (c) Reworded the dirty-tree guard comment: untracked files don't - block FF on their own, but a genuine overwrite conflict (upstream - adds a path that exists untracked locally) still surfaces via - git's own check as `EX_PULL_FAILED`. Original "no effect on - semantics" wording was misleading. - (d) Exit 0 conflated three outcomes (pulled / up-to-date / - skipped-branch) and the skill had to parse free-form English log - lines to tell them apart. Now each success path emits a single - stable stdout token (`pulled`, `up-to-date`, `skipped-branch`); - failure paths emit empty stdout. Stderr keeps the human text. - (e) `SKILL.md` rewritten around the token contract: the exit-code - table splits exit 0 into three token-keyed sub-rows, procedure - step 4 branches on the token (not stderr), and a new - `up-to-date` example was added. `README.md` exit-code table got - the same split plus an `EX_USAGE=64` row. - Smoke-tested all eight paths end-to-end: synthetic upstream FF emits - `pulled`, re-run on the same clone emits `up-to-date`, `validation` - on `address-issues` emits `skipped-branch`, `base-libraries` (dirty) - exits 4, missing path exits 1, non-repo exits 2, no-args/too-many - exit 64. All failure paths produce empty stdout — agent can never - misread an error message as a result token. -- 2026-05-21 — earlier code-review pass applied six fixes: - (1) `extract-sources`: pre-test `[ -e "$target" ]` plus post-mv - nested-debris cleanup. Previous version was unsafe because - `mv tmp target` into an existing directory silently moves tmp - INSIDE target on macOS/Linux instead of failing. - (2) `discover`: replaced `target=$(...); status=$?` with - `target=$(...) || exit $?` so `set -e` cannot terminate - between assignment and status check. - (3) `clean-cache`: added `prune_empty_parents()` and call it on - both removal and "no entries match" paths (skipped under - `--dry-run`). Empty `//` dirs are now - reclaimed. - (4) `read_declared_version`: anchored regex at line start (with - optional access modifier) to avoid matching `const val version` - strings in KDoc / comments / nested code. - (5) Removed dead `find_dep_file_for_artifact` (callers all use - `find_local_dep_file_for_artifact`). - (6) `find_local_dep_file_for_artifact`: validate artifact name - against `[A-Za-z0-9._-]` and ERE-escape it via new - `escape_ere()` before grep, blocking regex-metachar injection. - All seven smoke tests still pass; race-safety verified by parallel - extractions of `guava-testlib:33.5.0-jre` (both exit 0, no `.tmp.*` - remnants, no nested `target/v.tmp.PID/` debris). diff --git a/.agents/tasks/archive/prohibit-automatic-commits.md b/.agents/tasks/archive/prohibit-automatic-commits.md deleted file mode 100644 index ff067c505a..0000000000 --- a/.agents/tasks/archive/prohibit-automatic-commits.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -slug: prohibit-automatic-commits -branch: prohibit-automatic-commits -owner: claude -status: in-review -started: 2026-05-20 ---- - -## Goal - -Make it a durable, team-wide rule that AI agents (Claude Code main thread, -every subagent, every skill) MUST NOT run `git commit` (or other -history-writing git/gh operations) unless authorization is *explicit and -current*. Authorization comes from one of two sources only: - -1. The currently active skill's `SKILL.md` contains an explicit - `## Commit authorization` section. -2. The user's current prompt explicitly instructs the operation - (e.g. "commit this", "push the branch"). - -Agents must otherwise stage changes and stop, letting the user review and -decide. This preserves today's auto-commit behavior for `bump-version` -and `bump-gradle`, which will declare authorization in their SKILL.md. - -## Context - -- Today's pain: Claude Code commits routinely, even when the user wants - to review diffs locally first. -- The project's `.claude/settings.json` already has `Bash(git commit:*)` - in `permissions.ask`. That asks the user per-commit but does not - redirect agent behavior — the agent still proposes commits constantly. - The fix is at the *instruction* layer, not the permission layer. -- Skills that legitimately commit today: `bump-version`, `bump-gradle`. -- Skills that do not commit but prescribe commit messages for the human: - `dependency-update` (already says "Do not commit. Do not push."). -- The user accepted removal of the global `~/.claude/settings.json` hook - added earlier this session. Enforcement lives in `.agents/` instructions - only. - -## Plan - -- [x] **1. Add the canonical rule to `.agents/safety-rules.md`.** - Added section *Commits and history-writing*. Lists default (no - history writes), two authorization sources, the fallback behavior - (stage + show diff + stop), and the operations covered. Names the - `## Commit authorization` marker. - -- [x] **2. Surface the rule in `.agents/quick-reference-card.md`.** - Added one-line pointer to `safety-rules.md` → *Commits and - history-writing*. - -- [x] **3. Add a workflow rule to `CLAUDE.md`.** - Added bullet under *Workflow Rules* referencing - `.agents/safety-rules.md`. - -- [x] **4. Declare authorization in `bump-version/SKILL.md`.** - Added a top-level `## Commit authorization` section above the - Checklist: exactly one commit, stage only `version.gradle.kts`, - subject `` Bump version -> `` ``, no push/tag/amend. - -- [x] **5. Declare authorization in `bump-gradle/SKILL.md`.** - Added a top-level `## Commit authorization` section above the - Checklist: up to two commits (wrapper + dependency reports), exact - subjects, no push/tag/amend. - -- [x] **6. Cross-check the non-authorizing skills.** - `dependency-update/SKILL.md` already explicit ("Do not commit. Do - not push.") — left as is. `pre-pr/SKILL.md` does not commit — left - as is. Other skills scanned (see Log). - -- [x] **7. Verification.** See Log entry — all three grep checks pass. - -## Out of scope - -- Project `.claude/settings.json` `ask` rule for `Bash(git commit:*)`: - leave as defense-in-depth (zero cost when the agent obeys the rule). -- `~/.claude/settings.json` global hook: already reverted earlier this - session per user direction. - -## Log - -- 2026-05-20 — drafted, awaiting plan approval. -- 2026-05-20 — approved by user. Executed steps 1–6. -- 2026-05-20 — verification: - - `grep -RIn '^## Commit authorization' .agents/skills/` returns exactly - `bump-gradle/SKILL.md` and `bump-version/SKILL.md` ✓ - - `safety-rules.md` referenced from `CLAUDE.md`, `quick-reference-card.md`, - `bump-version/SKILL.md`, `bump-gradle/SKILL.md` ✓ - - Literal `git commit` strings live only in the two authorizing skills ✓ - - `dependency-update/SKILL.md` still says "Do not commit. Do not push."; - `pre-pr/SKILL.md` still writes a sentinel and does not commit ✓ -- Status: `in-review` — awaiting user sign-off, then delete on merge to master. diff --git a/.agents/tasks/archive/prompt-caching-org.md b/.agents/tasks/archive/prompt-caching-org.md deleted file mode 100644 index 71f0c4fb9a..0000000000 --- a/.agents/tasks/archive/prompt-caching-org.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -slug: prompt-caching-org -branch: improve-caching -owner: claude -status: in-review -started: 2026-05-24 -related-memories: [cache-warm-window, anthropic-api-caching] ---- - -## Goal - -Maximise Claude API prompt cache hit rates across the Spine GitHub organisation -(~40 sibling repos) so that repeated session starts and agent invocations read -from cache at 0.1× token cost rather than processing the full prompt fresh. - -## Context - -- Claude Code already applies automatic prompt caching to every API call it - makes. There is no single "enable" switch; the work is about raising the - cache hit rate and keeping it high. -- The `migrate` script overwrites `CLAUDE.md`, `.agents/`, `.claude/`, and - `buildSrc/` in each sibling repo with an exact copy from this repo. This - means all 40 repos hold byte-identical content after a `./config/pull` and - therefore share the same cache entry at any given config version. -- The `openai.yaml` files under each skill are FleetView UI interface metadata - only — they define display name and default prompt, not API call parameters. - `cache_control` cannot go there. -- No GitHub Actions workflow currently calls the Anthropic API directly. -- Current stable prefix: CLAUDE.md (≈ 900 tokens) + quick-reference card - (≈ 200 tokens) ≈ 1,100 tokens. - - This **clears** the 1,024-token minimum for Sonnet 4.6 / Opus. - - This **does not meet** the 4,096-token minimum for Haiku 4.5. -- The team memory system is empty; populating it will grow the stable prefix. -- Cache TTL defaults to 5 minutes. Sessions more than 5 minutes apart miss - the cross-session cache unless the extended 1-hour TTL is used. - -## Plan - -- [ ] **Step 0 — Diagnose why zero caching is happening and enable it** - - The Console Caching dashboard ("TeamDev Management OÜ", All workspaces) shows - no prompt caching in use — no `cache_control` blocks are being sent by any - caller. This is the highest-priority item; the remaining steps only add value - once caching is active. - - Sub-tasks: - - - **0a. Switch to Console OAuth on every developer machine** - - Raw API key auth loses per-developer identity (`email`, `orgId`, `orgName` - all null in `claude auth status`). Console OAuth preserves identity while - still billing to "TeamDev Management OÜ". - - **For each developer:** - 1. Remove `ANTHROPIC_API_KEY` from `~/.claude/settings.json` — it takes - precedence over OAuth in the auth stack and must be absent. - 2. Run `claude` → a browser window opens → log in with Console credentials - (the same account used at console.anthropic.com). - 3. Run `claude auth status` and confirm `email`, `orgId`, `orgName` are - populated. - - **For the org admin (Alexander):** - - Invite the second developer via Console → Settings → Members → Invite. - - Assign the "Developer" or "Claude Code" role. - - They accept the email invite, then follow the three steps above. - - - **0b. Enable 1-hour cache TTL on every developer machine** - - Console OAuth users get the **5-minute** default cache TTL — the 1-hour - TTL is only automatic for claude.ai subscription users. Add the opt-in - to `~/.claude/settings.json` on every developer machine: - - ```json - { - "env": { - "ENABLE_PROMPT_CACHING_1H": "1" - } - } - ``` - - Restart Claude Code after saving. This is the highest-impact change in - the entire plan — without it, cache entries expire every 5 minutes and - cross-session hits are rare. - - - **0c. Verify caching is active** — start a Claude Code session, make a - few turns, wait 2–3 minutes, then check Analytics → Usage in the Console - under "TeamDev Management OÜ". Non-zero `cache_creation_input_tokens` - confirms caching is active. Non-zero `cache_read_input_tokens` on a - subsequent session in the same hour confirms hits are occurring. - - - **0d. Investigate remote skill calls** — FleetView-managed remote skills - (the 7 skills with `openai.yaml`) make their own API calls through the - agent platform. Confirm whether those calls include `cache_control`; if - not, this may require configuration in the FleetView platform outside - this repo. - - Until steps 0a–0b are done on both developer machines, Steps 1–3 improve - future cache hygiene but produce limited cost savings. - -- ~~**Step 1 — Cache-hygiene team memory**~~ — *reverted 2026-05-25: the - batching guidance was too restrictive on `config` changes; removed - `.agents/memory/feedback/cache-hygiene.md` and its references.* - -- [x] **Step 2 — Post-migration cache-warm window (reference memory)** - - Create `.agents/memory/reference/cache-warm-window.md` documenting: - - Realistic concurrency is 1–2 developers working on different repos at the - same time, not the full fleet of 40. - - Default TTL is 5 minutes. If a second session starts within 5 minutes of - the first (on the same config version), it hits the warm entry rather than - writing a new one. - - Extended 1-hour TTL (available in direct API calls via - `cache_control: {ttl: "1h"}`) gives a wider window, at 2× write cost per - token — still profitable after even one hit within the hour. - - Update `.agents/memory/MEMORY.md` index. - -- [x] **Step 3 — API caching pattern reference memory (for future direct calls)** - - No workflow currently calls the Anthropic API directly, but when one is - added, developers need the pattern immediately. - - Create `.agents/memory/reference/anthropic-api-caching.md` documenting: - - Use `cache_control: {type: ephemeral}` on the system message block for - 5-minute TTL (1.25× write / 0.1× read). - - Use `cache_control: {type: ephemeral, ttl: "1h"}` for 1-hour TTL - (2× write / 0.1× read) — right for any workflow job spaced > 5 min apart. - - Place stable content (system instructions, skill definitions, shared - context) **before** any dynamic per-request content so the breakpoint - sits at the end of the stable prefix. - - Monitor: `usage.cache_read_input_tokens` should grow relative to - `usage.cache_creation_input_tokens` as the cache warms. - - Future: once direct API calls exist, consider a cache pre-warm job - triggered on push to `master` — calls the API with `max_tokens: 0` and - `cache_control: {ttl: "1h"}` so the first session after a config change - hits rather than writes. - - Update `.agents/memory/MEMORY.md` index. - -- [x] **Step 4 — API workspace consolidation (already confirmed — verify stays true)** - - A cache entry is visible only to API calls made with a key from the **same - Anthropic workspace** (a named sub-group within your Anthropic Console - organisation). Two requests using keys from different workspaces never share - cache, even if they send identical prompts. - - **Current state (confirmed):** "TeamDev Management OÜ" has a single default - workspace (Environments list is empty). Both developers use Console API keys - from this organisation. Both developers share the same cache pool — no action - needed today. - - **Keep true as the team grows:** do not create separate Environments per - developer or per project unless cache isolation is intentional. Any new API - key issued for a new caller (GitHub Actions, scripts, new developer) should - be issued from the same workspace. - -## Log - -- 2026-05-24 — drafted from codebase audit; awaiting review and approval -- 2026-05-24 — revised per review: added buildSrc to migrate list, removed dependency-audit caching step, corrected concurrency description to 1–2 repos, dropped pre-warm workflow step (pattern preserved in Step 3 memory), clarified per-workspace semantics in Step 4 -- 2026-05-24 — added Step 0 after Console Caching dashboard confirmed zero prompt caching in use; workspace confirmed as single default (no Environments), both devs on same org — Step 4 updated to reflect confirmed state -- 2026-05-24 — Step 0 revised: root cause identified — Console API key users get 5-min TTL by default vs 1-hour for subscription users; ENABLE_PROMPT_CACHING_1H=1 is the fix; warning on first launch is one-time approval only -- 2026-05-24 — Step 0 revised again: switched to Console OAuth (not raw API key) to preserve per-developer identity; ENABLE_PROMPT_CACHING_1H=1 still required for Console OAuth users (5-min TTL default applies to all non-subscription auth) -- 2026-05-24 — Steps 1–4 complete: three memory files created, MEMORY.md index updated, workspace consolidation confirmed; Step 0 remains in progress (Console OAuth setup and verification) -- 2026-05-25 — reverted Step 1: removed `cache-hygiene.md` and references — batching guidance was too restrictive for `config` development cadence diff --git a/.agents/tasks/archive/raise-coverage-kover-migration.md b/.agents/tasks/archive/raise-coverage-kover-migration.md deleted file mode 100644 index 268ba07806..0000000000 --- a/.agents/tasks/archive/raise-coverage-kover-migration.md +++ /dev/null @@ -1,449 +0,0 @@ ---- -slug: raise-coverage-kover-migration -branch: coverage-tests-skill -owner: claude -status: in-review -started: 2026-05-30 ---- - -## Goal - -Extend the `raise-coverage` skill with a precondition step that migrates a -consumer repo from the vanilla JaCoCo Gradle plugin to JetBrains Kover (with -`useJacoco(version = Jacoco.version)` so the engine and the JaCoCo-format XML -remain unchanged). The skill becomes **Kover-only** post-migration. Adjacent -JaCoCo-distribution code in `config`'s `buildSrc` is marked deprecated with a -pointer to the Kover path; no behaviour change for repos that still consume -the deprecated script plugins. - -## Context - -The skill in `.agents/skills/raise-coverage/` currently supports two coverage -frontends: Kover (consumer repos) and raw JaCoCo (the `config` repo itself). -Per user decision, the skill collapses to Kover-only and gains a Step 0 that -detects vanilla JaCoCo, proposes a one-shot repo-wide migration, waits for -approval, applies it, smoke-checks, and only then resumes the normal flow. - -`config` itself has no production Kotlin/Java code, so the skill never runs -*on* `config`. However, `config` distributes vanilla-JaCoCo infrastructure -(`buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts`, `jacoco-kmm-jvm.gradle.kts`, -and the `JacocoConfig` helper) that consumer repos may still apply — these -get deprecation annotations + a runtime `logger.warn` (script plugins only) -but stay on disk so existing consumers keep building. `Jacoco.kt` (engine -version) and `Kover.kt` (plugin version) are unchanged because Kover uses -`useJacoco(version = Jacoco.version)`. - -### Decisions (locked — do not re-litigate) - -| # | Decision | -|---|----------| -| 1 | Invocation: implicit precondition (Step 0 of the skill) | -| 2 | Scope: repo-wide, proposed once | -| 3 | Trigger: always, unless Kover is already applied everywhere | -| 4 | Both plugins applied: always remove `jacoco`, keep Kover | -| 5 | KMP: JVM-target-only migration via Spine's `kmp-module` script plugin — uses the same `::koverXmlReport` task and `build/reports/kover/report.xml` path as Kotlin-JVM, because `kmp-module` configures only Kover's `total` report (no named variants, so no `koverXmlReport` task is generated) | -| 6 | CI / `.codecov.yml` / scripts: all updated to Kover paths and tasks | -| 7 | Plugin/engine version: reference `io.spine.dependency.test.Kover` / `Jacoco`; do not hardcode | -| 8 | Translation fidelity: best-effort full; flag unmappable constructs | -| 9 | Post-migration: skill flow is Kover-only | -| 10 | No-coverage case: silent install, no approval gate | -| 11 | Verification: smoke check only (`koverXmlReport` exists + parses) | -| 12 | `JacocoConfig` and `jacoco-*.gradle.kts`: mark deprecated, do not delete | - -### Verified facts (from Phase 1 exploration) - -- `buildSrc/src/main/kotlin/jvm-module.gradle.kts:54` applies Kover; `:99` - configures it with `useJacoco(version = Jacoco.version)` and XML-on-check. -- `buildSrc/src/main/kotlin/kmp-module.gradle.kts:74` and `:181` mirror the - above for KMP modules. -- `buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/` contains the - JaCoCo-pipeline classes to be deprecated: `JacocoConfig`, `TaskName`, - `CodebaseFilter`, `FileFilter`, `FileExtensions`, `FileExtension`, - `PathMarker`. -- `scripts/upload-artifacts.sh:38` references `build/jacoco*` directories. -- `.github/workflows/*.yml` in `config` contain no JaCoCo references. - -## Implementation - -Paths are relative to the `config` repo root. - -### Area A — Skill files - -#### A1. `.agents/skills/raise-coverage/SKILL.md` - -- Frontmatter `description: >`: drop "Kover or JaCoCo frontend"; phrase the - report as "Kover's JaCoCo-format XML report"; add: "Before anything else, - ensures the repo is on Kover — if vanilla JaCoCo is detected, proposes a - one-shot repo-wide migration and waits for approval." -- Body "with JaCoCo" → "with **Kover**'s JaCoCo-format XML report". -- **Scope** bullet rewritten Kover-only. Per-module task - `::koverXmlReport`; XML at - `/build/reports/kover/report.xml`. Same task and path on KMP - modules configured by Spine's `kmp-module` script plugin — it sets up only - Kover's `total` report (no named variants, so no - `koverXmlReport` task is generated). Strip every "raw JaCoCo" / - `jacocoTestReport` / `jacocoRootReport` reference as a normal mode. -- **Insert new `## Step 0 — Ensure Kover`** between `## Inputs` and - `## Workflow`. Three branches: - 1. Kover applied everywhere → silently proceed. - 2. Nothing applied anywhere → silently install Kover; record - "Migration: installed Kover" in the final Report; no approval gate. - 3. Vanilla JaCoCo in ≥1 module → emit a proposal and **wait for approval**. -- **Proposal output structure** (Markdown, in this order): - 1. **Detected** — every module applying `jacoco` / `JacocoPlugin` / - `JacocoConfig.applyTo` / a `jacoco-*.gradle.kts`; annotate "vanilla - only" vs. "JaCoCo+Kover both"; note any root `jacocoRootReport`. - 2. **Plan** — file edits with paths (per-module `build.gradle.kts`, root - `build.gradle.kts`, `.codecov.yml`, `.github/workflows/*.yml`, - `scripts/*.sh`). - 3. **Translation notes** — applicable rows from the migrate-to-kover table. - 4. **Manual-review surfaces** — items the user must decide on. - 5. **Smoke check that will follow** — the E1 commands. - 6. Close with: "Confirm to apply, or call out anything to change first." -- **Wait for approval.** No writes until explicit "go" / "yes" / "apply". - On adjustment requests, regenerate the proposal and wait again. -- **Apply** per `references/migrate-to-kover.md`; log `edited ` per - file. Any unresolved manual-review surface → stop ("needs your call on - ``"). -- **Smoke check** per E1. Failure → stop; do not fall through. -- **Resume** at Workflow step 1. -- **Workflow step 1** (`--triage`): "per-module `koverXmlReport`, or the - aggregate `jacocoRootReport` in `config`" → "per-module `koverXmlReport`, - or the root-level Kover aggregation task `koverXmlReport` if the repo - wires one". -- **Workflow step 2**: "Detect the coverage frontend and run …" → "Run - `::koverXmlReport`" — same task on JVM and KMP modules configured - by Spine's `kmp-module` script plugin (no named variants → no - `koverXmlReport` task). Drop "either way" from the XML-parsing - sentence. -- **Workflow step 6**: "(`koverXmlReport` or `jacocoTestReport`)" → - `::koverXmlReport`. -- **Report**: add a **Migration** section (emitted only when Step 0 did work). -- **Safety**: add a bullet — "No migration without explicit approval when - vanilla JaCoCo is detected. Silent install only when *no* frontend is in - place." - -#### A2. `.agents/skills/raise-coverage/references/coverage-signals.md` - -- Top blurb: drop the "two frontends" paragraph; replace with a single - paragraph stating that the engine is JaCoCo and the Spine convention is - Kover with `useJacoco(version = Jacoco.version)`; the XML is - JaCoCo-format either way. -- Delete the entire **"Two coverage frontends"** section (current lines - 11–57). Replace with **"Where the report lives"** — per-module task / XML - path, root-level aggregation paths, `find` recipe if unknown. -- **"Generating a report"**: drop the two JaCoCo `./gradlew` lines; keep - Kover. Same task on KMP modules configured by Spine's `kmp-module` script - plugin — no `koverXmlReport` task is generated unless a named - `variant("…") { … }` block is declared. -- **"Extracting gaps for a class"**: drop "or the jacoco path". -- **"KMP / Kotlin-JVM modules"**: keep first sentence; delete the second - sentence about `jacoco-*-jvm` exec data paths. -- **Verification**: "the **same** report task" → `::koverXmlReport`. -- **Codecov triage tier appendix**: keep verbatim. - -#### A3. `.agents/skills/raise-coverage/references/migrate-to-kover.md` (new) - -Eight sections: - -1. **Purpose** — one paragraph; link to - and the - `migrations/migration-to-0.7.0.html` migration guide. -2. **Detection signals** — grep patterns per module's `build.gradle.kts`: - - Plugin block: `^\s*jacoco\b` inside `plugins {`, `id\("jacoco"\)`, - `apply\(\)`, `apply\(plugin = "jacoco"\)`. - - Script plugin: `apply\(plugin = "jacoco-`. Covers `jacoco-kotlin-jvm`, - `jacoco-kmm-jvm`. - - `JacocoConfig`: `JacocoConfig\.applyTo` or imports of - `io.spine.gradle.report.coverage.JacocoConfig`. - - DSL: `jacoco\s*\{`, `jacocoTestReport\s*\{`, - `jacocoTestCoverageVerification\s*\{`, `tasks\.named\("jacoco`. - - Kover applied: `org.jetbrains.kotlinx.kover`, or `id("jvm-module")` / - `id("kmp-module")` (both auto-apply Kover). - - Root aggregation: `jacocoRootReport`. - - Multi-module walk: parse `settings.gradle.kts` for `include(...)`; - honor `project(":x").projectDir = file(...)` overrides. -3. **Per-module migration**: - - Add Kover via `id(Kover.id)` if `buildSrc` is on the classpath; - otherwise `id("org.jetbrains.kotlinx.kover") version ""`. - If `jvm-module` / `kmp-module` is applied, skip the add (log "already - via jvm-module"). - - Strip `jacoco` from `plugins { }`. - - **Translation table**: - | JaCoCo construct | Kover / action | - |---|---| - | `jacoco { toolVersion = Jacoco.version }` | drop (engine version → root `useJacoco(...)`) | - | `jacoco { toolVersion = }` | **flag** | - | `reports { xml=true; html=true; csv=false }` | `kover { reports { total { xml { onCheck.set(true) }; html { } } } }` | - | `executionData.setFrom(...)` | **flag** (Kover-managed) | - | `sourceDirectories.setFrom(...)` | **flag** (Kover-inferred) | - | `classDirectories.setFrom(...)` (Kotlin-JVM/KMP `walkBottomUp`) | drop; **flag** if non-Kotlin | - | `reports.xml.outputLocation.set(...)` | **flag** (fixed path) | - | `tasks.named("jacocoTestReport") { dependsOn(...) }` | rewrite to `tasks.named("koverXmlReport")` | - | `violationRules { rule { limit { counter; value; minimum } } }` | `kover { reports { verify { rule { … } } } }`; counter map: INSTRUCTION/BRANCH/LINE = same; METHOD → INSTRUCTION + flag; CLASS → flag | - - `jvm-module` / `kmp-module` simplification: Kover already there; - migration becomes "remove JaCoCo bits only". -4. **Root-level aggregation**. Trigger: source had `jacocoRootReport` **or** - >1 module to aggregate. - - Apply Kover at root (skip if root applies `jvm-module`). - - `dependencies { kover(project(":foo")); … }` per consuming module. - - `kover { useJacoco(version = Jacoco.version); reports { total { xml { onCheck.set(true) }; html { } } } }`. - - Mirror per-module `violationRules` to root `verify { rule { … } }` - only if the source repo had a root-level rollup. -5. **CI / `.codecov.yml` / scripts** — substitutions: - - Workflows: `jacocoTestReport` → `koverXmlReport`; `jacocoRootReport` → - root `koverXmlReport`; - `build/reports/jacoco/test/jacocoTestReport.xml` and - `build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml` → - `build/reports/kover/report.xml`. - - `.codecov.yml`: same path tokens; preserve `ignore` and - `coverage.status` verbatim. - - `scripts/*.sh`: `build/jacoco*` glob → `build/reports/kover`; **flag** - scripts reading raw `.exec` paths (e.g., - `scripts/upload-artifacts.sh:38` in `config`). -6. **KMP recipe**. JVM-only target. Same task and XML path as Kotlin-JVM: - `::koverXmlReport` and `/build/reports/kover/report.xml`. - Spine's `kmp-module` script plugin configures only Kover's `total` report, - so no `koverXmlReport` task is generated — CI / `.codecov.yml` - must reference the unsuffixed path. A `koverXmlReportJvm` task would only - appear if a named `variant("jvm") { … }` block were declared, which - `kmp-module` does not do. -7. **Manual-review surfaces** (flag and ask): - - Custom `sourceDirectories` / `classDirectories` on `jacocoTestReport` - (the `jacoco-*-jvm.gradle.kts` pattern). - - Custom `reports.xml.destination` / `outputLocation`. - - Custom `executionData` paths. - - Indirect `jacoco.toolVersion` (property files, `gradle.properties`). - - Multi-pipeline setups where both reports are intentional. - - `JacocoConfig.applyTo(rootProject)` outside `config`. - - Custom convention plugins applying JaCoCo under a non-`jacoco-…` name. - - Non-JVM KMP targets — out of scope (decision 5). -8. **References** — links to the Kover migration guide and DSL docs. - -#### A4. `.agents/skills/raise-coverage/agents/openai.yaml` - -- `short_description`: "Migrate to Kover if needed, then generate unit tests - to close coverage gaps." -- `default_prompt`: rewrite to name Step 0 — detect setup; if vanilla - JaCoCo found, propose a one-shot repo-wide migration and wait for - approval; if nothing applied, install Kover silently. After smoke check, - run the existing flow (localize from Kover XML; propose; approve; - generate Kotest / Truth stubs; re-verify). End with: "Tests-only changes - do not require a version bump." - -### Area B — BuildSrc deprecations in `config` - -#### B1. `buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts` and `jacoco-kmm-jvm.gradle.kts` - -Runtime behaviour unchanged. Two edits per file: - -1. Below the copyright header, insert a `// DEPRECATED:` block: "This - script plugin distributes vanilla JaCoCo. New code should apply - `jvm-module` (or `kmp-module`), which configures Kover via - `useJacoco(version = Jacoco.version)`. The `raise-coverage` skill - migrates existing consumers. Kept so older consumer repos continue to - build; will be removed in a future release." -2. Immediately before `plugins { jacoco }`, add - `logger.warn("'jacoco-kotlin-jvm' is deprecated; use 'jvm-module' which applies Kover. See .agents/skills/raise-coverage/references/migrate-to-kover.md.")` - (use `jacoco-kmm-jvm` and `kmp-module` in the KMM file). - -#### B2. `@Deprecated` on JaCoCo-pipeline classes under `buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/` - -All `DeprecationLevel.WARNING`, no `ReplaceWith` (the replacement is a -multi-block DSL). - -- `JacocoConfig.kt` — class `JacocoConfig`: "Use Kover's root-level - aggregation (`dependencies { kover(project(...)) }` plus - `kover { useJacoco(version = Jacoco.version); reports { total { xml { onCheck = true } } } }`) - instead of `JacocoConfig.applyTo(...)`. The `raise-coverage` skill - performs this migration automatically." -- `TaskName.kt` — enum: "Internal task-name catalog for the deprecated - `JacocoConfig` pipeline; Kover uses its built-in task names - (`koverXmlReport`, etc.). Removed when `JacocoConfig` is." -- `CodebaseFilter.kt` — class: "Used only by the deprecated `JacocoConfig`. - Kover infers source sets and respects `kover { filters { excludes { … } } }`." -- `FileFilter.kt` — object: same wording as `CodebaseFilter`. -- `FileExtensions.kt` — `@file:Deprecated("Path/extension helpers used only by the deprecated `JacocoConfig` pipeline. Removed when `JacocoConfig` is.")`. -- `PathMarker.kt` enum and `FileExtension.kt` enum — same wording as - `FileExtensions`. - -For in-package self-references, accept the warnings or apply -`@Suppress("DEPRECATION")` on the call sites. - -#### B3. Untouched - -`buildSrc/src/main/kotlin/io/spine/dependency/test/Jacoco.kt` and `Kover.kt` -remain unchanged. `Jacoco.kt` is the engine-version source used by -`kover { useJacoco(version = Jacoco.version) }`. - -### Area C — Adjacent docs in `config` - -#### C1. `.agents/tasks/raise-coverage.md` - -- Decisions table "Coverage engine" row: "exposed via Kover - (`koverXmlReport`); when a consumer repo still has vanilla JaCoCo, the - skill migrates it first; Codecov deferred." -- "Verified facts": replace the **JaCoCo paths** bullet with a Kover-paths - bullet (`/build/reports/kover/report.xml` — same on KMP via - `kmp-module`, which configures only the `total` report; root aggregation - `build/reports/kover/report.xml`; Kover manages exec data internally). -- Plan: append `- [ ]` "Kover-only pivot: implement - `.agents/tasks/raise-coverage-kover-migration.md`". -- Log: append a 2026-05-30 entry summarising the pivot. -- Keep `status: draft`. - -#### C2. `.claude/commands/raise-coverage.md` - -- Description: "Ensure the repo is on Kover (migrate from JaCoCo if - needed), then localize coverage gaps and generate missing unit tests for - a module or path." -- Order bullet: `::jacocoTestReport` → `::koverXmlReport`. - `--triage` line: "ranked JaCoCo gap report" → "ranked Kover gap report". -- After the Skill bullet, add: "First-time setup: the skill enforces - Kover. If vanilla JaCoCo is found anywhere, the skill proposes a - repo-wide migration and **waits for your approval**. See - `references/migrate-to-kover.md`." -- `allowed-tools`: unchanged. - -#### C3. Other docs - -- `.agents/_TOC.md` — no edit. -- `.agents/tasks/buildsrc-gradle-review-findings.md` — above each item - referencing `jacoco-kmm-jvm.gradle.kts` or `JacocoConfig.kt` (lines - 77–78, 101–103, 121–122, 169–172, 201–203), insert one line: - "**Superseded by Kover-only migration**: these files are deprecated; do - not invest in micro-rewrites. See - `.agents/skills/raise-coverage/references/migrate-to-kover.md`." -- `scripts/upload-artifacts.sh` — add `# DEPRECATED:` comment line above - line 38 (`JACOCO_REPORTS=…`) pointing at the migration reference. No - behaviour change. -- `scripts/buildSrc-migration.kts`, `migrate`, `lychee.toml` — no edits. - -### Area E — Verification - -#### E1. Step 0 smoke check (post-migration) - -1. Run `./gradlew ::koverXmlReport --quiet` on the smallest leaf - JVM migrated module; if the root was touched, also `./gradlew - koverXmlReport --quiet`. -2. Assert `/build/reports/kover/report.xml` exists, is non-empty, - and the first non-XML-declaration line contains `Kt` synthetic) and emits both `FQN` and - `FQN$*` exclusion patterns to cover nested classes. The skill's - migration step now rewrites `JacocoConfig.applyTo` → `KoverConfig.applyTo` - instead of deleting the call. `./gradlew -p buildSrc compileKotlin` - green. diff --git a/.agents/tasks/archive/raise-coverage.md b/.agents/tasks/archive/raise-coverage.md deleted file mode 100644 index b0fc517ef1..0000000000 --- a/.agents/tasks/archive/raise-coverage.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -slug: raise-coverage -branch: coverage-tests-skill -owner: claude -status: draft -started: 2026-05-29 -updated: 2026-05-30 ---- - -## Goal - -Stand up a reusable, Spine-native agent skill — **`raise-coverage`** — that raises -JVM test coverage by localizing uncovered lines/branches with JaCoCo and -generating policy-compliant unit tests. The skill lives in `config` and -propagates to all ~50 repos via `./config/pull`. Success = the skill and its -wrappers are authored, distribution is wired, and the full loop has been -dry-run on one `base-libraries` module locally (nothing committed). - -## Context - -Scoped in Claude Chat; the produced `SKILL.md` was lost and the two surviving -files (`coverage-signals.md`, `coverage-tests.md`) are **drafts** to be -rewritten, not shipped. Clarification produced eight decisions that narrow and -simplify the original draft. - -### Decisions (locked — do not re-litigate) - -| Decision | Choice | -|---|---| -| Skill name | **`raise-coverage`** (verb-noun, like `write-docs` / `bump-version`) | -| Workflow | localize → propose cases → **wait for approval** → generate → verify; plus read-only `--triage` | -| Coverage engine | **JaCoCo engine**, exposed via **Kover** (`koverXmlReport`); when a consumer repo still has vanilla JaCoCo, the skill migrates it first (see `raise-coverage-kover-migration.md`); Codecov deferred | -| Test language | **Kotlin + Kotest** for every new test, regardless of the language of the code under test; class names use the **`Spec`** suffix (e.g. `AbstractSourceFileSpec`). Truth proto extension is reachable only when Kotest cannot express the assertion | -| Scratch dir | reuse existing `tmp/` → `tmp/base-libraries` (already gitignored via `/tmp`) | -| Done bar | full loop on one `base-libraries` module, **local, nothing committed** | -| Codex parity | include `agents/openai.yaml` | -| Branch/task | keep branch `coverage-tests-skill`; `git mv` task file → `raise-coverage.md` | - -### Verified facts (baked into the deliverables) - -- **Skill system**: source of truth is `.agents/skills//`; `.claude/skills` - is a **symlink** to `../.agents/skills` (author once). `.claude/commands/.md` - is the slash-command wrapper. Action skills also ship `agents/openai.yaml`. -- **Distribution**: `migrate` (sourced by `pull`) copies the whole `.agents` + - `.claude` tree, so a new skill auto-propagates — **except** a Hugo-only-repo - prune block that strips JVM-specific skills. `raise-coverage` is JVM-specific - and must be added to that block. -- **Test stack**: Kotest `6.1.11` (`io.kotest:kotest-assertions-core`), Google - Truth `1.4.4` (`truth` + `truth-proto-extension`), JUnit `6.0.3` - (`org.junit:junit-bom`), JaCoCo `0.8.14` (`Jacoco.kt`, already bumped in the - working tree). -- **House test idiom**: JUnit Jupiter structure (`@Test` / `@Nested` / - `@DisplayName` / `@TempDir`) + **Kotest matchers** (`shouldBe`, `shouldThrow`, - `shouldContainExactlyInAnyOrder`). NOT pure Kotest specs. (Verified in - `buildSrc/src/test/.../FileExtensionsTest.kt`.) -- **Kover paths** (post-migration): per-module - `/build/reports/kover/report.xml` — same on Kotlin-JVM and KMP - modules configured by Spine's `kmp-module` script plugin, which sets up - only the `total` Kover report (no named variants, so no - `koverXmlReport` task is generated). Root aggregation (when wired): - `build/reports/kover/report.xml`. Kover manages exec data internally — no - raw `.exec` paths are exposed to consumers. -- **Runtime successor to `JacocoConfig`**: - `io.spine.gradle.report.coverage.KoverConfig.applyTo(rootProject)`. Applies - the Kover plugin at the root, wires `dependencies { kover(project(...)) }` - for every Kover-enabled subproject, pins - `useJacoco(version = Jacoco.version)`, and pushes the union of generated-class - FQNs as Kover excludes into both per-module and root reports. Preserves the - generated-code-filtering behavior previously provided by `CodebaseFilter` so - the skill does not hallucinate gaps for generated classes. -- **Never test**: generated code (any path containing `generated`), `examples`, - existing `test` sources. `.codecov.yml` scope is `src/main/**` only. -- **No version bump** for tests-only changes (contrast with other action skills, - which end by invoking `/version-bumped`). - -## Deliverables (file-by-file) - -1. **`.agents/skills/raise-coverage/SKILL.md`** — frontmatter `name` + - `description: >`. Sections: Goal/scope (**Kover-only**, using its - JaCoCo-format XML report; human `src/main` only) · Inputs (`$ARGUMENTS` - = `:module` | path | `--triage`) · Step 0 — Ensure Kover (read-only - under `--triage`; silent install when no coverage plugin is in place; - **propose-and-wait** when vanilla JaCoCo is detected, per - `references/migrate-to-kover.md`) · Workflow (1 resolve target → - 2 localize gaps from Kover's `report.xml` → 3 read code-under-test + - existing tests + collaborators → 4 **propose test-case list and WAIT**; - `--triage` stops here with the ranked report → 5 generate → 6 verify) · - Test-generation rules (stubs not mocks; **Kotlin + Kotest** universal — - JUnit Jupiter structure with Kotest assertions, written in Kotlin even - when the code under test is Java; class names use the **`Spec`** suffix; - `truth-proto-extension` is reachable only as an isolated fallback for - Protobuf assertions Kotest cannot express; cover edge cases; scaffold - `when`/sealed branches; skip generated/excluded paths) · Report format · - Safety (`--triage` is read-only; migration requires approval when - vanilla JaCoCo is detected; never weaken a `.codecov.yml` target; never - add a mocking dependency; read-only until step-4 approval; no version - bump for tests-only). - -2. **`.agents/skills/raise-coverage/references/coverage-signals.md`** — JaCoCo - mechanics (rewritten from the draft, corrected to this repo): per-module vs - `jacocoRootReport`; the real report paths above; XML structure and gap rules - (`ci==0` uncovered line, `mb>0` partial branch); `xmllint`/Python extraction - recipes; the generated-code exclusion; `.codecov.yml` scope; KMP - source-set/exec-data variants. Ends with a short **"Future: Codecov triage - tier"** appendix capturing the deferred two-tier design. - -3. **`.agents/skills/raise-coverage/agents/openai.yaml`** — Codex parity - (`interface.display_name` / `short_description` / `default_prompt`). - -4. **`.claude/commands/raise-coverage.md`** — thin slash-command wrapper. - `allowed-tools: Read, Edit, Write, Grep, Glob, Bash(./gradlew:*), - Bash(git status:*), Bash(find:*)` (dropped `curl`/`WebFetch` — Codecov - deferred). Body points at the skill, states the order, honors `testing.md` + - `coding-guidelines.md`, notes no version bump for tests-only. - -5. **`.agents/_TOC.md`** — add `23. [Raise test coverage](skills/raise-coverage/SKILL.md)`. - -6. **`migrate`** — add `raise-coverage` to the Hugo-only prune block: - `rm -rf ../.agents/skills/raise-coverage`, - `rm -rf ../.claude/skills/raise-coverage`, - `rm -f ../.claude/commands/raise-coverage.md` (mirrors existing JVM-skill entries). - -7. **`.agents/tasks/raise-coverage.md`** — this file (`git mv` from - `improve-test-coverage.md`). - -> Dropped from the original plan: the standalone "install README". We author -> directly in `config`, so there is no separate install step — placement is -> documented here. - -## Reusable test harness - -```bash -# from the config repo root -mkdir -p tmp -git clone --recurse-submodules https://github.com/SpineEventEngine/base-libraries tmp/base-libraries -cd tmp/base-libraries && git submodule update --init --recursive - -# lay down the published .agents/.claude baseline -./config/pull - -# overlay the in-development skill on top of the baseline -cp -R /.agents/skills/raise-coverage .agents/skills/ -cp /.claude/commands/raise-coverage.md .claude/commands/ -# (.claude/skills → ../.agents/skills symlink resolves the skill automatically) -``` - -`pull` fetches the **published** config from master, so it won't contain -`raise-coverage` yet — the overlay-copy injects the in-dev version. Then execute -the skill's procedure against one module and verify. - -## Plan - -- [x] Housekeeping: `git mv` task file → `raise-coverage.md`; `TaskCreate` to track. -- [x] Author `SKILL.md`, `references/coverage-signals.md`, `agents/openai.yaml`, - and the `.claude/commands/raise-coverage.md` wrapper. -- [x] Wire-up: add `_TOC.md` entry; add `raise-coverage` to the `migrate` prune block. -- [x] Harness + pilot: cloned `base-libraries` into `tmp/`, ran `./config/pull`, - overlaid the skill. Localized gaps via Kover (`koverXmlReport`), then closed - `EnvironmentType.equals()`/`hashCode()` with a Java+Truth test — the gap went - to zero (4 tests green, nothing committed). Hardened `SKILL.md` + - `coverage-signals.md` for the Kover frontend and for non-actionable - (inline / unreachable) gaps surfaced by the pilot. -- [x] Review: `review-docs` over the new Markdown; sanity-check the `migrate` - edit; confirm nothing staged in `tmp/base-libraries`. -- [x] Kover-only pivot: implemented `.agents/tasks/raise-coverage-kover-migration.md`. - Dropped dual-frontend logic, added Step 0 migration gate to `SKILL.md`, - and deprecated the JaCoCo-pipeline `buildSrc` helpers (`JacocoConfig`, - `CodebaseFilter`, `FileFilter`, `FileExtensions`, `FileExtension`, - `PathMarker`, `TaskName`, plus the `jacoco-*-jvm.gradle.kts` script - plugins). Authored `KoverConfig.kt` as the live runtime successor — - preserves the generated-code exclusion previously provided by - `CodebaseFilter` and wires per-subproject `kover(project(...))` - aggregation, with the union of generated FQNs pushed into both - per-module and root reports. Three review/fix cycles - (`kotlin-review` + `review-docs`, parallel) all returned APPROVE; - `./gradlew -p buildSrc compileKotlin` green. -- [x] Re-run pilot with the updated skill against `tmp/base-libraries` - (`--triage` first, then close one fresh gap). Step 0 correctly detected - the hybrid state (root vanilla JaCoCo + subprojects already on Kover via - `module` script plugin), emitted the structured proposal, and on approval - migrated the root build (drop `jacoco` plugin; swap `JacocoConfig` → - `KoverConfig`; lift the call out of `gradle.projectsEvaluated`). Smoke - check passed: `:base:koverXmlReport` and root `koverXmlReport` both - produce JaCoCo-format XML with ``; generated - `OptionsProto` correctly excluded. Closed gaps in - `io.spine.code.fs.AbstractSourceFile` with a Java + Truth + `@TempDir` - stub (6 cases): coverage delta 20 missed LINE → 2, 2 missed BRANCH → 0 - (LINE 91 %, BRANCH 100 %). The residual 2 missed LINE are non-actionable - (`throw newIllegalStateException(...)` where the helper throws - internally) — pattern added to `references/coverage-signals.md`. -- [ ] On merge: flip `status: done` and delete this task file per the - `.agents/tasks/` lifecycle. - -## Verification (the done bar) - -- Files: `_TOC.md` resolves; `.claude/skills/raise-coverage/SKILL.md` resolves - through the symlink; `openai.yaml` parses. -- Distribution: confirm the `migrate` prune block lists `raise-coverage` so - Hugo-only repos won't receive it. -- End-to-end ✅: in `tmp/base-libraries`, `:environment:koverXmlReport` ran; - `EnvironmentTypeTest` (Java + Truth, 4 tests) compiles and passes; re-parsing - `build/reports/kover/report.xml` shows `EnvironmentType` `missedLINE`/ - `missedBRANCH` → 0 (was 2 / 1). **Nothing committed to base-libraries.** - -## Risks / notes - -- **Build feasibility**: `base-libraries` must build here (JDK + network; first - build slow). Mitigation: build only the pilot module's test+report tasks. If it - can't build, fall back to the analysis dry-run for the pilot and flag it — the - four authored files still ship. -- **Pre-existing working-tree changes**: `Jacoco.kt` (→`0.8.14`) and the old task - file are already modified. Leave `Jacoco.kt` alone (align docs to 0.8.14); only - `git mv`/rewrite the task file. -- **No commits/pushes** anywhere unless explicitly authorized. - -## Log - -- 2026-05-29 — Shortlisted candidate skills; selected `clear-solutions` as the - structural base with Kotest/taxonomy donors. -- 2026-05-29 — Recorded original decisions (mixed Java+Kotlin, Codecov + JaCoCo, - author in `config` for all repos); drafted SKILL/reference/command/install. -- 2026-05-30 — Re-scoped with the user. Renamed skill `coverage-tests` → - **`raise-coverage`**; **deferred Codecov** (JaCoCo-only v1); confirmed - Kotlin→Kotest / Java→Truth; chose `tmp/` scratch dir; set the done bar to a - local full-loop on a `base-libraries` module; added `openai.yaml` + `migrate` - prune-block deliverables. Verified the test stack and JaCoCo report paths from - `buildSrc`. Rewrote this task file from the plan; awaiting review. -- 2026-05-30 — Pivot to Kover-only. Decided to collapse the skill to a single - frontend and add a Step 0 migration gate. When the skill detects vanilla - JaCoCo, it proposes a one-shot repo-wide migration and waits for approval; - when nothing is in place, it installs Kover silently. The vanilla-JaCoCo - helpers under `buildSrc` (`jacoco-kotlin-jvm` / `jacoco-kmm-jvm` script - plugins, `JacocoConfig` aggregator and its support classes) are deprecated, - not deleted, so existing consumers keep building. Full implementation plan - moved to `.agents/tasks/raise-coverage-kover-migration.md`. -- 2026-05-30 — Built the four files + wire-up; ran the `base-libraries` pilot. - Findings that reshaped the skill: (1) consumer repos expose coverage through - **Kover** (`koverXmlReport`, JaCoCo engine, JaCoCo-format XML) — not the - per-module `jacocoTestReport` the draft assumed — so the skill is now - frontend-aware (Kover or raw JaCoCo). (2) `inline`/`reified` functions and - unreachable guards read as uncovered but are **non-actionable** (a passing test - for `parse` left `Parse.kt` `ci=0`), so the skill now filters them out. - Demonstrated true closure on `EnvironmentType.equals()`/`hashCode()` - (Java + Truth). `review-docs` running. -- 2026-05-30 — Tightened the test-generation policy in `SKILL.md` and the - Codex `default_prompt`. New tests are always written in **Kotlin** (JUnit - Jupiter + Kotest assertions), regardless of whether the code under test is - Kotlin or Java, and test class names use the **`Spec`** suffix - (`AbstractSourceFileSpec`, not `AbstractSourceFileTest`). This matches the - `*Spec.kt` convention already in use across `base-libraries` and removes - the dual-language Truth-for-Java branch that the prior pilot followed by - accident. Truth (`truth-proto-extension`) stays reachable only for - Protobuf assertions Kotest cannot express. -- 2026-05-30 — Re-ran the pilot end-to-end on `tmp/base-libraries`. Step 0 - detected vanilla JaCoCo at the root + Kover already applied in subprojects, - proposed the repo-wide migration, and on approval applied it. One lifecycle - gotcha surfaced: `KoverConfig.applyTo(root)` cannot live inside - `gradle.projectsEvaluated { … }` (Kover registers `afterEvaluate` hooks at - apply time). Documented in `references/migrate-to-kover.md` §3 and in the - `KoverConfig` class KDoc. Closed `AbstractSourceFile` with a Java + Truth - stub (6 cases via `@TempDir`); coverage went 20/2 missed LINE/BRANCH → 2/0. - Residual 2 LINE remained `mi=10 ci=0` despite passing tests — root cause is - the Spine `Exceptions.newIllegalStateException` idiom (declared to return - the exception but throws internally), making `throw helper(...)` lines - unreachable for JaCoCo's downstream probe. Added the pattern to - `references/coverage-signals.md` as a third non-actionable category. -- 2026-05-30 — Kover-only pivot landed. Implemented per - `.agents/tasks/raise-coverage-kover-migration.md`: collapsed the skill to a - single Kover frontend with a Step 0 migration gate that proposes a repo-wide - JaCoCo → Kover migration (waits for approval) when vanilla JaCoCo is - detected. Deprecated the `JacocoConfig` aggregator and its supporting helpers - (`CodebaseFilter`, `FileFilter`, `FileExtensions`, `FileExtension`, - `PathMarker`, `TaskName`) plus the `jacoco-*-jvm.gradle.kts` script plugins - — kept on disk so existing consumers keep building. Authored `KoverConfig.kt` - as the live runtime successor (preserves generated-code exclusion via Kover - `filters { excludes { classes(...) } }` derived from source dirs containing - `generated/`; wires `kover(project(...))` aggregation for every Kover-enabled - subproject; pins the JaCoCo engine version). Doc set updated: - `references/migrate-to-kover.md` is the new mechanical recipe; - `SKILL.md` got the Step 0 proposal protocol; `coverage-signals.md` rewritten - Kover-only. Reviewed across three cycles (`kotlin-review` + `review-docs` - in parallel) — all APPROVE, zero outstanding comments; - `./gradlew -p buildSrc compileKotlin` green. Nothing committed. diff --git a/.agents/tasks/archive/setup-cross-tool-agent-instructions.md b/.agents/tasks/archive/setup-cross-tool-agent-instructions.md deleted file mode 100644 index 02672e2c8f..0000000000 --- a/.agents/tasks/archive/setup-cross-tool-agent-instructions.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -slug: setup-cross-tool-agent-instructions -branch: improve-caching -owner: claude -status: in-review -started: 2026-05-24 ---- - -# Task: Consolidate Agent Instructions into AGENTS.md - -## Goal - -Move universal agent instructions from `CLAUDE.md` into `AGENTS.md` so that -Claude Code, GitHub Copilot, and Codex all read identical rules from a single -source. Reduce `CLAUDE.md` to a thin wrapper that imports `AGENTS.md` plus a -small Claude Code-specific section. - -## Current state - -Both files already exist with real content. - -**`AGENTS.md`** currently has: -- Orientation — `project.md` reference, link to `.agents/_TOC.md` -- Commit and history safety — full rule (authoritative) -- Other safety rules — compile check, no auto-deps, no analytics, no reflection -- Moving files — `git mv` rule - -**`CLAUDE.md`** currently has: -- Project Guidelines — quick-reference-card, `project.md`, `jvm-project.md`, - skills, TOC -- Workflow Rules — `EnterPlanMode`, task planning, `api-discovery` skill, - commit rule (duplicate of AGENTS.md) -- Memory — team memory (`.agents/memory/`) + per-developer (auto-memory) -- Verification & Quality -- Core Principles -- Task Flow — plan writing, `ExitPlanMode`, `TaskCreate` -- Final Rule - -## Content split - -**Universal — move to `AGENTS.md`:** - -| Section | Notes | -|---|---| -| Project Guidelines (project.md, jvm-project.md, skills, TOC) | All agents need this orientation | -| Memory → team-shared store only (`.agents/memory/`) | Codex/Copilot have no auto-memory; the team store is universal | -| Verification & Quality | Universal engineering standards | -| Core Principles | Universal | -| Task Flow items 1, 4, 5, 6 (plan write, verify, update memory, delete task) | Universal; omit items 2–3 (ExitPlanMode/TaskCreate) | - -**Claude Code-specific — keep in `CLAUDE.md` only:** - -| Item | Why Claude-only | -|---|---| -| `EnterPlanMode` / `ExitPlanMode` | Claude Code SDK tools | -| `api-discovery` skill / never unzip JARs | Gradle cache path is machine-local | -| Per-developer auto-memory | Claude Code built-in feature | -| `TaskCreate` for live status | Claude Code SDK tool | -| Final Rule meta-note | Claude Code session advice | - -## Steps - -### 1. Expand `AGENTS.md` - -Add the universal sections to `AGENTS.md` after the existing content. Do not -duplicate the commit rule — it is already there. Resulting sections in order: - -1. Welcome / Orientation *(already exists — update to include quick-reference-card and skills references)* -2. Commit and history safety *(already exists — keep as-is)* -3. Other safety rules *(already exists — keep as-is)* -4. Moving files *(already exists — keep as-is)* -5. **Memory** — team-shared store only; omit the per-developer store -6. **Verification & Quality** -7. **Core Principles** -8. **Task planning** — write plan to `.agents/tasks/.md`; verify before marking done; delete task file on merge - -Keep `AGENTS.md` under 120 lines. Every line must change agent behaviour. - -### 2. Rewrite `CLAUDE.md` as a thin wrapper - -Replace the current content with: - -```markdown -@AGENTS.md - -## Claude Code-specific notes - -- Use Plan mode (`EnterPlanMode`) for architecture, refactoring, multi-file - changes, or lengthy documentation. Show the plan (`ExitPlanMode`) before - implementing. -- Track live progress with `TaskCreate`. -- Before reading library source code from `~/.gradle/caches`, follow the - `api-discovery` skill — never `unzip` JARs directly. -- Per-developer memory lives in the built-in auto-memory dir. Use it for - personal preferences, ephemeral project state, and per-machine resources. - Litmus test: *would a teammate benefit from this next month?* → repo. - Otherwise → auto-memory. -- This is living team memory. Update it regularly and keep it concise - (<120 lines / ~2.5k tokens). -``` - -### 3. Verify `.github/copilot-instructions.md` - -This file already exists. Confirm it contains an explicit reference to `AGENTS.md` -at the repository root, a pointer to `project.md` for repo context, and the -universal "Do not suggest" safety rules. Add the `AGENTS.md` reference if absent. - -### 4. Verify the setup - -Run these checks and report results: - -- `AGENTS.md` exists at repo root and is under 120 lines (`wc -l AGENTS.md`). -- `CLAUDE.md` first non-empty line is `@AGENTS.md`. -- `.github/copilot-instructions.md` exists and references `.agents/project.md`. -- All modified files are tracked by git (no relevant "Untracked files" in - `git status`). - -### 5. Commit - -Stage only the files modified by this task. Use this commit message: - -``` -refactor: consolidate agent instructions into AGENTS.md - -Move universal rules (orientation, memory, quality, principles, task -planning) from CLAUDE.md into AGENTS.md so Codex, Copilot, and Claude -Code all read from a single source. CLAUDE.md becomes a thin @AGENTS.md -wrapper plus Claude Code-specific notes. -``` - -## Acceptance Criteria - -- Editing `AGENTS.md` is the only required change to update agent behaviour - across all three tools. -- No universal instruction content exists only in `CLAUDE.md`. -- `AGENTS.md` is under 120 lines. -- `CLAUDE.md` first non-empty line is `@AGENTS.md`. -- All checks in step 4 pass. diff --git a/.agents/tasks/validating-builder-validate.md b/.agents/tasks/archive/validating-builder-validate.md similarity index 100% rename from .agents/tasks/validating-builder-validate.md rename to .agents/tasks/archive/validating-builder-validate.md diff --git a/.agents/tasks/buildsrc-gradle-review-findings.md b/.agents/tasks/buildsrc-gradle-review-findings.md deleted file mode 100644 index 8890e49419..0000000000 --- a/.agents/tasks/buildsrc-gradle-review-findings.md +++ /dev/null @@ -1,303 +0,0 @@ ---- -slug: buildsrc-gradle-review-findings -branch: gradle-review-skill -owner: claude -status: draft -started: 2026-05-29 -related-memories: [] ---- - -## Goal - -Apply the findings of the `/gradle-review` run against all sources -under `buildSrc/` in `config` (2026-05-29). The review found three -categories of issues: Spine mandate violations (`group = "spine"` / -`description`), Gradle correctness issues (`Provider.get()` at -configuration time, eager `Configuration`/`FileCollection` APIs that -discard task wiring), and a layer of Should-fix items around -cacheability annotations, `@PathSensitivity`, and lazy task -realisation. The work is large enough that it ships as a separate PR -from the `gradle-review-skill` branch. - -## Context - -- Review transcript: ran `/gradle-review` on the full `buildSrc/` - tree on 2026-05-29 in the `gradle-review-skill` branch. Verdict: - `REQUEST CHANGES`. -- Authoritative rules: - - [`.agents/skills/gradle-review/spine-task-conventions.md`](../skills/gradle-review/spine-task-conventions.md). - - [`.agents/skills/gradle-review/practices/tasks.md`](../skills/gradle-review/practices/tasks.md) - (ingested from the Gradle "Best practices for tasks" page). -- `SpineTaskGroup` constant already exists at - `buildSrc/src/main/kotlin/io/spine/gradle/SpineTaskGroup.kt` and is - the recommended replacement for the bare string `"spine"` (see - [`.agents/tasks/spine-task-group-constant.md`](spine-task-group-constant.md)). -- Pre-flight ripgrep confirmed: **0 hits** for `tasks.create(`, - `@CacheableTask`, `@DisableCachingByDefault`, `@UntrackedTask`, - `@PathSensitivity`, and the various `@Input*` / `@Output*` - annotations across `buildSrc/src/main/kotlin/**`. - -## Plan - -### A. Spine mandate — `group = SpineTaskGroup.name` (Must fix) - -The string `"spine"` does not appear as a task group anywhere in -`buildSrc/`. Two sub-patterns to fix: - -**A.1 — Tasks that set `group` to a non-Spine value.** - -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/testing/Tasks.kt:82, 96` - — `FastTest` / `SlowTest`: replace `group = "Verification"` - with `group = SpineTaskGroup.name`. -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/dart/task/DartTasks.kt:111-114` - — drop the `Group` object (`"Dart/Build"`, `"Dart/Publish"`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/JsTasks.kt:111-117` - — drop the `Group` object (`"JavaScript/Assemble"`, - `"JavaScript/Check"`, `"JavaScript/Clean"`, `"JavaScript/Build"`, - `"JavaScript/Publish"`). -- [x] Update every `Group.*` consumer to set - `group = SpineTaskGroup.name` instead: - `Webpack.kt:104`, `Check.kt:100, 129, 164, 189`, - `Assemble.kt:108, 133, 161, 188`, `Publish.kt:93, 116, 156, 187`, - `Clean.kt:90, 117`, `IntegrationTest.kt:90, 121`, - `LicenseReport.kt:80`, `dart/task/Build.kt:101, 128, 150`, - `dart/task/Publish.kt:95, 131, 163`. - -**A.2 — Tasks that set neither `group` nor `description`.** -For each, add `group = SpineTaskGroup.name` and an imperative -`description` (no trailing period): - -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt:94` - (`tasks.register(taskName, Javadoc::class.java)`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt:58` - (`tasks.register(taskName, CheckVersionIncrement::class.java)`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt:121, 149, 165, 183` - (`updateGitHubPages`, `copyJavadocDocs`, `copyHtmlDocs`, - `updatePagesTask`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt:165, 196` - (`jacocoRootReport`, `copyReports`). **Superseded by Kover-only - migration**: this file is deprecated; do not invest in micro-rewrites. - See `.agents/skills/raise-coverage/references/migrate-to-kover.md`. -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt:111` - (`mergeAllLicenseReports`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt:85` - (`generatePom`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt:235, 249, 261, 273` - (`sourcesJar`, `protoJar`, `testJar`, `javadocJar` via - `getOrCreate`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt:100, 121, 136` - (three registrations). -- [x] `buildSrc/src/main/kotlin/write-manifest.gradle.kts:106` - (`exposeManifestForTests`). -- [x] `buildSrc/src/main/kotlin/config-tester.gradle.kts:53` - (the script's local `clean`). -- [x] `buildSrc/src/main/kotlin/jvm-module.gradle.kts:152` - (`cleanGenerated`). - -**A.3 — Additional Spine-owned registrations uncovered during -review of Section A.** Not listed in the original `/gradle-review` -report but covered by the same mandate. All addressed in the same PR. - -- [x] `buildSrc/src/main/kotlin/DokkaExts.kt:206` - (`htmlDocsJar` via `getOrCreate`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/dart/task/IntegrationTest.kt:76` - (`DartTasks.integrationTest`). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt:157` - (`getOrCreatePublishTask` — the root-aggregator `publish` task - created when the `maven-publish` plugin is absent). -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt:186` - (`registerCheckCredentialsTask` — both the `register` and the - `replace` code paths). - -### B. `Provider.get()` outside a task action (Must fix) - -Each of these forces evaluation during the configuration phase, -breaking the configuration cache and serialising work Gradle would -otherwise run in parallel. - -- [x] `buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts:58-72` — - rewrite the `tasks.getting(JacocoReport::class) { ... }` block - to `tasks.named("jacocoTestReport") { ... }`, - remove the `project.layout.buildDirectory.get().asFile.absolutePath` - call, and replace the eager `walkBottomUp().toSet()` with - a lazy `DirectoryProperty`/`Provider` chain. Note - that the current code silently produces an empty set on a - clean build because `build/classes/kotlin/jvm/` does not yet - exist — that correctness bug goes away with the lazy form. - **Superseded by Kover-only migration**: this file is deprecated; - do not invest in micro-rewrites. See - `.agents/skills/raise-coverage/references/migrate-to-kover.md`. -- [ ] `buildSrc/src/main/kotlin/DokkaExts.kt:62` — change - `dokkaHtmlOutput(): File` to return `Provider` (or - a `DirectoryProperty`); update its two call sites. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt:105-106` - — `val Project.buildDirectory: File`: same pattern. Either - delete the helper (it just shells out to - `layout.buildDirectory.get().asFile`) or change it to return - `Provider`. Audit callers before deciding. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt:84` - — `project.layout.buildDirectory.dir(Paths.relativePath).get().asFile`: - compose with `map` and consume inside the action. -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt:98-99` - — same pattern with the `reportsDirSuffix` directory. - **Superseded by Kover-only migration**: this file is deprecated; - do not invest in micro-rewrites. See - `.agents/skills/raise-coverage/references/migrate-to-kover.md`. -- [ ] `buildSrc/src/main/kotlin/DependencyResolution.kt:146` — - `named(configurationName).get().exclude(...)`: rewrite as - `named(configurationName) { exclude(...) }`. - -### C. Eager `FileCollection` / `Configuration` APIs (Must fix) - -These resolve configurations during configuration *and* discard -implicit task-dependency wiring — the wrong-outputs failure mode the -upstream rule warns about. - -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt:109` - — `docletpath = excludeInternalDoclet.files.toList()`: route - via a `Provider>` from `excludeInternalDoclet.elements`, - resolved inside the `Javadoc` task action. -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/CodebaseFilter.kt:65` - — `it.classesDirs.files.stream()`: switch to - `it.classesDirs.elements` and a lazy stream/iterator. - **Superseded by Kover-only migration**: this file is deprecated; - do not invest in micro-rewrites. See - `.agents/skills/raise-coverage/references/migrate-to-kover.md`. - -### D. Plugin task classes — caching annotations (Should fix) - -None of the three custom `DefaultTask` subclasses are annotated. -Document the contract: - -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/RunGradle.kt:48` - — add - `@DisableCachingByDefault(because = "Runs an external Gradle build whose outputs are not tracked")`. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt:45` - — add - `@DisableCachingByDefault(because = "Performs network I/O against a Maven repository")`. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/docs/UpdatePluginVersion.kt:56` - — add - `@DisableCachingByDefault(because = "Rewrites build scripts in place without declared outputs")`. - -### E. `@PathSensitivity` (Should fix) - -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/docs/UpdatePluginVersion.kt:58-59` - — add `@get:PathSensitive(PathSensitivity.RELATIVE)` on the - `directory` `DirectoryProperty`. The task only cares about - file names matching `build.gradle.kts` within the tree. - -### F. Eager realisation in convention code (Should fix) - -Replace eager APIs with their lazy siblings where one exists: - -- [ ] `buildSrc/src/main/kotlin/uber-jar-module.gradle.kts:73` — - `tasks.getting` → `tasks.named("publishFatJarPublicationToMavenLocal") { ... }`. -- [x] `buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts:58` — - `tasks.getting(JacocoReport::class)` → - `tasks.named("jacocoTestReport") { ... }` (folded - into B above). - **Superseded by Kover-only migration**: this file is deprecated; - do not invest in micro-rewrites. See - `.agents/skills/raise-coverage/references/migrate-to-kover.md`. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt:60` - — `tasks.getByName("check").dependsOn(this)` → - `tasks.named("check") { dependsOn(this@register) }`. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt:95, 99` - — `tasks.findByName("assemble")!!` / - `tasks.findByName("build")!!` → - `tasks.named("assemble") { ... }` / `tasks.named("build") { ... }`. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt:41, 46` - — `getByName(named) as Javadoc` → `tasks.named(named)`; - ripple through callers (`ExcludeInternalDoclet.kt:93`, - `JavadocConfig.kt:73`). -- [ ] `buildSrc/src/main/kotlin/dokka-setup.gradle.kts:51` — replace - `val kspKotlin = tasks.findByName("kspKotlin")` inside - `afterEvaluate { ... }` with a - `tasks.matching { it.name == "kspKotlin" }.configureEach { ... }` - block, or rely on `tasks.named("kspKotlin").orNull`. - -### G. `dependsOn` where input/output wiring expresses the link (Should fix) - -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt:273-278` - — `javadocJar()`: replace - `from(layout.buildDirectory.dir("dokka/javadoc"))` + - `dependsOn("dokkaGeneratePublicationJavadoc")` with - `from(tasks.named("dokkaGeneratePublicationJavadoc").map { it.outputs.files })`. -- [ ] `buildSrc/src/main/kotlin/DokkaExts.kt:206-213` — - `htmlDocsJar()`: same pattern; wire - `from(tasks.dokkaHtmlTask().map { it.outputs.files })` and - remove the explicit `dependsOn(dokkaTask)`. -- [x] `buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt:196-203` - — drop the trailing `dependsOn(projects.map { ... })` once - `everyExecData` is verified to be a Provider-typed chain that - carries producer dependencies. - **Superseded by Kover-only migration**: this file is deprecated; - do not invest in micro-rewrites. See - `.agents/skills/raise-coverage/references/migrate-to-kover.md`. -- [ ] `buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt:117-118, 123` - — the explicit `consolidationTask.dependsOn(perProjectTask)` - and `perProjectTask.dependsOn(assembleTask)` should be - expressed via the merge task's `@InputFiles` on the per-project - report files. Refactor `mergeReports` to take a - `ListProperty` and let Gradle infer ordering. - -### H. Nits - -- [ ] **Trailing period in `description`** — strip from every Dart/JS - task helper (`Webpack.kt:103`, `Check.kt:99, 128, 163, 188`, - `Assemble.kt:107, 132, 160, 187`, `Publish.kt:92, 115, 155, 186`, - `Clean.kt:89, 116`, `IntegrationTest.kt:88, 120`, - `LicenseReport.kt:79`, `dart/task/Build.kt:100, 127, 149`, - `dart/task/Publish.kt:94, 130, 162`) and from - `testing/Tasks.kt:81, 95`. -- [ ] **KDoc back-link.** Add a KDoc link to the relevant rule from - [`.agents/skills/gradle-review/practices/tasks.md`](../skills/gradle-review/practices/tasks.md) - (or the upstream Gradle page) on each of `RunGradle.kt`, - `CheckVersionIncrement.kt`, `UpdatePluginVersion.kt`. -- [ ] **`project` access inside task actions** — `RunGradle.kt:142-180` - (`project.rootDir`, `project.gradle.taskGraph.hasTask(":clean")`, - `project.file(directory)`, `project.rootProject`), - `CheckVersionIncrement.kt:60-115` (`project.artifactPath()` and - friends), `PomGenerator.kt:85-93`, - `LicenseReporter.kt:120-122`. Capture the necessary values or - `Provider`s during configuration; pass them in via task - properties. -- [ ] **`@Internal lateinit var directory: String` in `RunGradle.kt:60-62`** - — should be a `DirectoryProperty` (or at least a - `Property`) so the task can participate in - configuration-cache serialisation. - -### Verification - -- [ ] Run `./gradlew clean build` against `config` and confirm it - passes without configuration-cache warnings. -- [ ] Re-run `/gradle-review` against `buildSrc/` and confirm - `APPROVE` (or `APPROVE WITH CHANGES` for residual Nits). -- [ ] Smoke-test downstream consumers (`base`, `base-types`, - `core-java`) via the `buildDependants` task in - `config-tester.gradle.kts`. - -## Decisions - -- **Scope.** All three findings categories (Must fix, Should fix, - Nits) are in scope. The volume justifies a dedicated PR. -- **Sequencing.** Sections A and B-C are independent and can be done - in either order. Section G depends on the lazy-Provider rewrites - in B for `DokkaExts.kt`. Run the verification step after every - section to keep the PR bisectable. -- **Out of scope.** `io.spine.dependency.*` files (owned by the - `dependency-audit` skill) and `gradlew` / `gradlew.bat` are - excluded from this task. - -## Log - -- 2026-05-29 — drafted from the `/gradle-review` run on the full - `buildSrc/` tree. Branch `gradle-review-skill` carries the - document; execution lands in a separate PR. -- 2026-05-29 — Section A applied on branch - `address-gradle-review-01`. Five review rounds against the diff - surfaced four additional Spine-owned task registrations that the - original report missed (`htmlDocsJar`, dart `integrationTest`, the - root-aggregator `publish` task, and `checkCredentials`); all four - added to Section A.3 and addressed in the same PR. Sections B–H - remain pending. diff --git a/.agents/tasks/cross-agent-skill-best-practices.md b/.agents/tasks/cross-agent-skill-best-practices.md deleted file mode 100644 index 4876f28c86..0000000000 --- a/.agents/tasks/cross-agent-skill-best-practices.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -slug: cross-agent-skill-best-practices -branch: codex/audit-skills-discoverability -owner: codex -status: draft -started: 2026-05-31 ---- - -## Goal - -Bring the repository skills in `.agents/skills/` closer to the shared skills -standard so they are easy to discover and execute across Codex, Claude, and -other compatible agents. Success means a new agent can identify the right skill -from metadata, load a short `SKILL.md`, follow agent-neutral instructions, and -delegate deterministic work to scripts or references where appropriate. - -## Context - -- Audit source: Claude skill authoring best practices.[^claude-best-practices] -- Current inventory: 16 skills, 16 `SKILL.md` files, and 16 - `agents/openai.yaml` files. -- Good baseline: skill directory names match frontmatter names, names use the - expected lowercase hyphenated form, all `SKILL.md` files are under the - 500-line guideline, and frontmatter descriptions are under 1024 characters. -- User direction: optimize for compatibility with Codex, Claude, and other AI - agents that support the skills standard, not for a single agent runtime. - -## Findings - -1. Some fragile deterministic workflows are still mostly prose instead of - scripts. - - `check-links` embeds site detection, binary preflight, Lychee download, - Hugo server lifecycle, reporting, and sentinel writing in `SKILL.md`. - - `dependency-update` asks the agent to parse Kotlin dependency files, - discover versions, compare versions, and edit files manually. - - Best-practice risk: high-cognitive-load procedures are harder for agents - to pick up reliably and should be moved behind deterministic entrypoints - where practical. - -2. `raise-coverage` has a high-impact automatic path. - - The skill silently installs Kover when no coverage plugin is present. - - Best-practice risk: a request to add tests can mutate build configuration - without an explicit approval checkpoint. - - Cross-agent concern: different agents may interpret "silent install" - differently, so this should become an explicit policy decision. - -3. Long reference files need top-level contents. - - `raise-coverage/references/coverage-signals.md` is 181 lines. - - `raise-coverage/references/migrate-to-kover.md` is 352 lines. - - `gradle-review/practices/tasks.md` is 147 lines. - - Best-practice risk: reference material over 100 lines should be easier to - skim before an agent loads or follows a specific section. - -4. Some metadata and prompt surfaces are less portable than the rest. - - `raise-coverage/agents/openai.yaml` has a much longer `default_prompt` - than other skills. - - `writer/agents/openai.yaml` does not mention `$writer`, unlike the other - skill prompts. - - `raise-coverage/SKILL.md` still uses slash-command phrasing such as - `/raise-coverage` and `/version-bumped`, which is less portable across - agents. - -5. Evaluation evidence is missing. - - No eval or scenario files were found under `.agents/skills/`. - - Only `update-copyright` currently has script tests. - - Best-practice risk: the repo does not make it visible that skills were - tested on realistic examples, so future agents cannot distinguish - validated workflows from untried instructions. - -## Plan - -- [ ] Decide whether `raise-coverage` may silently install Kover, or whether all - build-configuration edits require explicit approval. -- [ ] Extract or introduce deterministic entrypoints for the highest-risk - procedural skills, starting with `check-links` and `dependency-update`. -- [ ] Add table-of-contents sections to reference files over 100 lines. -- [ ] Normalize cross-agent phrasing by removing slash-command assumptions and - keeping instructions skill-name based. -- [ ] Shorten unusually long `openai.yaml` default prompts while preserving - discoverability for Codex. -- [ ] Decide whether to add lightweight skill scenarios or eval notes for the - major skills. -- [ ] Re-audit all skills against the Claude best-practices checklist and record - the result in this task log. - -## Open Decisions - -- Should `raise-coverage` require approval before any Kover installation, even - when no coverage plugin exists? -- Should `dependency-update` get a real implementation script now, or should the - first pass only split parsing/versioning rules into references? -- What is the desired minimum evaluation artifact: short scenario files, - executable tests, or both? - -## Log - -- 2026-05-31: Drafted from the cross-agent skills best-practices audit. Awaiting - maintainer review before changes. - -[^claude-best-practices]: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices diff --git a/.agents/tasks/enforce-max-line-length.md b/.agents/tasks/enforce-max-line-length.md deleted file mode 100644 index 3cad1c6962..0000000000 --- a/.agents/tasks/enforce-max-line-length.md +++ /dev/null @@ -1,279 +0,0 @@ ---- -slug: enforce-max-line-length -branch: address-gradle-review-01 -owner: claude -status: draft -started: 2026-05-29 -related-memories: [] ---- - -## Goal - -Extend the agent-facing instructions and skills under `.agents/` so -that detekt's `MaxLineLength` rule -(`buildSrc/quality/detekt-config.yml:19-21`, -`maxLineLength: 100`, `excludeCommentStatements: true`) is honoured at -author time and surfaced at review time, instead of being discovered -late by CI on GitHub. - -Severity by file type: - -- **Detekt-enforced → Must fix** — non-comment lines in `.kt` / `.kts` - over the configured limit. These break `./gradlew build`. -- **Repo policy → Should fix** — KDoc / Javadoc body lines in any - source extension; `.java` lines; `.proto` lines; `.md` lines - (incl. `README.md`, `docs/**`, `.agents/**`). Detekt does not flag - these; the reviewer skills do. - -## Context - -CI and local builds repeatedly fail on detekt's `MaxLineLength` rule. -The user finds the late discovery — especially on GitHub — annoying. -None of the current agent instructions or skills name the rule, so -agents write code that breaks the build, then have to retry. - -### Framing - -The numeric threshold is a configuration parameter, not a constant. - -**Author-time behaviour**: agents read `MaxLineLength.maxLineLength` -from `buildSrc/quality/detekt-config.yml` once per session and treat -the value as a session-local constant. This is workable; re-reading -the YAML for every line of output is not. - -**Guidance text**: the new sections never bake the literal number -into `.agents/` prose. They reference the rule name and the file -path. If the threshold changes, the agent's session-start lookup -picks up the new value with no doc edit. - -**Review-time behaviour**: when a reviewer surfaces a finding, the -report cites the actual value (`"line 47 is 108 chars (limit 100, -from buildSrc/quality/detekt-config.yml)"`). The number lands in the -report, not in the rule. - -### KDoc handling (empirically verified) - -`excludeCommentStatements: true` excludes lines whose statement is a -comment — single-line `//`, trailing `//`, and KDoc body lines. The -exclusion of KDoc bodies is confirmed by -`buildSrc/src/main/kotlin/detekt-code-analysis.gradle.kts:52`, a -115-character KDoc body line that ships in the codebase and passes -the detekt build today. KDoc body lines are therefore Should-fix -repo policy, not Must-fix. - -### Splitting / restructure rules (confirmed with user) - -- String literals (including URLs inside strings) split at a - meaningful boundary into ≥ 2 `+`-concatenated pieces — never - truncated. -- Long imports: prefer an import alias - (`import a.b.c.LongName as Short`). If unavailable, a - `@file:Suppress("MaxLineLength")` is acceptable. -- Other unbreakable tokens (`[name][some.long.FQN]` in KDoc; long - generated identifier): prefer restructure (intermediate `val`, - reference-style Markdown link, alias). When no restructure is - reasonable, use `@Suppress("MaxLineLength")` on the declaration - with a brief `// Reason: …` comment. Use `@file:Suppress` only for - file-scope cases (e.g., a long import that cannot be aliased). - -### Scope clarifications - -- **Generated sources excluded**: do not flag lines under - `**/generated/**` or `**/generated-proto/**` — these are the paths - Spine's `buildSrc/quality/checkstyle.xml:35-42` and - `buildSrc/quality/pmd.xml:36-37` already exclude from the other - static-analysis runs. -- **Reading context vs. reporting scope.** Reviewers continue to read - each affected file fully (existing `kotlin-review` rule at - `.agents/skills/kotlin-review/SKILL.md:31-32`). They only *report* - line-length findings on lines the diff touched - (`git diff -U0 ...HEAD`). Pre-existing long lines are not - flagged. The two rules co-exist: read all, report changed. -- **`module.gradle.kts` carve-out**: per `AGENTS.md § Code review`, - in a consumer repo `buildSrc/src/main/kotlin/module.gradle.kts` is - in scope for the reviewers; it follows the same Must-fix rule as - any other `.kts`. -- **YAML lookup is from `HEAD`, not the base ref.** Long-lived - branches sometimes change `detekt-config.yml` mid-branch; reviewers - always re-read the value from the working tree, so the rule matches - what `./gradlew build` will see. -- **YAML missing is a hard error.** If - `buildSrc/quality/detekt-config.yml` is absent or lacks - `MaxLineLength.maxLineLength`, the reviewer reports a Must-fix - asking the user to restore the config rather than silently - inventing a number. - -## Plan - -Six `.agents/` Markdown files. No code or build changes. New lines -wrap at the configured limit. - -### 1. `.agents/coding-guidelines.md` - -- [ ] Add a new top-level `## Line length` section, placed immediately - after the existing "Text formatting" section. The canonical - content lives here; other docs cross-reference this heading. - Cover: - - Source-of-truth lookup: read `MaxLineLength.maxLineLength` from - `buildSrc/quality/detekt-config.yml` once at session start. Never - write the literal number into the guideline. - - Severity split (detekt-enforced vs. repo policy) per Context above. - - String-literal strategy with a small example whose split is at a - URL path boundary, e.g. - - ```kotlin - val ref = "https://github.com/SpineEventEngine/config/blob/master/" + - "buildSrc/quality/detekt-config.yml" - ``` - - This covers the URL-splitting case the user called out; the - existing `JacocoConfig.kt:122-125` pattern splits prose, not a - URL, and is not a sufficient teacher on its own. - - Unbreakable-token rules: import alias, restructure, then - `@Suppress` placement (on the declaration; `@file:Suppress` for - file-scope). - - Scope exclusions: generated sources; changed lines only. - -### 2. `.agents/documentation-guidelines.md` - -- [ ] Append one bullet to "Commenting guidelines": - - > Wrap KDoc / Javadoc body lines and Markdown body lines at the - > limit defined in `buildSrc/quality/detekt-config.yml` - > (`MaxLineLength.maxLineLength`). See - > `coding-guidelines.md § Line length` for the splitting strategy. - - Single sentence; no duplication of the canonical section. - -### 3. `.agents/quick-reference-card.md` - -- [ ] Rewrap the existing 135-char line 3 so the card itself respects - the rule it now advertises. -- [ ] Append one line (plain text, no decorative emoji — the rest of - the card uses 🚫 for a hard prohibition only, and line-length - guidance isn't in that category): - - > At session start, read `MaxLineLength.maxLineLength` from - > `buildSrc/quality/detekt-config.yml` and wrap new lines under it. - > See `coding-guidelines.md § Line length`. - -### 4. `.agents/skills/kotlin-review/SKILL.md` - -- [ ] In "Review procedure" step 3 (the coding-guidelines checklist), - append: - - > Line length (`MaxLineLength`). The reviewer reads the limit from - > `buildSrc/quality/detekt-config.yml` and applies it only to lines - > the diff touched. Non-comment `.kt` / `.kts` lines over the limit - > are **Must fix** (detekt breaks the build; - > `excludeCommentStatements: true` exempts KDoc bodies from the - > build break). KDoc bodies in `.kt` / `.kts`, and any `.java` line - > over the limit, are **Should fix**. For changed lines inside a - > string literal the fix is splitting into ≥ 2 `+`-concatenated - > pieces; otherwise follow `coding-guidelines.md § Line length`. - -- [ ] Update "Output format" correspondingly: add the bucket entries - but keep the existing Must / Should / Nits semantics unchanged. - -### 5. `.agents/skills/review-docs/SKILL.md` - -- [ ] Insert into "Checks → A. KDoc / Javadoc inside sources": - - > **Line length.** KDoc / Javadoc body lines wrap at the limit from - > `buildSrc/quality/detekt-config.yml`. Long body lines are - > **Should fix**; code lines around the comment, if also too long, - > are owned by `kotlin-review`. - -- [ ] Insert into "Checks → B. Markdown docs": - - > **Line length.** Body lines in `.md` — including `README.md`, - > `docs/**`, and `.agents/**` (this expands the skill's prior `.md` - > scope explicitly) — wrap at the configured limit. Long URLs go in - > reference-style footnote definitions. Long lines are - > **Should fix**. - -### 6. `.agents/skills/pre-pr/SKILL.md` - -- [ ] In the "Procedure" section, add a one-line pointer near the - existing reviewer-dispatch table (around - `.agents/skills/pre-pr/SKILL.md:104-106`): - - > Line-length findings on changed Kotlin / Java / Markdown lines - > are reported by the dispatched reviewers (`kotlin-review`, - > `review-docs`). pre-pr itself does not re-check. - - Documentation only — no logic change. Clarifies that the rule is - inherited via the existing dispatch and prevents future edits from - duplicating the check inside pre-pr. - -### Verification - -- [ ] Visually scan every edited file for the literal `100`. The - number should not appear in the new prose; only the rule name - and the YAML path should. -- [ ] Read the YAML, capture the value - (`LIMIT=$(awk '/maxLineLength:/ {print $2}' - buildSrc/quality/detekt-config.yml)`), and run - `awk -v n=$LIMIT 'length > n' `. `awk`'s - `length` counts bytes; for the ASCII prose introduced here that - matches characters, but a non-ASCII glyph in future edits would - miscount. Acceptable for this change. -- [ ] Sanity-check cross-references: every `coding-guidelines.md § - Line length` link resolves to the new top-level section heading. -- [ ] Spot test the author behaviour. In a fresh session, ask the - agent to write a long Kotlin string literal containing a URL; - confirm the result splits with `+` at a URL path boundary and - preserves every character. -- [ ] Spot test the reviewer behaviour. Synthesize a diff with: one - non-comment `.kt` line over the limit (expect Must fix); one - KDoc body line over the limit (expect Should fix); one `.java` - line over the limit (expect Should fix); one `.md` body line - over the limit (expect Should fix). Run `kotlin-review` and - `review-docs` and confirm bucketing. -- [ ] Confirm the missing-YAML behaviour: temporarily move - `buildSrc/quality/detekt-config.yml` aside, run a reviewer over - a synthetic diff, confirm it reports a **Must fix** asking the - user to restore the config (not a silent fallback). - -## Out of scope - -- `buildSrc/quality/detekt-config.yml` — unchanged. -- `writer/SKILL.md` and `java-to-kotlin/SKILL.md` — they author, they - don't enforce. The canonical rule in `coding-guidelines.md` reaches - them by reference. -- `gradle-review/SKILL.md` — `.kts` files are reviewed by - `kotlin-review` (via pre-pr's `code` dispatch). Adding a second - owner would double-report; defer to `kotlin-review § Line length`. -- `update-copyright/SKILL.md` — if a header rewrite produces a long - line, the reviewer will catch it; no skill-local rule. -- `memory/MEMORY.md` and `_TOC.md` — the rule is durable team policy - belonging in `.agents/`, indexed via the natural section heading. -- Rewrap of pre-existing over-length lines outside the diff (e.g., - `java-to-kotlin/SKILL.md:24,25,40,42`) — separate cleanup task, not - blocked by this plan. - -## Decisions - -- **KDoc severity**. Should-fix, not Must-fix. Empirically verified - by `buildSrc/src/main/kotlin/detekt-code-analysis.gradle.kts:52` - (115-char KDoc body line that ships and builds clean). -- **`gradle-review` not edited**. `.kts` files flow through - `kotlin-review` already (via pre-pr's `code` dispatch); a second - owner in `gradle-review` would cause double-reports for the same - finding. The trade-off is that manual `/gradle-review` runs without - a paired `/kotlin-review` will not surface line-length findings on - `.kts` files; users running only `gradle-review` are looking for - Gradle conventions, not detekt rules, so the gap is acceptable. -- **YAML lookup at session start, not per line**. Re-reading the YAML - for every line of output is impractical; the agent caches the value - as a session-local constant. Documentation never bakes the literal. -- **Missing YAML is Must-fix, not informational**. Avoids silent - fallback drift. - -## Log - -- 2026-05-29 — drafted in this session; plan revised twice to address - findings from two review rounds (KDoc empirics, generated-source - globs, `## Line length` heading placement, `gradle-review` → - `pre-pr` swap, YAML-missing severity, verification cleanup). - Awaiting approval. diff --git a/.agents/tasks/gradle-caching-plan.md b/.agents/tasks/gradle-caching-plan.md deleted file mode 100644 index efc3859ff2..0000000000 --- a/.agents/tasks/gradle-caching-plan.md +++ /dev/null @@ -1,200 +0,0 @@ ---- -slug: gradle-caching-plan -branch: gradle-review-skill -owner: claude -status: draft -started: 2026-05-29 ---- - -# Plan: Speed Up Builds via Gradle Caching (org-wide, through `config`) - -> Implementation plan for Claude Code operating in the **`SpineEventEngine/config`** repository. -> Follow the repo's existing conventions in `CLAUDE.md` / `.agents/` (commit style, copyright -> headers, Kotlin guidelines, allowed commands). Make minimal diffs and land each phase as its -> own PR. - -## Purpose - -Make CI and local builds across the Spine organization faster by enabling **every free Gradle -caching layer**. Because `config` is the shared submodule pulled into every Spine repository, -changes here propagate org-wide — no per-repo edits required. - -## Why this work belongs in `config` - -`config` is added to each Spine project as a Git submodule, and `./config/pull` copies shared -files into the consuming project. Two of those files are exactly the levers we need: - -- **Root `gradle.properties`** — *overwritten* into each consuming repo on every `pull`. This is - the single source of truth for Gradle build flags. -- **`.github-workflows/`** — its workflow scripts are *merged into* each repo's - `.github/workflows/` on `pull`. This is where the CI definitions that run in every repo live. - (Per the repo README, these workflows intentionally do **not** run for `config` itself, so they - live under `.github-workflows/` rather than `.github/workflows/`.) - -Editing these here, then bumping the submodule + running `./config/pull` in a consuming repo, is -how the change reaches the whole org. - -## Goal - -Enable, in order of safety/ROI: - -1. **Dependency cache** — downloaded dependencies + wrapper distributions. -2. **Local build cache** — task outputs (`caches/build-cache-1`), persisted across CI runs so cold - CI builds skip unchanged work. -3. **Configuration cache** — skip Gradle's configuration phase on repeat runs (gated; higher risk). - -**Non-goal (out of scope here):** a *remote* build cache (Develocity or a self-hosted cache node). -That is the only layer that shares task outputs *across* repositories and across machines, but it -requires infrastructure (a reachable cache node + credentials, or Develocity) and is not a -config-only change. It is captured as a future phase, not to be implemented now. - -## Mental model (so changes are made for the right reasons) - -- The **dependency cache** speeds up *resolution/download*; it does not reuse build work. -- The **build cache** reuses *task outputs*, keyed by a hash of their inputs. Gradle's up-to-date - checks already cover "same workspace, nothing changed," so the build cache only adds value from a - **cold/fresh state** with unchanged inputs. -- **CI is cold on every run** (fresh checkout), so the build cache is precisely what helps CI — - independent of team size or number of repos. -- `gradle/actions/setup-gradle` persists the Gradle User Home (deps, wrapper, **and** - `caches/build-cache-1`) via the GitHub Actions cache. By default it **writes** the cache only - from the **default branch**; other branches **read** the default branch's cache. So PR builds - reuse what `main`'s CI produced, without polluting the shared cache. - - Caveat: for `pull_request`-triggered runs, the cache scope is the PR merge ref and writes are - disabled by default (only re-runs of the same PR restore them). The read-from-`main` behavior - still applies. - -## Guardrails (do / don't) - -- ✅ **DO** edit the **root `gradle.properties`** in `config` for all Gradle flags. -- ⛔ **DON'T** add Gradle flags to individual consuming repos' `gradle.properties` — `./config/pull` - overwrites that file, so such edits are lost. `config` is the only correct place. -- ✅ **DO** edit workflow templates in **`.github-workflows/`** (and, if you also want `config`'s - own CI to benefit, `config`'s own `.github/workflows/`). -- ⛔ **DON'T** keep `actions/setup-java` with `cache: gradle` alongside `setup-gradle` — the two - caching mechanisms conflict; remove `cache: gradle` when adding `setup-gradle`. -- ⛔ **DON'T** create any remote cache server, add secrets, create accounts, or change repo - permissions. (Out of scope; infra/owner decisions.) -- ✅ Keep diffs minimal: don't reorder or delete existing properties/steps that are unrelated. -- ✅ Land each phase as a **separate commit/PR** and validate before moving on. - -## Tasks - -### Phase 0 — Inventory (no changes) - -1. Read the root `gradle.properties`; record which `org.gradle.*` flags already exist (caching, - parallel, configuration-cache, jvmargs, etc.). -2. List `.github-workflows/`. For each workflow, locate the Java/Gradle setup steps and how Gradle - is invoked (`./gradlew ...`). Note any use of `actions/setup-java` with `cache: gradle`. -3. Check `config`'s own `.github/workflows/` separately (these run for `config` itself). -4. Read `gradle/wrapper/gradle-wrapper.properties` to determine the **Gradle version**. The stable - configuration-cache property names below assume Gradle **8.1+**; if older, adjust property names - and treat Phase 3 with extra caution. -5. Summarize findings before editing. - -### Phase 1 — Switch CI to `gradle/actions/setup-gradle` - -For each relevant workflow: - -- Remove `cache: gradle` from any `actions/setup-java` step. -- Add a `gradle/actions/setup-gradle@v6` step **after** Java setup and **before** any Gradle - invocation. (The action also configures init-scripts that apply to later `run: ./gradlew` steps.) -- Match the repo's existing action-pinning policy; current major versions available are - `actions/checkout@v6`, `actions/setup-java@v5`, `gradle/actions/setup-gradle@v6`. - -Reference shape (adapt to each workflow's actual jobs/matrix — do not blindly overwrite): - -```yaml -steps: - - uses: actions/checkout@v6 - - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 17 # keep whatever the repo currently targets; no `cache: gradle` - - uses: gradle/actions/setup-gradle@v6 - - run: ./gradlew build -``` - -Notes: -- The default `enhanced` cache provider is **free for public repositories** (all Spine repos are - public). No `cache-provider` override needed unless a fully-MIT path is preferred - (`cache-provider: basic`). -- Leave the default write-on-default-branch-only behavior in place; it's the desired setup. - -### Phase 2 — Enable build cache + parallel in shared `gradle.properties` - -In the root `gradle.properties`, add (only if absent): - -```properties -org.gradle.caching=true -org.gradle.parallel=true -``` - -- `caching=true` enables the **local** build cache; combined with `setup-gradle` persisting - `caches/build-cache-1`, CI runs now reuse task outputs. -- `parallel=true` is generally safe but must be validated (see acceptance). - -### Phase 3 — Configuration cache (gated; higher risk) - -In the root `gradle.properties`, add: - -```properties -org.gradle.configuration-cache=true -org.gradle.configuration-cache.problems=warn -``` - -- Start in **warn** mode so configuration-cache-incompatible tasks **do not fail** the build. -- Spine relies on many custom Gradle plugins and code-generation tasks (Protobuf / model compiler - / etc.) that may not yet be configuration-cache compatible. Warn mode surfaces problems without - breaking builds. -- Where feasible, fix incompatibilities in **`buildSrc`** (the shared build logic). If problems are - extensive, **leave configuration cache in warn mode or defer Phase 3 entirely** — do **not** - switch to strict/fail mode until `buildDependants` is clean. -- (On Gradle < 8.1 the stable property differs; do not guess — check the wrapper version from - Phase 0 and use the matching property name, or skip this phase.) - -### Phase 4 — Remote build cache (FUTURE — do not implement now) - -Documented for completeness only. If pursued later: -- Configure `buildCache { remote(HttpBuildCache) { ... } }` (in `settings.gradle.kts` of consuming - projects, or centrally via `buildSrc`), pushing **only from CI**. -- Per Gradle's guidance, **disable the local build cache on CI** when a remote cache is available, - to keep GitHub Actions cache entries small. -- Requires a reachable cache node + credentials (or Develocity) and is an infrastructure decision — - not a config-only change. Stop and flag this to a human rather than implementing it. - -## Verification / acceptance criteria - -`config` ships `ConfigTester`, wired into `build.gradle.kts` as the `buildDependants` task, which -checks out and builds the dependant repos (`base`, `base-types`, `core-java`) against the **local** -`config`. Use it as the gate for every phase: - -```bash -./gradlew clean buildDependants # ~30+ minutes; builds base, base-types, core-java with local config -``` - -Acceptance for each phase: - -1. `buildDependants` **passes** with the change applied. -2. **Cache reuse is observable:** run a dependant build twice; the second run shows many tasks as - `FROM-CACHE` / `UP-TO-DATE`. -3. **CI evidence:** in a workflow run, the `setup-gradle` **Job Summary** reports cache entries - restored/saved; compare overall job wall-clock **before vs after**. -4. **Phase 3 specifically:** `buildDependants` completes with configuration cache enabled (warn mode - acceptable). Record any remaining configuration-cache problems in the PR description. - -## Rollout - -1. Land Phases 1–2 (and 3 if clean) as separate PRs in `config`. -2. Pilot in **one** consuming repo first (suggest `base`): bump the `config` submodule, run - `./config/pull` (this overwrites `gradle.properties` and merges `.github-workflows/` into - `.github/workflows/`), confirm CI is green and faster. -3. Propagate to the remaining repos once the pilot is validated. - -## References - -- `setup-gradle` docs: https://github.com/gradle/actions/blob/main/docs/setup-gradle.md -- Gradle Build Cache: https://docs.gradle.org/current/userguide/build_cache.html -- Gradle Configuration Cache: https://docs.gradle.org/current/userguide/configuration_cache.html -- `config` README (pull mechanism, `.github-workflows`, `ConfigTester`/`buildDependants`): - https://github.com/SpineEventEngine/config diff --git a/.agents/tasks/spine-task-group-constant.md b/.agents/tasks/spine-task-group-constant.md deleted file mode 100644 index 24667819f9..0000000000 --- a/.agents/tasks/spine-task-group-constant.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -slug: spine-task-group-constant -branch: gradle-review-skill -owner: claude -status: in-progress -started: 2026-05-29 -related-memories: [] ---- - -## Goal - -Replace the bare string literal `"spine"` (the Gradle task group used -by every custom task in this organisation) with a shared constant in -two locations: - -1. **In `config`'s `buildSrc/`** — so all build files in `config` and - all consumer projects that apply `config` reference the same - symbol instead of repeating the literal. -2. **In `tool-base`** — so the production code of every Spine SDK - Gradle plugin references the same symbol when it registers or - configures tasks. - -Once both constants exist, `gradle-review` reports a remaining bare -literal `"spine"` as a Nit and recommends the relevant constant as -the replacement. - -## Context - -- The Spine convention "every custom task has `group = "spine"`" is - documented in - [`.agents/skills/gradle-review/spine-task-conventions.md`](../skills/gradle-review/spine-task-conventions.md). -- The `gradle-review` skill (see - [`../skills/gradle-review/SKILL.md`](../skills/gradle-review/SKILL.md)) - enforces the rule, and lists the constant migration as a Nit until - the symbol exists. -- Two separate codebases are involved because of dependency direction: - `buildSrc/` in `config` is on the build classpath of every consumer - project's `build.gradle.kts`, while `tool-base` is consumed at - runtime by SDK plugins. A single source-of-truth in `tool-base` and - a re-export from `buildSrc/` would couple the two — instead each - side declares its own constant and both keep the same value - (`"spine"`). The `gradle-review` skill cross-checks both. - -## Plan - -### A. `config/buildSrc` constant - -- [x] Add `object SpineTaskGroup { const val name = "spine" }` in - `buildSrc/src/main/kotlin/io/spine/gradle/SpineTaskGroup.kt` - with copyright header and KDoc referencing - `.agents/skills/gradle-review/spine-task-conventions.md`. -- [x] Migrate every `group = "spine"` usage in `buildSrc/**/*.kt` and - `buildSrc/**/*.gradle.kts` to the constant. (Verified by - `rg "group\s*=\s*\"spine\""` — no existing literals in - `buildSrc/`; the only `"spine"` occurrence there is the - unrelated artifact-prefix constant in `dependency/local/Base.kt`.) -- [x] Migrate every `group = "spine"` usage in the project's - `build.gradle.kts` and `settings.gradle.kts` (the constant is - visible from build files thanks to `buildSrc/`). (Verified — no - existing literals.) -- [x] Spot-check with `rg -n '"spine"' --type kotlin` (ripgrep's - built-in `kotlin` type covers both `*.kt` and `*.kts`; the - short alias `--type kt` is **not** recognised) — only the - constant declaration and unrelated occurrences (artifact - prefix in `Base.kt`, exclude rule for `spine-base` in - `DependencyResolution.kt`) remain. - -### B. `tool-base` constant + GitHub issue - -- [x] Open the tracking issue under `tool-base` — [tool-base#171][tool-base-171]. -- [ ] (Remaining migration is tracked by that issue, not this branch.) - -[tool-base-171]: https://github.com/SpineEventEngine/tool-base/issues/171 - -## Decisions - -- **Naming and shape.** `object SpineTaskGroup { const val name = "spine" }`. - Reference site reads `group = SpineTaskGroup.name`. Mirrors the - `JsTasks.Group.build` precedent already used inside `buildSrc/` and - leaves room for related constants later. Consistency with the - `tool-base` constant — once it exists — is more important than the - specific shape; the `tool-base` issue should adopt the same shape. -- **Location.** New file at - `buildSrc/src/main/kotlin/io/spine/gradle/SpineTaskGroup.kt`, - alongside `TaskName.kt` and other top-level Gradle helpers. - Visibility is `public` (default) so consumer `build.gradle.kts` - files can import the symbol. -- **KDoc link form.** Plain text path to - `.agents/skills/gradle-review/spine-task-conventions.md`; KDoc does - not resolve relative Markdown links in the IDE, and an absolute - GitHub URL would couple the source to a specific branch. - -## Log - -- 2026-05-29 — drafted alongside the `gradle-review` skill, awaiting - approval to start migration. -- 2026-05-29 — implemented `SpineTaskGroup` in `config/buildSrc` - (`io.spine.gradle.SpineTaskGroup`). Verified by ripgrep that no - bare `"spine"` task-group literals exist in `*.kt` or `*.gradle.kts` - under this repo, so the migration step in section A is a no-op - inside `config`. The constant is in place for new tasks added here - and for consumer repositories' build files. The `tool-base` - constant and its migration remain tracked under - [tool-base#171][tool-base-171]. From 6511d3928b94bd6775995547b0bdc3294c154d8d Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 21:15:12 +0100 Subject: [PATCH 2/9] Bump version -> `2.0.0-SNAPSHOT.449` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 7ad68a8dd8..1814bd9665 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -27,4 +27,4 @@ /** * The version of the Validation library to publish. */ -val validationVersion by extra("2.0.0-SNAPSHOT.448") +extra.set("validationVersion", "2.0.0-SNAPSHOT.449") From f4ef5b94f918d8d5e73a4ff358e69b87c54a3cd9 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 21:20:42 +0100 Subject: [PATCH 3/9] Update dependency reports --- docs/dependencies/dependencies.md | 68 +++++++++++++++---------------- docs/dependencies/pom.xml | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index 7c2fa5d873..866ee81821 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -1106,14 +1106,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -1806,14 +1806,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:55 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-docs:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-docs:2.0.0-SNAPSHOT.449` ## Runtime ## Compile, tests, and tooling @@ -1823,14 +1823,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:58 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:48 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -2870,14 +2870,14 @@ This report was generated on **Mon Jul 06 18:58:58 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -3983,14 +3983,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 13.0. @@ -4040,14 +4040,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:58 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:48 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java-settings:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-java-settings:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4847,14 +4847,14 @@ This report was generated on **Mon Jul 06 18:58:58 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-validation-jvm-runtime:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine:spine-validation-jvm-runtime:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -5674,14 +5674,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -6362,14 +6362,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:51 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -6826,14 +6826,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -7447,14 +7447,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8014,14 +8014,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:59:00 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-time:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-time:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8446,14 +8446,14 @@ This report was generated on **Mon Jul 06 18:59:00 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -9060,14 +9060,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -9800,14 +9800,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -10043,14 +10043,14 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:48 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.448` +# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.449` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -10396,6 +10396,6 @@ This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 18:58:59 WEST 2026** using +This report was generated on **Mon Jul 06 21:17:49 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/docs/dependencies/pom.xml b/docs/dependencies/pom.xml index 89b383889e..79ea99cb81 100644 --- a/docs/dependencies/pom.xml +++ b/docs/dependencies/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine.tools validation -2.0.0-SNAPSHOT.448 +2.0.0-SNAPSHOT.449 2015 From 3b85c2ea0ebbbb5df4c124cc7ae52e8b6865f255 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 23:16:38 +0100 Subject: [PATCH 4/9] Credit `context-tests` coverage to the aggregated report The `context-tests` module ran its in-process compilation tests without the Kover plugin, so the coverage they produce for `:context` production classes (66 `RangeReaction` cases, `Min`/`Max`, and the other reaction/option specs) was never credited to the aggregated report. Those classes therefore showed large false coverage gaps. Apply the Kover plugin to `context-tests` so `KoverConfig` folds the module into the root rollup, and exclude the module's generated Protobuf test fixtures (the `io.spine.tools.validation.given` package) from the root report: `KoverConfig` only excludes generated classes discovered under `main` source sets, not `testFixtures`, so without this the generated fixtures would flood the report. This credits ~105 previously-uncounted production lines and ~34 branches with no new tests. Co-Authored-By: Claude Opus 4.8 --- build.gradle.kts | 18 ++++++++++++++++++ context-tests/build.gradle.kts | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index a95dc08e81..497bce36ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,6 +44,7 @@ import io.spine.gradle.report.coverage.KoverConfig import io.spine.gradle.report.license.LicenseReporter import io.spine.gradle.report.pom.PomGenerator import io.spine.gradle.testing.enableSpineCompilerCoverage +import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension buildscript { standardSpineSdkRepositories() @@ -142,6 +143,23 @@ allprojects { KoverConfig.applyTo(rootProject) +// Exclude the Protobuf test fixtures of `:context-tests` from the aggregated +// coverage report. That module applies Kover so its in-process compilation tests +// credit `:context` production classes, but its fixtures generate into the +// `io.spine.tools.validation.given` package. `KoverConfig` only excludes +// generated classes discovered under `main` source sets, not `testFixtures`, so +// these generated fixtures are filtered out here to keep them out of the figures +// Codecov ingests. +extensions.configure { + reports { + filters { + excludes { + classes("io.spine.tools.validation.given.**") + } + } + } +} + // Attach the JaCoCo agent to the forked Spine Compiler JVMs so that the // out-of-process execution of code-generation plugins (the `java` and `context` // modules' renderers and generators) is credited to the root coverage report. diff --git a/context-tests/build.gradle.kts b/context-tests/build.gradle.kts index 17a8839f23..229e559cac 100644 --- a/context-tests/build.gradle.kts +++ b/context-tests/build.gradle.kts @@ -37,6 +37,10 @@ plugins { protobuf `java-test-fixtures` prototap + // Apply Kover so that the in-process compilation tests of this module credit + // coverage to the `:context` (and other) production classes they exercise. + // `KoverConfig` at the root folds this module into the aggregated report. + id("org.jetbrains.kotlinx.kover") } LicenseReporter.generateReportIn(project) From 4a80029b5dd73a3a0659b528a5571bc17582619f Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 23:16:38 +0100 Subject: [PATCH 5/9] Add tests for `ValidationSdk` and the `(validate)` option Add `ValidationSdkSpec`, covering the Maven coordinates and compiler-plugin class name exposed by the `ValidationSdk` object. Add `ValidateOptionSpec` (with the `ValidateOnNonMessageField` proto fixture), covering the previously untested field-type check in `ValidateReaction` that rejects the `(validate)` option on non-message fields. Co-Authored-By: Claude Opus 4.8 --- .../tools/validation/ValidateOptionSpec.kt | 53 +++++++++++++++++ .../validation/validate_option_spec.proto | 42 ++++++++++++++ .../validation/gradle/ValidationSdkSpec.kt | 57 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 context-tests/src/test/kotlin/io/spine/tools/validation/ValidateOptionSpec.kt create mode 100644 context-tests/src/testFixtures/proto/spine/validation/validate_option_spec.proto create mode 100644 gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationSdkSpec.kt diff --git a/context-tests/src/test/kotlin/io/spine/tools/validation/ValidateOptionSpec.kt b/context-tests/src/test/kotlin/io/spine/tools/validation/ValidateOptionSpec.kt new file mode 100644 index 0000000000..2234b99da8 --- /dev/null +++ b/context-tests/src/test/kotlin/io/spine/tools/validation/ValidateOptionSpec.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2026, 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 + * + * 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. + */ + +package io.spine.tools.validation + +import io.kotest.matchers.string.shouldContain +import io.spine.tools.compiler.ast.name +import io.spine.tools.compiler.ast.qualifiedName +import io.spine.tools.validation.given.ValidateOnNonMessageField +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +/** + * Tests the field-type check performed by + * [ValidateReaction][io.spine.tools.validation.option.ValidateReaction], which + * rejects the `(validate)` option on fields that are not messages (or repeated + * of messages, or maps with message values). + */ +@DisplayName("`ValidateReaction` should reject the `(validate)` option") +internal class ValidateOptionSpec : CompilationErrorTest() { + + @Test + fun `applied to a non-message field`() = + assertCompilationFails(ValidateOnNonMessageField::class) { field -> + shouldContain(field.qualifiedName) + shouldContain(field.type.name) + shouldContain("is not supported by the") + shouldContain("Supported field types") + } +} diff --git a/context-tests/src/testFixtures/proto/spine/validation/validate_option_spec.proto b/context-tests/src/testFixtures/proto/spine/validation/validate_option_spec.proto new file mode 100644 index 0000000000..826b847bc5 --- /dev/null +++ b/context-tests/src/testFixtures/proto/spine/validation/validate_option_spec.proto @@ -0,0 +1,42 @@ +/* + * Copyright 2026, 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 + * + * 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. + */ + +syntax = "proto3"; + +package spine.validation.stubs; + +import "spine/options.proto"; + +option (type_url_prefix) = "type.spine.io"; +option java_package = "io.spine.tools.validation.given"; +option java_outer_classname = "ValidateOptionSpecProto"; +option java_multiple_files = true; + +// Applies the `(validate)` option to a field of a non-message type, which the +// option does not support. +message ValidateOnNonMessageField { + int32 value = 1 [(validate) = true]; +} diff --git a/gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationSdkSpec.kt b/gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationSdkSpec.kt new file mode 100644 index 0000000000..c32cb3e284 --- /dev/null +++ b/gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationSdkSpec.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026, 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 + * + * 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. + */ + +package io.spine.tools.validation.gradle + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +/** + * Verifies the Maven coordinates and the compiler-plugin class name exposed by + * [ValidationSdk], which the Validation Gradle plugin uses to wire user projects. + */ +@DisplayName("`ValidationSdk` should") +internal class ValidationSdkSpec { + + @Test + fun `expose the JVM runtime artifact`() { + ValidationSdk.jvmRuntime.group shouldBe "io.spine" + ValidationSdk.jvmRuntime.name shouldBe "spine-validation-jvm-runtime" + } + + @Test + fun `expose the Java codegen bundle artifact`() { + ValidationSdk.javaCodegenBundle.group shouldBe "io.spine.tools" + ValidationSdk.javaCodegenBundle.name shouldBe "validation-java-bundle" + } + + @Test + fun `expose the fully qualified name of the Java compiler plugin`() { + ValidationSdk.javaCompilerPlugin shouldBe + "io.spine.tools.validation.java.JavaValidationPlugin" + } +} From 8d59eef36e382519cfe44bb1e6b1b6c2ec065471 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 23:16:38 +0100 Subject: [PATCH 6/9] Update dependency reports Regenerated after `context-tests` began applying the Kover plugin, which brings its JaCoCo-engine dependencies into the aggregated graph. Co-Authored-By: Claude Opus 4.8 --- docs/dependencies/dependencies.md | 23 ++++++++++++++++++++++- docs/dependencies/pom.xml | 10 ---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index 866ee81821..79a667298e 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1610,6 +1610,12 @@ This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using 1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.15. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.15. + * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) + +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.15. + * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) + 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 13.0. * **Project URL:** [http://www.jetbrains.org](http://www.jetbrains.org) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1794,11 +1800,26 @@ This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using * **Project URL:** [https://github.com/ota4j-team/opentest4j](https://github.com/ota4j-team/opentest4j) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.10.1. + * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) + * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.8. * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.ow2.asm. **Name** : asm-commons. **Version** : 9.10.1. + * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) + * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.ow2.asm. **Name** : asm-tree. **Version** : 9.10.1. + * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) + * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.yaml. **Name** : snakeyaml. **Version** : 2.5. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml](https://bitbucket.org/snakeyaml/snakeyaml) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1806,7 +1827,7 @@ This report was generated on **Mon Jul 06 21:17:50 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Jul 06 21:17:55 WEST 2026** using +This report was generated on **Mon Jul 06 23:00:19 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). diff --git a/docs/dependencies/pom.xml b/docs/dependencies/pom.xml index 79ea99cb81..c4417d6c7c 100644 --- a/docs/dependencies/pom.xml +++ b/docs/dependencies/pom.xml @@ -349,11 +349,6 @@ all modules and does not describe the project structure per-subproject. templating-plugin 2.2.0 - - org.jetbrains.kotlin - abi-tools - 2.3.21 - org.jetbrains.kotlin kotlin-build-tools-compat @@ -364,11 +359,6 @@ all modules and does not describe the project structure per-subproject. kotlin-build-tools-impl 2.3.21 - - org.jetbrains.kotlin - kotlin-klib-commonizer-embeddable - 2.3.21 - org.jetbrains.kotlin kotlin-scripting-compiler-embeddable From b0cf64563ecc86975d9877cc5cff2d7bfb85143f Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 23:25:35 +0100 Subject: [PATCH 7/9] Add tests for the deprecated `(is_required)` and `(if_invalid)` warnings Add `DeprecatedOptionsSpec` (with the `deprecated_options_spec.proto` fixtures), driving the compiler pipeline against messages that use the deprecated `(is_required)` oneof option and `(if_invalid)` field option, and asserting the deprecation warnings emitted by `IsRequiredReaction` and `IfInvalidReaction` via captured console output. Co-Authored-By: Claude Opus 4.8 --- .../tools/validation/DeprecatedOptionsSpec.kt | 108 ++++++++++++++++++ .../validation/deprecated_options_spec.proto | 58 ++++++++++ 2 files changed, 166 insertions(+) create mode 100644 context-tests/src/test/kotlin/io/spine/tools/validation/DeprecatedOptionsSpec.kt create mode 100644 context-tests/src/testFixtures/proto/spine/validation/deprecated_options_spec.proto diff --git a/context-tests/src/test/kotlin/io/spine/tools/validation/DeprecatedOptionsSpec.kt b/context-tests/src/test/kotlin/io/spine/tools/validation/DeprecatedOptionsSpec.kt new file mode 100644 index 0000000000..bab9ad75d8 --- /dev/null +++ b/context-tests/src/test/kotlin/io/spine/tools/validation/DeprecatedOptionsSpec.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2026, 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 + * + * 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. + */ + +package io.spine.tools.validation + +import com.google.protobuf.Descriptors.Descriptor +import io.kotest.matchers.string.shouldContain +import io.spine.logging.testing.ConsoleTap +import io.spine.logging.testing.tapConsole +import io.spine.testing.compiler.PipelineSetup +import io.spine.testing.compiler.acceptingOnly +import io.spine.testing.compiler.pipelineParams +import io.spine.testing.compiler.withRequestFile +import io.spine.testing.compiler.withSettingsDir +import io.spine.tools.code.SourceSetName +import io.spine.tools.compiler.params.WorkingDirectory +import io.spine.tools.validation.given.WithDeprecatedIfInvalid +import io.spine.tools.validation.given.WithDeprecatedIsRequired +import java.nio.file.Path +import kotlin.io.path.createDirectories +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Verifies that the validation compiler reports a deprecation warning when the + * deprecated `(is_required)` and `(if_invalid)` options are used, exercising + * [IsRequiredReaction][io.spine.tools.validation.option.IsRequiredReaction] and + * [IfInvalidReaction][io.spine.tools.validation.option.IfInvalidReaction]. + * + * Both reactions emit their message via `Compilation.warning(...)`, so the test + * runs the compiler pipeline against a fixture and captures the console output. + */ +@DisplayName("The validation compiler should warn about a deprecated option") +internal class DeprecatedOptionsSpec { + + @TempDir + lateinit var workingDir: Path + + @Test + fun `'(is_required)' on a oneof group`() { + val output = compile(WithDeprecatedIsRequired.getDescriptor()) + + output shouldContain "(is_required)" + output shouldContain "deprecated" + } + + @Test + fun `'(if_invalid)' on a field`() { + val output = compile(WithDeprecatedIfInvalid.getDescriptor()) + + output shouldContain "(if_invalid)" + output shouldContain "deprecated" + } + + private fun compile(descriptor: Descriptor): String { + val wd = WorkingDirectory(workingDir) + val outputDir = workingDir.resolve("output") + outputDir.createDirectories() + val params = pipelineParams { + withRequestFile(wd.requestDirectory.file(SourceSetName("testFixtures"))) + withSettingsDir(wd.settingsDirectory.path) + } + val setup = PipelineSetup.byResources( + params, + plugins = listOf(object : ValidationPlugin() {}), + outputRoot = outputDir, + descriptorFilter = acceptingOnly(descriptor) + ) {} + val pipeline = setup.createPipeline() + return tapConsole { + pipeline() + } + } + + companion object { + + @JvmStatic + @BeforeAll + fun installConsoleTap() { + ConsoleTap.install() + } + } +} diff --git a/context-tests/src/testFixtures/proto/spine/validation/deprecated_options_spec.proto b/context-tests/src/testFixtures/proto/spine/validation/deprecated_options_spec.proto new file mode 100644 index 0000000000..1acabefb1b --- /dev/null +++ b/context-tests/src/testFixtures/proto/spine/validation/deprecated_options_spec.proto @@ -0,0 +1,58 @@ +/* + * Copyright 2026, 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 + * + * 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. + */ + +syntax = "proto3"; + +package spine.validation.stubs; + +import "spine/options.proto"; + +option (type_url_prefix) = "type.spine.io"; +option java_package = "io.spine.tools.validation.given"; +option java_outer_classname = "DeprecatedOptionsSpecProto"; +option java_multiple_files = true; + +// Uses the deprecated `(is_required)` oneof option, which the validation +// compiler reports with a deprecation warning. +message WithDeprecatedIsRequired { + oneof choice { + option (is_required) = true; + + string first = 1; + string second = 2; + } +} + +// Uses the deprecated `(if_invalid)` field option, which the validation +// compiler reports with a deprecation warning. +message WithDeprecatedIfInvalid { + IfInvalidPayload value = 1 [(if_invalid).error_msg = "The value is invalid."]; +} + +// A message type referenced by `WithDeprecatedIfInvalid`. +message IfInvalidPayload { + string content = 1; +} From 825282bad4678f745a27ba6d19b83235d0291ae2 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 Jul 2026 23:45:01 +0100 Subject: [PATCH 8/9] Add an apply test for `ValidationGradlePlugin` Add `ValidationGradlePluginSpec`, which applies the plugin to a `ProjectBuilder` project and verifies the configuration it performs eagerly: registering the `validation` extension, applying the Protobuf and Spine Compiler Gradle plugins, and adding the validation runtime as an `implementation` dependency. Applying the plugin in-process exercises the transitively-applied Spine Compiler plugin, which references `org.gradle.kotlin.dsl` extensions and requires the `JavaPluginExtension`; hence the `testImplementation(gradleKotlinDsl())` dependency and the `java` plugin applied first in the test. The plugin's `afterEvaluate` and per-task `doFirst` actions run only during a real build and remain covered by the integration projects under `:tests`. Co-Authored-By: Claude Opus 4.8 --- gradle-plugin/build.gradle.kts | 4 + .../gradle/ValidationGradlePluginSpec.kt | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationGradlePluginSpec.kt diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index aadf29c236..13a7aca649 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -82,6 +82,10 @@ gradlePlugin { dependencies { compileOnly(gradleKotlinDsl()) + // Needed at test runtime too: applying the plugin under `ProjectBuilder` + // exercises the Spine Compiler Gradle plugin, which references + // `org.gradle.kotlin.dsl` extensions. + testImplementation(gradleKotlinDsl()) implementation(Protobuf.GradlePlugin.lib) implementation(Base.format) implementation(Compiler.api) diff --git a/gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationGradlePluginSpec.kt b/gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationGradlePluginSpec.kt new file mode 100644 index 0000000000..913e298463 --- /dev/null +++ b/gradle-plugin/src/test/kotlin/io/spine/tools/validation/gradle/ValidationGradlePluginSpec.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2026, 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 + * + * 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. + */ + +package io.spine.tools.validation.gradle + +import com.google.protobuf.gradle.ProtobufPlugin +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.spine.tools.gradle.lib.spineExtension +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import io.spine.tools.compiler.gradle.plugin.Plugin as CompilerGradlePlugin + +/** + * Verifies the eager configuration performed by [ValidationGradlePlugin] when + * it is applied to a project: registering the `validation` extension, applying + * the Protobuf and Spine Compiler Gradle plugins on the user's behalf, and + * adding the validation runtime as an `implementation` dependency. + * + * The plugin's `afterEvaluate` and per-task `doFirst` actions run only during a + * real build and are covered by the integration projects under `:tests`. + */ +@DisplayName("`ValidationGradlePlugin` should") +internal class ValidationGradlePluginSpec { + + private lateinit var project: Project + + @BeforeEach + fun setUp() { + project = ProjectBuilder.builder().build() + // A consumer project always applies a JVM plugin; the Spine Compiler + // plugin (applied transitively below) requires the `JavaPluginExtension`. + project.pluginManager.apply("java") + project.pluginManager.apply(ValidationGradlePlugin::class.java) + } + + @Test + fun `register the 'validation' extension`() { + project.spineExtension().shouldNotBeNull() + } + + @Test + fun `apply the Protobuf Gradle plugin on the user's behalf`() { + project.plugins.hasPlugin(ProtobufPlugin::class.java) shouldBe true + } + + @Test + fun `apply the Spine Compiler Gradle plugin on the user's behalf`() { + project.plugins.hasPlugin(CompilerGradlePlugin::class.java) shouldBe true + } + + @Test + fun `add the validation runtime as an 'implementation' dependency`() { + val implementation = project.configurations.getByName("implementation") + val hasRuntime = implementation.dependencies.any { + it.name == "spine-validation-jvm-runtime" + } + hasRuntime shouldBe true + } +} From bd0a068ae577e2b13680ce2ebfb4a952b5a2377d Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Tue, 7 Jul 2026 01:04:34 +0100 Subject: [PATCH 9/9] Re-anchor the version embed on `validationVersion` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `version.gradle.kts` bump migrated the publishing-version declaration from the deprecated `val validationVersion by extra(...)` delegate to `extra.set("validationVersion", ...)`. `build-and-release.md` embedded that line via `start/end="val validationVersion"`, so the anchor no longer matched and `:docs:checkSamples` failed (`Panic: there is no line matching Pattern val validationVersion`). Re-anchor the embed on `validationVersion` — unique in the file and stable across version bumps — and regenerate the embedded snippet via `:docs:embedCode`. Addresses the review comments on #318. Co-Authored-By: Claude Opus 4.8 --- docs/content/docs/validation/developer/build-and-release.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/validation/developer/build-and-release.md b/docs/content/docs/validation/developer/build-and-release.md index da3cc71af5..cf2c350e09 100644 --- a/docs/content/docs/validation/developer/build-and-release.md +++ b/docs/content/docs/validation/developer/build-and-release.md @@ -43,11 +43,11 @@ through Gradle's `extra` properties: + start="validationVersion" + end="validationVersion"> ```kotlin -val validationVersion by extra("2.0.0-SNAPSHOT.446") +extra.set("validationVersion", "2.0.0-SNAPSHOT.449") ``` The root build script applies this file under `allprojects { … }` and assigns