diff --git a/.agents/advanced-safety-rules.md b/.agents/advanced-safety-rules.md deleted file mode 100644 index e410581..0000000 --- a/.agents/advanced-safety-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# 🚨 Advanced safety rules - -- Do **not** auto-update external dependencies without explicit request. -- Do **not** inject analytics or telemetry code. -- Flag any usage of unsafe constructs (e.g., reflection, I/O on the main thread). -- Avoid generating blocking calls inside coroutines. diff --git a/.agents/documentation-guidelines.md b/.agents/documentation-guidelines.md deleted file mode 100644 index 6c9c1ba..0000000 --- a/.agents/documentation-guidelines.md +++ /dev/null @@ -1,14 +0,0 @@ -# Documentation & comments - -## Commenting guidelines -- Avoid inline comments in production code unless necessary. -- Inline comments are helpful in tests. -- When using TODO comments, follow the format on the [dedicated page][todo-comments]. -- File and directory names should be formatted as code. - -## Avoid widows, runts, orphans, or rivers - -Agents should **AVOID** text flow patters illustrated -on [this diagram](widow-runt-orphan.jpg). - -[todo-comments]: https://github.com/SpineEventEngine/documentation/wiki/TODO-comments diff --git a/.agents/documentation-tasks.md b/.agents/documentation-tasks.md deleted file mode 100644 index 8ac4660..0000000 --- a/.agents/documentation-tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# πŸ“„ Documentation tasks - -1. Ensure all public and internal APIs have KDoc examples. -2. Add in-line code blocks for clarity in tests. -3. Convert inline API comments in Java to KDoc in Kotlin: - ```java - // Literal string to be inlined whenever a placeholder references a non-existent argument. - private final String missingArgumentMessage = "[MISSING ARGUMENT]"; - ``` - transforms to: - ```kotlin - /** - * Literal string to be inlined whenever a placeholder references a non-existent argument. - */ - private val missingArgumentMessage = "[MISSING ARGUMENT]" - ``` - -4. Javadoc -> KDoc conversion tasks: - - Remove `

` tags in the line with text: `"

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

` with empty line if the tag is the only text in the line. diff --git a/.agents/guidelines b/.agents/guidelines new file mode 120000 index 0000000..6f9d966 --- /dev/null +++ b/.agents/guidelines @@ -0,0 +1 @@ +shared/guidelines \ No newline at end of file diff --git a/.agents/memory/reference/cache-warm-window.md b/.agents/memory/reference/cache-warm-window.md index 796dd4d..424fdbb 100644 --- a/.agents/memory/reference/cache-warm-window.md +++ b/.agents/memory/reference/cache-warm-window.md @@ -15,10 +15,10 @@ same cache slot β€” provided they fall within the TTL. - Default: **5 minutes** (applies to all non-subscription auth) - With `ENABLE_PROMPT_CACHING_1H=1` in `~/.claude/settings.json`: **1 hour** -Developers must have `ENABLE_PROMPT_CACHING_1H=1` set, otherwise the +Developers must have `ENABLE_PROMPT_CACHING_1H=1` set; otherwise the window is too short for cross-session hits to occur reliably. -This setting will work ONLY for Claude Code which runs the CLI binary. -It will not work for JetBrains Air or any other IDE plugin which does not +This setting will work ONLY for Claude Code that runs the CLI binary. +It will not work for JetBrains Air or any other IDE plugin that does not run the Claude Code CLI binary. **Cache is per Anthropic workspace.** All developers authenticated via the diff --git a/.agents/project.md b/.agents/project.md deleted file mode 100644 index 77c3f9c..0000000 --- a/.agents/project.md +++ /dev/null @@ -1,59 +0,0 @@ -# Project: documentation - -## Overview - -This repository is the **documentation aggregator and content host** for the -Spine SDK. It owns the Hugo site setup that gathers documentation from sibling -SDK repos (currently `SpineEventEngine/validation`) as Hugo modules, and it -also stores original Markdown content under `docs/`. The repo additionally -serves as the GitHub Wiki source for committer-facing documentation about -contributing to the framework. - -The public site at [spine.io](https://spine.io) is built from -`SpineEventEngine/SpineEventEngine.github.io`, which **imports this repo's -`docs/` directory as a Hugo module**. Edits made here flow to the public site -when `SpineEventEngine.github.io` bumps its module pin. - -## Architecture - -**Role in the org:** documentation aggregator + content host. - -**Stack.** A Hugo + Node project at heart. Gradle is a thin task-runner -wrapper around Hugo, Node, and Go tooling β€” not a JVM build in any meaningful -sense, so JVM coding conventions do not apply here. - -- `docs/` β€” published content, Hugo site root, exported as a Hugo module to - `SpineEventEngine.github.io`. -- `docs/_preview/` β€” local-only Hugo setup for running the site during - authoring (`./gradlew :runSite`, or `hugo server` from this directory). -- `docs/_code/examples/*` β€” git submodules pinned at - `spine-examples/{airport, blog, hello, kanban, todo-list}`. These are the - **canonical source of embedded code samples**; this repo does not modify - them. -- `config/` β€” git submodule pointing at `SpineEventEngine/config`. Provides - shared agent guidance, skills, and build config consumed across Spine SDK - repos. Applied via the `Apply config` step. -- Theme: components, layouts, and styles come from the `site-commons` Hugo - theme (`github.com/SpineEventEngine/site-commons`). - -**Doc modules pulled in via `docs/hugo.toml`.** Currently only the -`validation` repo contributes docs as a Hugo module. The README lists -`framework` and `compiler` as examples of how to add more; they are not -wired in today. - -**Key conventions and constraints (not obvious from the code):** - -- **Theme changes mirror to spine.io.** Any non-trivial change to - `site-commons` usage here must also be applied in the main `spine.io` site - repo, or the live site will diverge from preview. -- **Embedded code must round-trip.** Code blocks in pages are generated from - the `docs/_code` submodules by the [`embed-code`][embed-code] tool. Do not - hand-edit embedded code blocks in Markdown; run `./gradlew :embedCode` and - verify with `./gradlew :checkSamples`. See [`EMBEDDING.md`](../EMBEDDING.md). -- **Submodules are pinned.** `docs/_code/examples/*` and `config` are pinned - intentionally β€” do not bump them as a side effect of unrelated work. -- **Link checking is required pre-PR.** Run the `check-links` skill before - opening any PR that touches `docs/**` or `site/**`; CI runs the same check - via `lychee.toml` against the rendered HTML. - -[embed-code]: https://github.com/SpineEventEngine/embed-code-go diff --git a/.agents/project.md b/.agents/project.md new file mode 120000 index 0000000..7e0bf9b --- /dev/null +++ b/.agents/project.md @@ -0,0 +1 @@ +../docs/project.md \ No newline at end of file diff --git a/.agents/project.template.md b/.agents/project.template.md deleted file mode 100644 index b6882e0..0000000 --- a/.agents/project.template.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# Project: - -## Overview - -*One paragraph: what this repo is, what problem it solves, and its role in the -Spine SDK organisation.* - -## Architecture - -*Role in the org: library / tool / Gradle plugin / application. -Key patterns, public API boundaries, and constraints specific to this repo.* - - diff --git a/.agents/safety-rules.md b/.agents/safety-rules.md deleted file mode 100644 index e7fece3..0000000 --- a/.agents/safety-rules.md +++ /dev/null @@ -1,49 +0,0 @@ -# Safety rules - -- βœ… All code must compile and pass static analysis. -- βœ… Do not auto-update external dependencies. -- ❌ Never use reflection or unsafe code without an explicit approval. -- ❌ No analytics or telemetry code. -- ❌ No blocking calls inside coroutines. - -## Commits and history-writing - -**Default: do not write to git history.** This is a hard rule for every -agent β€” the main thread, every subagent, every skill. It overrides any -local convenience or "the change looks done" instinct. - -The rule covers all of these operations: - -- `git commit`, `git commit-tree` -- `git push`, `git push --force` -- `git tag` -- `git rebase`, `git merge`, `git cherry-pick` against shared history -- `git reset` that discards committed work -- `gh release create`, `gh pr merge` - -Authorization to perform one of these operations exists only when **one** -of the following is true *right now*: - -1. **Skill-declared.** The currently active skill's `SKILL.md` contains - a `## Commit authorization` section that explicitly authorizes the - operation and constrains it (which files may be staged, the exact - commit subject, the maximum number of commits). The mere mention of - a commit message inside skill prose is **not** authorization β€” the - section heading must be present. -2. **User-instructed.** The user's *current* prompt explicitly tells - the agent to perform the operation. Examples that qualify: - "commit this", "make a commit with subject X", "push the branch", - "tag this release". Authorization from previous turns, from - `CLAUDE.md`, or from any memory file does **not** carry over. - -If neither holds, the agent: - -1. Stages relevant changes with `git add` (only if helpful for review). -2. Prints the proposed commit subject (if any) and `git diff --staged`. -3. **Stops.** The user runs the commit themselves, or replies with - explicit authorization in the next prompt. - -The project's `.claude/settings.json` keeps `Bash(git commit:*)` in -`permissions.ask` as defense-in-depth, but the primary enforcement is -this rule β€” agents must not propose commit attempts that rely on the -user clicking the prompt. diff --git a/.agents/scripts b/.agents/scripts new file mode 120000 index 0000000..96bf06e --- /dev/null +++ b/.agents/scripts @@ -0,0 +1 @@ +shared/scripts \ No newline at end of file diff --git a/.agents/scripts/pre-pr-gate.sh b/.agents/scripts/pre-pr-gate.sh deleted file mode 100755 index cb80b31..0000000 --- a/.agents/scripts/pre-pr-gate.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -# -# PreToolUse hook: block `gh pr create` unless /pre-pr has successfully run -# for the current HEAD. The hook is intentionally unaware of the repository's -# versioning or build system; the /pre-pr skill decides which checks apply. -# -# Input: hook JSON on stdin (tool_name, tool_input.command). -# Exit: 0 to allow, 2 to block (stderr is surfaced to Claude). -# -set -eu - -input=$(cat) -tool=$(printf '%s' "$input" | jq -r '.tool_name // empty') -[ "$tool" != "Bash" ] && exit 0 - -cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty') - -# Split the command on shell separators (`;`, `&`, `|` β€” `&&`/`||` collapse -# to repeated newlines, which is fine) and check each segment. Only block -# when a segment STARTS (after optional whitespace) with `gh pr create`. -# This avoids false positives like `echo "gh pr create"` or test fixtures -# that mention the string, while still catching `cd dir && gh pr create` -# and `cat body | gh pr create`. `tr` is used (not `sed s///`) because -# BSD `sed` on macOS does not interpret `\n` in the replacement string. -if ! printf '%s' "$cmd" \ - | tr ';&|' '\n\n\n' \ - | grep -qE '^[[:space:]]*gh[[:space:]]+pr[[:space:]]+create([[:space:]]|$)'; then - exit 0 -fi - -repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0 -sentinel="$repo_root/.git/pre-pr.ok" - -block() { - cat >&2 - exit 2 -} - -if [ ! -f "$sentinel" ]; then - block < 1) next; print; next } - { blank = 0; print } - ' "$path" > "$tmp" && mv "$tmp" "$path" -} - -if [ -n "$file" ]; then - sanitize_file "$file" - exit 0 -fi - -printf '%s\n' "$command" \ - | sed -nE 's/^\*\*\* (Add|Update) File: (.*)$/\2/p' \ - | sort -u \ - | while IFS= read -r path; do - sanitize_file "$path" - done diff --git a/.agents/scripts/update-copyright.sh b/.agents/scripts/update-copyright.sh deleted file mode 100755 index b25282f..0000000 --- a/.agents/scripts/update-copyright.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# -# PostToolUse hook: refresh the copyright header of source files touched by -# Edit/Write/MultiEdit. Delegates to -# .agents/skills/update-copyright/scripts/update_copyright.py, which: -# - operates only on recognized source extensions, -# - never adds a header to a file that does not already have one, -# - rewrites `today.year` to the current year per the IntelliJ profile. -# -# Input: hook JSON on stdin. Claude Code passes `tool_input.file_path`; -# Codex `apply_patch` passes the patch text in `tool_input.command`. -# Exit: 0 always (post-tool-use; never block). -# -set -u - -# Required tools β€” silently no-op if either is missing so the hook never blocks. -command -v jq >/dev/null 2>&1 || exit 0 -command -v python3 >/dev/null 2>&1 || exit 0 - -input=$(cat) -file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true) -command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || true) - -root="${CLAUDE_PROJECT_DIR:-$(pwd)}" -script="$root/.agents/skills/update-copyright/scripts/update_copyright.py" - -[ -f "$script" ] || exit 0 - -update_path() { - local path="$1" - [ -z "$path" ] && return 0 - [ ! -f "$path" ] && return 0 - python3 "$script" --root "$root" "$path" >/dev/null 2>&1 || true -} - -if [ -n "$file" ]; then - update_path "$file" - exit 0 -fi - -printf '%s\n' "$command" \ - | sed -nE 's/^\*\*\* (Add|Update) File: (.*)$/\2/p' \ - | sort -u \ - | while IFS= read -r path; do - update_path "$path" - done - -exit 0 diff --git a/.agents/shared b/.agents/shared new file mode 160000 index 0000000..fefe0ed --- /dev/null +++ b/.agents/shared @@ -0,0 +1 @@ +Subproject commit fefe0ed0bf47066b4b50eb01c2823a18dd2e9702 diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000..f14734d --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +shared/skills \ No newline at end of file diff --git a/.agents/skills/check-links/SKILL.md b/.agents/skills/check-links/SKILL.md deleted file mode 100644 index 5571e13..0000000 --- a/.agents/skills/check-links/SKILL.md +++ /dev/null @@ -1,320 +0,0 @@ ---- -name: check-links -description: > - Validate the Hugo documentation site under `docs/` or `site/` for broken - links. Builds the site, starts the Hugo server locally, runs Lychee against - the rendered HTML using the repo's `lychee.toml`, and reports any broken URLs - grouped by source Markdown page. Use locally before pushing changes that - touch `docs/**` or `site/**`, when CI's `Check Links` job fails, or whenever - the user asks to "check doc links". Read-only with respect to the project - sources. Does **not** cover Javadoc/KDoc (out of scope for this skill). ---- - -# Check links in the Hugo docs (repo-specific) - -You are the documentation link checker for this Spine Event Engine project. -You build the site under `docs/` or `site/` (auto-detected; see step 0), serve -it locally on port `1414`, run Lychee against the rendered HTML, and report -broken URLs. You mirror what the `.github/workflows/check-links.yml` workflow -does in CI: same Hugo version, same Lychee version, same Hugo environment -(`development`), and the same `lychee.toml`. Two deliberate differences remain: -the skill serves on port `1414` (CI uses `1313`) to avoid clashing with a -developer's local `hugo server`, and the skill writes a local sentinel that CI -does not. Both differences are harmless because `--base-url` is rewritten to -match the local port and the sentinel is consumed only by the local `pre-pr` -skill. - -### Pinned versions - -`.github/workflows/check-links.yml` is the **single source of truth** for the -Hugo and Lychee pins. This file does not duplicate the current values -because duplicates inevitably drift; see the workflow's `env:` block for -the canonical `HUGO_VERSION` and `LYCHEE_VERSION_TAG`. The auto-download -step (Β§2) reads `LYCHEE_VERSION_TAG` out of the workflow at runtime, so a -workflow bump propagates automatically. Hugo is not auto-installed; the -skill uses whichever `hugo` is on `$PATH` and only warns (does not block) -if the installed version is older than the workflow's `HUGO_VERSION` β€” -Hugo's HTML output is stable enough across minor versions that a small -skew does not invalidate link-check results. - -The authoritative shared config is `lychee.toml` at the repo root. Do not -fork its exclude list β€” fix the source link or, if the failing URL is a known -flaky external endpoint, add it to `lychee.toml` once (the change applies to -both the skill and CI). - -## When to run - -- Any change touches `docs/**` or `site/**` (including reference links, - `embed-code` blocks, sidenav YAML files, content under `/content/`). -- A change touches `lychee.toml` itself. -- CI reported broken links and you want a fast local repro. -- The user asks to "check the doc links" or invokes `/check-links`. - -If none of the above is true, decline with a one-line note rather than -running the (~30 s) build+check. - -## Tooling - -The skill needs four binaries: - -| Tool | Purpose | Install hint | -|--------|------------------------------------------|-------------------------------| -| Hugo | Build and serve the site | `brew install hugo` (extended)| -| Node | Hugo theme dependencies (`npm ci`) | `brew install node` | -| npm | Same | bundled with Node | -| Lychee | Link checker | `brew install lychee` | - -For **Lychee**, prefer a pre-installed binary on `$PATH`. If none is found, -download the pinned release (see `LYCHEE_VERSION_TAG` in -`.github/workflows/check-links.yml` β€” the dynamic-read pattern in step 2 below -keeps this version in lock-step with CI) into -`.agents/skills/check-links/.cache/lychee/` and use that path. The pinned -version matches what the CI workflow uses, so behavior is identical. - -`.agents/skills/check-links/.cache/` is git-ignored (see `.gitignore`). - -## Procedure - -Execute the steps in order. On the first failure, stop, write a `FAIL` -sentinel (step 8), and report the failure with the next action. - -### 0. Detect site root - -Before any other step, determine `SITE_DIR` β€” the directory that contains the -Hugo config file: - -```bash -SITE_DIR="" -for dir in docs site; do - for cfg in hugo.toml hugo.yaml; do - if [ -f "$dir/$cfg" ]; then - SITE_DIR="$dir" - break 2 - fi - done -done -if [ -z "$SITE_DIR" ]; then - echo "ERROR: No Hugo config found under docs/ or site/." >&2 - exit 1 -fi -``` - -Use `$SITE_DIR` everywhere a directory path is needed in the steps below. - -### 1. Scope check - -Run `git diff ...HEAD --name-only` (default `` = `master` unless -the user provides another). If the change set has **no** files under -`$SITE_DIR/**` and no changes to `lychee.toml`, and the user did not -explicitly ask, decline and exit cleanly. - -### 2. Preflight binaries - -- `hugo version` β†’ must succeed; capture the version. If missing, stop with - Must-fix: "Install Hugo extended (`brew install hugo`)." If installed but - older than the workflow's `HUGO_VERSION` (parse with - `grep -E '^[[:space:]]+HUGO_VERSION:' .github/workflows/check-links.yml | sed -E 's/.*: *"?([^"]+)"?$/\1/'`), warn but - continue. -- `node -v` and `npm -v` β†’ must succeed. If missing, stop with Must-fix: - "Install Node (`brew install node`) at the major version pinned by - `node-version:` in `.github/workflows/check-links.yml`." -- `lychee --version` β†’ if it succeeds, record the path and version. -- If `lychee` is missing: - 1. Read the canonical pin from the workflow file so the skill cannot drift - from CI: - ```bash - LYCHEE_VERSION_TAG=$( - grep -E '^[[:space:]]+LYCHEE_VERSION_TAG:' .github/workflows/check-links.yml \ - | sed -E 's/.*: *"?([^"]+)"?$/\1/' - ) - ``` - Expected shape: `lychee-vX.Y.Z` (the leading `lychee-` is part of the - upstream release tag, not a typo). - 2. Determine platform via `uname -s` / `uname -m`. Map to the matching - Lychee asset (recent releases β€” `v0.24.2` and later β€” drop the - version from the asset filename): - - `Darwin` + `arm64` β†’ `lychee-aarch64-apple-darwin.tar.gz` - - `Darwin` + `x86_64` β†’ `lychee-x86_64-apple-darwin.tar.gz` - - `Linux` + `x86_64` β†’ `lychee-x86_64-unknown-linux-gnu.tar.gz` - - `Linux` + `aarch64` β†’ `lychee-aarch64-unknown-linux-gnu.tar.gz` - - any other combination (e.g. Windows, FreeBSD, 32-bit) β†’ stop with - Must-fix: "Unsupported platform for Lychee auto-download β€” install - Lychee manually (`brew install lychee` / `cargo install lychee`) - and rerun." - 3. Ensure the cache directory exists *before* the download β€” - `mkdir -p .agents/skills/check-links/.cache/lychee/` β€” - because the path is git-ignored and absent on a fresh clone, - and `tar -xzf … -C

` will fail with "no such file or - directory" if the target does not exist yet. This mirrors the - `mkdir -p lychee` that `check-links.yml` does before its own - extract step. - 4. Download from - `https://github.com/lycheeverse/lychee/releases/download/${LYCHEE_VERSION_TAG}/` - into `.agents/skills/check-links/.cache/lychee/` and extract - with `tar -xzf --strip-components=1 -C .agents/skills/check-links/.cache/lychee/` - so the binary lands at - `.agents/skills/check-links/.cache/lychee/lychee`. - 5. Use `.agents/skills/check-links/.cache/lychee/lychee` for the rest of this run. - 6. Print a one-line note: "Using auto-downloaded Lychee. For faster runs, - install with `brew install lychee`." - -### 3. Install Hugo deps - -Run `( cd ${SITE_DIR}/_preview && npm ci )`. We deliberately use `npm ci` -(matching the CI workflow's `Install Dependencies` step in `check-links.yml`) -rather than `npm install`: - -- `npm ci` installs exactly the versions pinned by `package-lock.json`; - `npm install` is allowed to update the lockfile and may resolve to - different transitive versions than CI, which defeats the "render - identical HTML to CI" goal. -- If `package.json` and `package-lock.json` drift out of sync, `npm ci` - fails fast with a clear error rather than silently healing the - lockfile β€” a divergence we want to surface, not paper over. - -The helper script `${SITE_DIR}/_script/install-dependencies` exists for -interactive use but does a relative `cd _preview` and therefore only works -when invoked from `${SITE_DIR}/` β€” calling it from the repo root (the skill's -default CWD) would fail with "No such file or directory: _preview". - -### 4. Build the site - -Run `( cd ${SITE_DIR}/_preview && hugo -e development )`. -This emits `${SITE_DIR}/_preview/public/**/*.html`. The `-e development` flag -matches what CI uses in `check-links.yml` so the two builds render identical -HTML. (The helper `${SITE_DIR}/_script/hugo-build` exists for interactive use -but defaults to `production`; we invoke `hugo` directly to keep the env in -lock-step with CI.) - -### 5. Start the Hugo server in the background - -The server must survive across multiple `Bash` tool calls (steps 5 β†’ 6 β†’ 8 -typically run in separate shells), so we rely on `nohup` alone β€” a `trap … -EXIT` would fire when *this* shell exits and kill the server before Lychee -can query it. Teardown happens explicitly in step 8. - -Before launching, kill any leftover server from a previous crashed run so a -stale process does not hold port `1414`: - -```bash -pkill -F /tmp/check-links.hugo.pid 2>/dev/null || true -rm -f /tmp/check-links.hugo.pid - -( cd ${SITE_DIR}/_preview && nohup hugo server --environment development --port 1414 \ - > /tmp/check-links.hugo.out 2>&1 & echo $! > /tmp/check-links.hugo.pid ) -sleep 5 - -# Verify the captured PID is alive before relying on it. `$!` for -# `nohup foo &` is reliable on bash but not portable across shells; the -# pgrep check turns a silent "Lychee fetches an empty port" failure into -# a clear error. -if ! pgrep -F /tmp/check-links.hugo.pid > /dev/null 2>&1; then - echo "ERROR: Hugo server failed to start. Tail of log:" >&2 - tail -20 /tmp/check-links.hugo.out >&2 || true - exit 1 -fi -``` - -Port `1414` is chosen to avoid clashing with a developer's local `hugo server` -(default `1313`). The `--environment development` flag matches CI's build env. - -### 6. Run Lychee - -```bash - --config lychee.toml --timeout 60 \ - --base-url http://localhost:1414/ \ - "${SITE_DIR}/_preview/public/**/*.html" -``` - -Capture exit code. Any non-zero exit means at least one broken link. - -### 7. Report - -Group the broken URLs from Lychee's output by source page. To reverse-map -an HTML path to its Markdown source: - -`${SITE_DIR}/_preview/public/docs/
//index.html` -↔ `${SITE_DIR}/content/docs/
/.md` (or `/_index.md`). - -Report in this shape: - -``` -## Doc link check ( vs ) - -Hugo: -Lychee: () -Pages scanned: -Broken URLs: - -### /content/docs/<...>/.md -- β€” -- β€” ... - -### /content/docs/<...>/.md -- ... -``` - -If `K == 0`, report a single line: "All links OK." - -### 8. Tear down and sentinel - -- Kill the Hugo server (and clean up its pid file): - - ```bash - pkill -F /tmp/check-links.hugo.pid 2>/dev/null || true - rm -f /tmp/check-links.hugo.pid /tmp/check-links.hugo.out - ``` - - Run this even if Lychee failed β€” leaving a server on port `1414` would - poison the next invocation. -- Write `.git/check-links.ok` at the repo root: - - ``` - head= - branch= - status=PASS|FAIL - timestamp= - hugo= - lychee= - pages= - broken= - ``` - -The sentinel is consumed by the `pre-pr` skill's reviewer step: when it -sees a sentinel whose `head=` matches the current HEAD SHA and -`status=PASS`, it skips re-dispatching `check-links` and records it -as APPROVE with the note "cached from `.git/check-links.ok`". Any -HEAD advance (commit, amend, rebase) invalidates the cache automatically. - -## Notes - -- This skill does **not** modify tracked sources. It does, however, write - several git-ignored build artifacts during a run β€” listed here so a future - reader does not mistake them for unrelated side-effects: - - `.agents/skills/check-links/.cache/lychee/` β€” auto-downloaded - Lychee binary, when the system Lychee was unavailable. - - `${SITE_DIR}/_preview/node_modules/` β€” installed by `npm ci` in step 3. - - `${SITE_DIR}/_preview/public/` β€” Hugo's rendered HTML (the corpus Lychee - scans). - - `${SITE_DIR}/_preview/resources/` β€” Hugo's asset-pipeline cache. - - `.lycheecache` at the repo root β€” Lychee's per-URL result cache - (honoured for `max_cache_age = "3d"` per `lychee.toml`). - - `/tmp/check-links.hugo.{pid,out}` β€” server PID file and log, both - removed in step 8's teardown. - - Every path above is matched by an existing `.gitignore` entry; none is - committed. -- The `lychee.toml` exclude list is the single source of truth for flaky - external endpoints. If a real link must be excluded, add it there and - explain why in a comment so CI and local runs stay in sync. -- The skill assumes the docs build succeeds. A Hugo build error is treated - the same as a link failure β€” surface it and stop. -- The `include_verbatim = false` setting in `lychee.toml` skips links inside - code blocks. That is intentional today; flip it on if you specifically need - to validate examples. - -## Related skills - -- `review-docs` β€” prose, KDoc/Javadoc, and Markdown style review. Runs in - parallel with `check-links` when invoked by `pre-pr`. -- `pre-pr` β€” composes the above and gates `gh pr create`. diff --git a/.agents/skills/move-files/SKILL.md b/.agents/skills/move-files/SKILL.md deleted file mode 100644 index b92b05d..0000000 --- a/.agents/skills/move-files/SKILL.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: move-files -description: > - Move or rename any files/directories in a repo: preserve history, update all - references and build metadata, verify no stale paths remain. ---- - -# Move Files - -## Workflow - -1. Preflight. - - Run `git status --short`. - - Map each `source -> destination`. - - Classify scope: simple same-module moves stay targeted; package, module, or - cross-module moves need broader inspection. - - Ask before ambiguous mappings, destination conflicts, or unclear semantic - package/module changes. - -2. Search before moving. - - Search all old identifiers: paths, names, resource refs, doc links. - - For Gradle/module/source-set moves, check `settings.gradle.kts`, - `build.gradle.kts`, and `buildSrc`. - - For Kotlin/Java, update package declarations only when package intent - changes. - -3. Move safely. - - Always use `git mv` for tracked files in the repo. If sandboxing blocks - it, request approval; do not use delete/create as a fallback. - - Use filesystem moves only for untracked/generated/out-of-git files. - - Create parent directories first. - - For case-only renames, move through a temporary name. - -4. Repair references. - - Update all references: imports, build metadata, docs, resources, and scripts. - - Start search scope narrow: affected directory, then module, then repo-wide. - - Prefer precise edits; avoid broad replacements on generic names. - -5. Verify. - - Re-run targeted searches for old tokens. - - Run `git status --short` and confirm the delta matches the move. - - Run focused validation for moved files, or state what could not run. - -6. Ensure the version is bumped. - Invoke `/version-bumped` so the branch carries a strictly greater - `version.gradle.kts` than the base ref before any `./gradlew build` - (which can transitively `publishToMavenLocal` and overwrite - consumer-facing snapshots). The skill is a no-op if a bump already - happened earlier on the branch. - -## Repo Notes - -Follow `.agents/project-structure-expectations.md` for module/source-set/test moves. - -## Report - -Return: `Moved[]`, `UpdatedRefs[]`, `Verification[]`, `Risks[]`. diff --git a/.agents/skills/move-files/agents/openai.yaml b/.agents/skills/move-files/agents/openai.yaml deleted file mode 100644 index ba90a9f..0000000 --- a/.agents/skills/move-files/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Move Files" - short_description: "Move files safely across a repo" - default_prompt: "Use $move-files to relocate files or directories in this repository while preserving history, updating references, and verifying the result." diff --git a/.agents/skills/pre-pr/SKILL.md b/.agents/skills/pre-pr/SKILL.md deleted file mode 100644 index 2c81dfd..0000000 --- a/.agents/skills/pre-pr/SKILL.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -name: pre-pr -description: > - Run the pre-PR checklist for this repo: apply the version gate only when - the repository has a root `version.gradle.kts`, run the configured - build/check command per `.agents/running-builds.md`, and invoke the - configured reviewers (`kotlin-review`, `review-docs`, `dependency-audit`, - `check-links`) against the branch diff. On success, write a sentinel file at - `.git/pre-pr.ok` so the `gh pr create` hook can verify the checklist ran - for the current HEAD. Use before opening a PR, or when CI rejected a - branch and you want a fast local repro. ---- - -# Pre-PR checklist (repo-specific) - -You are the pre-PR gate for this repository. You compose the existing -reviewers and the documented repository rules into a single pass that must -succeed before a pull request is opened. - -This skill supports both versioned Gradle Build Tools projects and repositories -that intentionally do not have `version.gradle.kts`. Do not create -`version.gradle.kts` just to satisfy this checklist. When the file is absent -from the project root, the version-bump check is **not applicable**. - -The authoritative standards live in `.agents/`: - -- `.agents/version-policy.md` β€” applies only when the repository has a root - `version.gradle.kts`. -- `.agents/running-builds.md` β€” which build/check command to run. -- `.agents/safety-rules.md` and `.agents/advanced-safety-rules.md` β€” hard - constraints checked by the reviewers. - -## Procedure - -Run steps 1–4 fully before aggregating. Collect all findings; do not stop at -the first failure. - -### 1. Determine scope and repository capabilities - -- Base ref: `master` unless the user provides a different one. -- Changed files: `git diff ...HEAD --name-only` -- Repository root: `git rev-parse --show-toplevel` -- Version gate: check only the repository-root `version.gradle.kts`. - - Absent at both sides β†’ `not-applicable`, continue. - - Present at `HEAD` β†’ enforce in step 2. - - Present at `` but missing at `HEAD` β†’ fail unless the user - explicitly asked to migrate away from Gradle Build Tools versioning. -- Classify changes: - - **proto** β€” any `*.proto` changed - - **code** β€” any `*.kt`, `*.kts`, or `*.java` changed - - **docs** β€” any `*.md` or doc-only source edits changed - - **deps** β€” any file under `buildSrc/src/main/kotlin/io/spine/dependency/` changed - - **site** β€” any file under `docs/**` or `lychee.toml` (triggers Hugo link - check; pure `README.md` or KDoc-only changes do *not* count) - -### 2. Version-bump check - -- Skip when version gate is `not-applicable`. -- Read `version.gradle.kts` at `HEAD`. Read `` only if the file exists - there; if it does not, the file is newly introduced β€” record the introduced - version and continue. -- When both sides have the file: if the version is not strictly greater (semver - + Spine snapshot rules in `.agents/version-policy.md`): if - `.agents/skills/bump-version/` exists, **auto-fix immediately** by invoking - `/bump-version` without asking; otherwise record a Must-fix and continue. - Re-read the file after the fix. If the version is still not strictly greater, - record a Must-fix and continue. If the auto-fix succeeded, recompute the - changed-file list (`git diff ...HEAD --name-only`) before proceeding to - Step 3 β€” the bump commit adds `version.gradle.kts` to the diff. - -### 3. Build or check - -Pick the target per `.agents/running-builds.md`: - -- **proto** changed β†’ `./gradlew clean build` -- Else **code** changed β†’ `./gradlew build` -- Else **docs**-only β†’ `./gradlew dokka` - -If `./gradlew` is absent, read `.agents/running-builds.md` for the -repository-specific command. If that file is also absent, or if none is -documented for the change type, record `build_status=skipped` with the -reason and continue. - -Run the chosen command. On failure, record the first failing task and -continue to step 4 β€” do not abort. Pass `build_status=FAIL` in the context -given to reviewers so they can discount false positives from non-compiling -code. - -### 4. Reviewers (run in parallel) - -Dispatch relevant reviewers concurrently; collect all verdicts before -aggregating. Before dispatching, check that the skill directory exists under -`.agents/skills/`; if a skill is absent, skip it with a note "not applicable -for this repo" rather than failing. - -- **code** changed β†’ `kotlin-review` -- **docs** or KDoc changed β†’ `review-docs` -- **deps** changed β†’ `dependency-audit` -- **site** changed β†’ `check-links` (unless the sentinel short-circuit below - applies) - -**`check-links` sentinel short-circuit.** Read `.git/check-links.ok` (if -present). If `head=` equals the current **full** HEAD SHA and `status=PASS`, skip -dispatch and record `APPROVE` with note "cached from `.git/check-links.ok`" -(caching its ~30 s rebuild+serve cycle; the result is deterministic for a given -HEAD). Otherwise dispatch normally. - -Pass each reviewer: base ref, changed-file list, build result, version result. -When the version check is `not-applicable`, say so explicitly so reviewers don't flag a -missing version bump. - -**Auto-fix policy for reviewer findings:** - -- Findings from `kotlin-review`, `review-docs`, or `dependency-audit` β†’ record - as Must-fix or Should-fix; do **not** auto-apply. Surface them and wait for - user action. -- If a reviewer reports a missing version bump after Step 2 already ran, the - auto-fix did not take β€” record a Must-fix and do not silently re-apply. -- `dependency-audit` reports a **version rollback** β†’ do **not** auto-fix. - Surface it as a Must-fix and wait for user confirmation, because a rollback - can be intentional. - -### 5. Aggregate - -- **PASS**: version check passed or `not-applicable`, build succeeded or - `build_status=skipped` (no documented command for the change type), every - reviewer returned `APPROVE` or `APPROVE WITH CHANGES`, and no unaddressed - Must-fix items remain. -- **FAIL**: anything else. - -### 6. Sentinel - -Write `.git/pre-pr.ok` at the repo root (never under `.claude/`). The `gh pr -create` hook (`.agents/scripts/pre-pr-gate.sh`) checks `head=` and `status=`; -field names in this block are part of that contract. - -``` -head= -branch= -status=PASS|FAIL -timestamp= -build= -build_status=PASS|FAIL|skipped -reviewers= -version=new, introduced:, or "not-applicable"> -``` - -## Output format - -**On PASS** β€” single line: - -``` -Pre-PR: PASS ( vs ) β€” ready to `gh pr create`. -``` - -**On FAIL** β€” header line, then only the items that need attention, each -prefixed with the source reviewer or check: - -``` -Pre-PR: FAIL ( vs ) - -Must fix: -- [kotlin-review] -- [review-docs] - -Should fix: -- [dependency-audit] -``` - -Report nothing about checks that passed. If auto-fixes were applied, list -them in one line before the verdict: `Auto-fixed: .` - -## Notes - -- This skill must NOT create the PR itself. -- This skill must NOT create `version.gradle.kts`. -- The sentinel lives under `.git/` β€” per-clone, never committed. -- Each reviewer is the source of truth for its own checks; this skill only - orchestrates and aggregates. -- This skill may auto-fix a missing version bump by invoking `/bump-version`; - all other fixes require explicit user confirmation. diff --git a/.agents/skills/review-docs/SKILL.md b/.agents/skills/review-docs/SKILL.md deleted file mode 100644 index d936fa2..0000000 --- a/.agents/skills/review-docs/SKILL.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -name: review-docs -description: > - Review documentation changes β€” KDoc/Javadoc inside Kotlin/Java sources and - Markdown docs (`README.md`, `docs/**`) β€” against Spine documentation - conventions. Use when a diff touches doc comments or Markdown, before - opening a doc-affecting PR, or when asked for a documentation review. - Read-only; does not run builds. ---- - -# Review documentation (repo-specific) - -You are the documentation reviewer for a Spine Event Engine project. You -focus strictly on documentation quality β€” prose, KDoc/Javadoc, and Markdown β€” -and deliberately do **not** duplicate the code-review skill (which owns -Kotlin idioms, safety rules, tests, and version-gate checks). - -The authoritative standards live in `.agents/`: - -- `.agents/documentation-guidelines.md` β€” commenting rules, TODO-comment - format, "file/dir names as code", widow/runt/orphan/river rule (with the - diagram at `.agents/widow-runt-orphan.jpg`). -- `.agents/documentation-tasks.md` β€” KDoc-example requirement on APIs; - Javadoc β†’ KDoc conversion rules (`

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

` tags on a text line removed (`"

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

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

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

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

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

` and preserve meaning. - - Prefer short paragraphs and blank lines over HTML formatting. - -## Validate changes - -- For code changes, follow `.agents/running-builds.md`. -- For documentation-only changes in Kotlin/Java sources, prefer `./gradlew dokka`. diff --git a/.agents/skills/writer/agents/openai.yaml b/.agents/skills/writer/agents/openai.yaml deleted file mode 100644 index 44eaa4e..0000000 --- a/.agents/skills/writer/agents/openai.yaml +++ /dev/null @@ -1,5 +0,0 @@ -interface: - display_name: "Writer" - short_description: "Write and update user/developer docs" - default_prompt: "Write or revise documentation in this repository (for example: README.md, docs/**, CONTRIBUTING.md, and API documentation/KDoc). Follow local documentation guidelines in .agents/*.md, keep changes concise and actionable, and include concrete examples and commands where appropriate." - diff --git a/.agents/skills/writer/assets/templates/doc-page.md b/.agents/skills/writer/assets/templates/doc-page.md deleted file mode 100644 index f405b71..0000000 --- a/.agents/skills/writer/assets/templates/doc-page.md +++ /dev/null @@ -1,23 +0,0 @@ -# Title - -## Goal - -State what the reader will accomplish. - -## Prerequisites - -- List versions/tools the reader needs. - -## Steps - -1. Do the first thing. -2. Do the next thing. - -## Verify - -Show how the reader can confirm success. - -## Troubleshooting - -- Common failure: likely cause β†’ fix. - diff --git a/.agents/skills/writer/assets/templates/kdoc-example.md b/.agents/skills/writer/assets/templates/kdoc-example.md deleted file mode 100644 index fdbd9b6..0000000 --- a/.agents/skills/writer/assets/templates/kdoc-example.md +++ /dev/null @@ -1,11 +0,0 @@ -````kotlin -/** - * Explain what this API does in one sentence. - * - * ## Example - * ```kotlin - * // Show the typical usage pattern. - * val result = doThing() - * ``` - */ -```` diff --git a/.agents/skills/writer/assets/templates/kotlin-java-example.md b/.agents/skills/writer/assets/templates/kotlin-java-example.md deleted file mode 100644 index 5517516..0000000 --- a/.agents/skills/writer/assets/templates/kotlin-java-example.md +++ /dev/null @@ -1,13 +0,0 @@ -{{< code-tabs langs="Kotlin, Java">}} - -{{< code-tab lang="Kotlin" >}} -```kotlin -``` -{{< /code-tab >}} - -{{< code-tab lang="Java" >}} -```java -``` -{{< /code-tab >}} - -{{< /code-tabs >}} diff --git a/.agents/widow-runt-orphan.jpg b/.agents/widow-runt-orphan.jpg deleted file mode 100644 index 284b02a..0000000 Binary files a/.agents/widow-runt-orphan.jpg and /dev/null differ diff --git a/.claude/agents b/.claude/agents new file mode 120000 index 0000000..18e96c9 --- /dev/null +++ b/.claude/agents @@ -0,0 +1 @@ +../.agents/shared/claude/agents \ No newline at end of file diff --git a/.claude/agents/review-docs.md b/.claude/agents/review-docs.md deleted file mode 100644 index 0481b24..0000000 --- a/.claude/agents/review-docs.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: review-docs -description: Reviews documentation changes β€” KDoc/Javadoc inside Kotlin/Java sources and Markdown docs (`README.md`, `docs/**`) β€” against Spine documentation conventions. Use proactively when a diff touches doc comments or Markdown, before opening a doc-affecting PR, or when the user asks for a documentation review. Read-only; does not run builds. -tools: Read, Grep, Glob, Bash -model: inherit ---- - -Follow the `review-docs` skill exactly: - -- Skill: `.agents/skills/review-docs/SKILL.md` -- The skill owns the review procedure, the per-area checks (KDoc/Javadoc, - Markdown, prose flow, terminology), and the output format - (Must fix / Should fix / Nits + one-line verdict). -- Scope yourself to documentation only. If you spot a code-quality issue, - surface it briefly as a Nit pointing at the `kotlin-review` agent β€” - do not expand the review. -- Read-only: use `Read`, `Grep`, `Glob`, and `Bash` solely for `git diff` - and related read-only inspection. Do not run builds. diff --git a/.claude/commands b/.claude/commands new file mode 120000 index 0000000..ad85cd8 --- /dev/null +++ b/.claude/commands @@ -0,0 +1 @@ +../.agents/shared/claude/commands \ No newline at end of file diff --git a/.claude/commands/move-files.md b/.claude/commands/move-files.md deleted file mode 100644 index 25885f9..0000000 --- a/.claude/commands/move-files.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: Move or rename files/directories, updating all references and build metadata. -argument-hint: " " -allowed-tools: Read, Edit, Bash(git mv:*), Bash(git status:*), Bash(git ls-files:*), Grep, Glob ---- - -Follow the `move-files` skill exactly: - -- Skill: `.agents/skills/move-files/SKILL.md` -- Operation: $ARGUMENTS -- Preflight (run `git status --short`, classify scope) -> Search for all old identifiers -> Move with `git mv` -> Repair references (imports, build metadata, docs) -> Verify. -- Report: Moved[], UpdatedRefs[], Verification[], Risks[]. diff --git a/.claude/commands/pre-pr.md b/.claude/commands/pre-pr.md deleted file mode 100644 index 24499cc..0000000 --- a/.claude/commands/pre-pr.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -description: Run the applicable pre-PR checklist (version gate, build/check, reviewers) and write a sentinel so `gh pr create` is unblocked. -argument-hint: "[base-ref]" -allowed-tools: Read, Write, Grep, Glob, Agent, Bash ---- - -Follow the `pre-pr` skill exactly: - -- Skill: `.agents/skills/pre-pr/SKILL.md` -- Base ref: $ARGUMENTS (treat empty as `master`). -- Detect whether the repository-root `version.gradle.kts` exists. If it is - absent at both the base ref and `HEAD`, the version check is `N/A`; do not - create the file and do not ask for `/bump-version`. -- Run the build/check command selected by the skill and - `.agents/running-builds.md`. The command may be Gradle or non-Gradle. -- Dispatch the reviewers as Claude subagents in parallel β€” send a single - message with multiple Agent tool uses: - - `kotlin-review` when `.kt|.kts|.java` files changed. - - `review-docs` when `.md` files or KDoc inside sources changed. - - `dependency-audit` when any file under - `buildSrc/src/main/kotlin/io/spine/dependency/` changed. -- Pass the version-check status to reviewers. If it is `N/A`, tell them: - "This repository has no root `version.gradle.kts`; a version bump is not - applicable and must not be reported as missing." -- Each reviewer is read-only; do not pass it edit tools. -- On any reviewer returning `REQUEST CHANGES`, treat the overall result - as `FAIL` and stop before writing the sentinel as `PASS`. -- Sentinel location: `$(git rev-parse --show-toplevel)/.git/pre-pr.ok`, - format per the skill (`head=`, `branch=`, `status=`, `timestamp=`, - `build=`, `reviewers=`, `version=`). Use `git rev-parse HEAD` for the - SHA and `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. -- Do NOT run `gh pr create`. That is the user's next step. diff --git a/.claude/commands/review-docs.md b/.claude/commands/review-docs.md deleted file mode 100644 index f8043f0..0000000 --- a/.claude/commands/review-docs.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Review documentation changes (KDoc/Javadoc and Markdown) against Spine documentation conventions. -argument-hint: "[base-ref | --staged | paths...]" -allowed-tools: Read, Grep, Glob, Bash(git diff:*), Bash(git log:*), Bash(git status:*), Bash(git rev-parse:*), Bash(git ls-files:*) ---- - -Follow the `review-docs` skill exactly: - -- Skill: `.agents/skills/review-docs/SKILL.md` -- Scope / flags: $ARGUMENTS - - Empty: review the current branch's diff against `master` (`git diff master...HEAD`). - - `--staged`: review staged changes only (`git diff --staged`). - - A base ref (e.g. `master`, `origin/master`, a commit SHA): review `git diff ...HEAD`. - - Explicit paths: limit the review to those paths in addition to the diff scope. -- The skill owns the procedure, the per-area checks (KDoc/Javadoc, Markdown, - prose flow, terminology), and the output format (Must fix / Should fix / - Nits + one-line verdict). -- Stay in scope: documentation only. If a code-quality issue surfaces, - note it briefly as a Nit pointing at `/review` (or the `kotlin-review` - agent) β€” do not expand the review. -- Read-only: do not edit files, do not run builds. diff --git a/.claude/commands/update-copyright.md b/.claude/commands/update-copyright.md deleted file mode 100644 index 076fb61..0000000 --- a/.claude/commands/update-copyright.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -description: Refresh copyright headers from the IntelliJ profile, replacing today.year with the current year. -argument-hint: "[paths...]" -allowed-tools: Bash(python3 .agents/skills/update-copyright/scripts/update_copyright.py:*), Read ---- - -Follow the `update-copyright` skill exactly: - -- Skill: `.agents/skills/update-copyright/SKILL.md` -- Run: `python3 .agents/skills/update-copyright/scripts/update_copyright.py $ARGUMENTS` -- If $ARGUMENTS is empty, run once with `--dry-run`, show the output to the user, then run without `--dry-run`. -- Never add a header to a file that doesn't already have one. diff --git a/.claude/commands/write-docs.md b/.claude/commands/write-docs.md deleted file mode 100644 index b9b9a74..0000000 --- a/.claude/commands/write-docs.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -description: Write or update Markdown / KDoc documentation per Spine documentation conventions. -argument-hint: "" -allowed-tools: Read, Edit, Write, Grep, Glob ---- - -Follow the `writer` skill exactly: - -- Skill: `.agents/skills/writer/SKILL.md` -- Topic / target: $ARGUMENTS -- Decide audience first (end user, contributor, maintainer, tooling). -- Prefer updating an existing doc over creating a new one. -- Keep `docs/data/docs/

//sidenav.yml` in sync when adding, removing, moving, or renaming pages under `docs/content/docs/
/`. -- Honor `.agents/documentation-guidelines.md` and `.agents/documentation-tasks.md`. diff --git a/.claude/settings.json b/.claude/settings.json index 4fdcab8..337c904 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,5 +1,6 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", + "plansDirectory": ".claude/plans", "permissions": { "allow": [ "Bash(git status:*)", @@ -30,7 +31,9 @@ "Bash(touch:*)", "Bash(python3 .agents/skills/update-copyright/scripts/update_copyright.py:*)", "Bash(./config/pull)", - "Bash(./config/migrate)" + "Bash(./config/migrate)", + "Skill(pre-pr)", + "Skill(pre-pr:*)" ], "deny": [ "Bash(git push:*)", @@ -49,6 +52,27 @@ ] }, "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/init-submodules" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/secret-scan-gate.sh" + } + ] + } + ], "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 81c8d50..039657b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,8 +10,27 @@ repository root β€” read it first. If `.agents/project.md` exists, read it before reviewing. It provides the language, architecture, role, and code review checklist for this specific repo. -Additional guidelines are in `.agents/` β€” see `.agents/_TOC.md` for the index -(if present; Hugo repos do not include this file). +Additional guidelines are in `.agents/guidelines/` β€” see +`.agents/guidelines/_TOC.md` for the index. + +## Do not review + +Never review `gradlew` or `gradlew.bat` in any repository, including `config`. +These files are provided by Gradle and are not edited manually. + +If the current repository is `config`, review its files normally unless noted +above: they are authoritative there. In other repositories, the following files are managed by +the `config` submodule and must be reviewed in the `config` repository, not +here. In those consumer repositories, skip them without comment: + +- `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` +- `.agents/**` (except `.agents/project.md`) +- `.claude/**`, `.idea/**`, `.junie/**` +- `.github/copilot-instructions.md` +- `buildSrc/**` (except `buildSrc/src/main/kotlin/module.gradle.kts`) +- `gradle/`, `gradlew`, `gradlew.bat` +- `.codecov.yml`, `.gitignore`, `gradle.properties`, `lychee.toml` +- `.github/workflows/` β€” unless the workflow was introduced by this repo ## Universal rules diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 7a51f8f..6755ff9 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -33,10 +33,15 @@ jobs: cancel-in-progress: true steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Detect the Hugo site root (`docs/` or `site/`) by looking for a Hugo - # config file. Outputs `present=true|false` and `site_dir=docs|site`. + # config file. Hugo config may live directly in the site root or in a + # `config/` or `config/_default/` subdirectory (both layouts are valid). + # Outputs `present=true|false` and `work_dir` (the directory where + # `npm ci` / `hugo` commands should run β€” either `$dir/_preview` for + # repos that use a separate preview sub-tree, or `$dir` for repos whose + # Node/Hugo setup lives at the site root). # When neither directory has a Hugo config, the job short-circuits to a # success so that this shared workflow stays green on repos that do not # host a Hugo site at all. @@ -44,15 +49,21 @@ jobs: id: docs run: | for dir in docs site; do - for cfg in hugo.toml hugo.yaml; do + for cfg in hugo.toml hugo.yaml \ + config/hugo.toml config/hugo.yaml \ + config/_default/hugo.toml config/_default/hugo.yaml; do if [ -f "$dir/$cfg" ]; then - echo "site_dir=$dir" >> "$GITHUB_OUTPUT" if [ -f "$dir/_preview/package-lock.json" ]; then + echo "work_dir=$dir/_preview" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" - echo "::notice::Hugo site found under $dir/" + echo "::notice::Hugo site found under $dir/ (work_dir: $dir/_preview)" + elif [ -f "$dir/package-lock.json" ]; then + echo "work_dir=$dir" >> "$GITHUB_OUTPUT" + echo "present=true" >> "$GITHUB_OUTPUT" + echo "::notice::Hugo site found under $dir/ (work_dir: $dir)" else echo "present=false" >> "$GITHUB_OUTPUT" - echo "::notice::Hugo config found in $dir/ but $dir/_preview/package-lock.json is missing β€” skipping link check." + echo "::notice::Hugo config found in $dir/ but no package-lock.json found β€” skipping link check." fi exit 0 fi @@ -78,7 +89,7 @@ jobs: with: node-version: '26' cache: 'npm' - cache-dependency-path: ${{ steps.docs.outputs.site_dir }}/_preview/package-lock.json + cache-dependency-path: ${{ steps.docs.outputs.work_dir }}/package-lock.json # `HUGO_CACHEDIR=/tmp/hugo_cache` (set in `env:` above) makes Hugo # actually write to the path this step restores from. The key hashes @@ -95,12 +106,12 @@ jobs: - name: Install Dependencies if: steps.docs.outputs.present == 'true' - working-directory: ${{ steps.docs.outputs.site_dir }}/_preview + working-directory: ${{ steps.docs.outputs.work_dir }} run: npm ci - name: Build docs preview site if: steps.docs.outputs.present == 'true' - working-directory: ${{ steps.docs.outputs.site_dir }}/_preview + working-directory: ${{ steps.docs.outputs.work_dir }} run: hugo -e development # Cache Lychee results to avoid hitting rate limits. @@ -183,7 +194,7 @@ jobs: # Lychee step is visible β€” change one, change the other. - name: Start Hugo server if: steps.docs.outputs.present == 'true' - working-directory: ${{ steps.docs.outputs.site_dir }}/_preview + working-directory: ${{ steps.docs.outputs.work_dir }} run: | nohup hugo server \ --environment development \ @@ -202,4 +213,4 @@ jobs: run: | ./lychee/lychee --config lychee.toml --timeout 60 \ --base-url http://localhost:1313/ \ - '${{ steps.docs.outputs.site_dir }}/_preview/public/**/*.html' + '${{ steps.docs.outputs.work_dir }}/public/**/*.html' diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..6f0130e --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,70 @@ +name: Secret scan + +# Defense-in-depth behind the local `secret-scan` pre-commit hook and the +# `.gitignore` secret patterns: if a credential is committed despite those, this +# fails the pull request before it can merge. Distributed to every Spine repo by +# `./config/pull`. + +on: + pull_request: + push: + branches: + - master + - main + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + env: + # Pinned gitleaks version β€” bump through the usual dependency-update process. + GITLEAKS_VERSION: "8.21.2" + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # Full history so a pull request's commit range can be scanned. + fetch-depth: 0 + + - name: Install gitleaks + # Run gitleaks as the runner user against the checkout it owns β€” no + # container, so no "dubious ownership" git error and no GitHub Action + # org-licence requirement. + run: | + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xzf - gitleaks + ./gitleaks version + + - name: Scan + env: + EVENT: ${{ github.event_name }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + BEFORE: ${{ github.event.before }} + AFTER: ${{ github.sha }} + run: | + if [ "$EVENT" = pull_request ]; then + # Scan the PR's own commit RANGE: a secret added in one commit and + # deleted in a later commit of the same PR is still caught (a + # working-tree scan would miss it, yet merging keeps the secret-bearing + # commit reachable), while already-rotated secrets in older history + # outside base..head are not re-flagged. + ./gitleaks git --log-opts="$BASE..$HEAD" --redact --verbose --exit-code=1 . + else + # Push to a default branch: scan the pushed commit RANGE (before..after) + # so an add-then-remove batch is caught here too, not only on PRs β€” the + # leaked commit would otherwise stay reachable on the default branch. A + # branch's first push reports an all-zero `before` (no range); fall back + # to a working-tree scan then. + if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ]; then + ./gitleaks git --log-opts="$BEFORE..$AFTER" --redact --verbose --exit-code=1 . + else + # Branch's first push (all-zero `before`): no range to diff against, so + # scan the whole history reachable from the pushed tip as the initial + # import β€” an add-then-remove within those commits is still caught. + ./gitleaks git --log-opts="$AFTER" --redact --verbose --exit-code=1 . + fi + fi diff --git a/.gitignore b/.gitignore index 472e7ab..ecac2a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,51 @@ -# Hugo cache files -docs/_preview/resources -public +# >>> shared config (managed by ./config/pull -- do not edit inside this block) >>> +# +# Copyright 2025, TeamDev. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# -# Cache folder for Hugo Modules -docs/_preview/_vendor +# +# This file is used for two purposes: +# 1. ignoring files in the `config` project. +# 2. ignoring files in the projects that import `config` as a sub-module. +# +# Therefore, instructions below are superset of instructions required for all the projects. -# Node modules -node_modules +# Temporary output of AI agents. +.output -# Used to control concurrency between multiple Hugo instances -docs/_preview/.hugo_build.lock +# `jenv` local configuration. +.java-version -# Hugo build output -docs/_site/ +# Internal tool directories. +.fleet/ +.junie/memory/ -# Needed for navigation/intellisense help inside code editors -jsconfig.json +# Kotlin temp directories. +**/.kotlin/ -# IntelliJ IDEA modules and interim config files +# IntelliJ IDEA modules and interim config files. *.iml .idea/*.xml .idea/.name @@ -26,23 +54,197 @@ jsconfig.json .idea/modules .idea/shelf -!.idea/misc.xml +# `.idea/misc.xml` is intentionally NOT re-included below. It is project-local β€” +# it holds the per-project JDK name and IDEA's own churn (entry-point list +# indices, external-storage toggles) β€” so `.idea/*.xml` above keeps it ignored. +# `./config/pull` (via `migrate`) untracks any copy an earlier pull committed. + +# Do not ignore the following IDEA settings +!.idea/codeStyleSettings.xml !.idea/codeStyles/ !.idea/copyright/ -# The `embed-code` temporary directory -_code/build - -# Local Java version for this project -.java-version +# Ignore IDEA config files under `tests` +/tests/.idea/** # Gradle interim configs -.gradle/ +**/.gradle/** + +# Temp directory for Gradle TestKit runners +**/.gradle-test-kit/** + +# Integration test log files +/tests/_out/** + +# Generated source code +**/generated/** +**/*.pb.dart +**/*.pbenum.dart +**/*.pbserver.dart +**/*.pbjson.dart + +# Generated source code with custom path under `tests` +/tests/**/proto-gen/** # Gradle build files +**/build/** +!**/src/**/build/** + +# Build files produced by the IDE +**/out/** + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# Spine internal directory for storing intermediate artifacts +**/.spine/** + +# --------------------------------------------------------------------------- +# Secrets β€” NEVER commit these. +# +# Encrypted credentials live under `.github/keys/*.gpg` and ARE committed. +# `config/scripts/decrypt.sh` turns each into its PLAINTEXT twin at build / CI / +# publish time (e.g. `spine-dev-framework-ci.json.gpg` -> `spine-dev.json`). The +# decrypted twins below β€” and any private key or service-account file β€” must stay +# out of Git. The shared `secret-scan` pre-commit hook is the backstop if one ever +# slips past these patterns. +# --------------------------------------------------------------------------- + +# Maven repository login details; each workstation defines its own. +credentials.tar +credentials.properties +cloudrepo.properties +deploy_key_rsa +gcs-auth-key.json + +# Decrypted Google / GCP service-account keys (plaintext twins of *.gpg). +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json + +# Decrypted credential property files and portal / publisher secrets. +*.secret.properties + +# Private SSH keys (public keys are *.pub and remain committable). +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 + +# ...but always keep the committed ENCRYPTED forms. +!*.gpg + +# Log files +*.log + +# Package Files # +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.packages +pubspec.lock + +# Ignore the `tmp` directory used for building dependant repositories. +/tmp + +.gradle-test-kit/ + +# Python cache +__pycache__/ +*.pyc + +# Claude working files +/.claude/worktrees/ +# Ephemeral plan-mode scratch (durable task docs live in `.agents/tasks/`). +/.claude/plans/ + +# Personal, per-developer Claude Code settings overrides (never committed; +# the distributed `.claude/settings.json` is the shared, committed layer). +/.claude/settings.local.json + +# Auto-downloaded Lychee binary used by the `check-links` skill. +/.agents/skills/check-links/.cache/ + +# Lychee link-checker cache (created by the `check-links` skill and +# the `Check Links` workflow when run locally). +.lycheecache + +# Hugo docs preview site build artifacts (used by the `check-links` +# skill and the `Check Links` workflow in repos that contain a +# `docs/_preview` Hugo site). +docs/_preview/node_modules/ +docs/_preview/public/ +docs/_preview/resources/ +# <<< shared config <<< + +# >>> repo-local entries (preserved across ./config/pull) >>> +# Hugo cache files +docs/_preview/resources +public +# Cache folder for Hugo Modules +docs/_preview/_vendor +# Node modules +node_modules +# Used to control concurrency between multiple Hugo instances +docs/_preview/.hugo_build.lock +# Hugo build output +docs/_site/ +# Needed for navigation/intellisense help inside code editors +jsconfig.json +# IntelliJ IDEA modules and interim config files +!.idea/codeStyles/ +!.idea/copyright/ +# The `embed-code` temporary directory +_code/build +# Local Java version for this project +.gradle/ build/ generated/ - # The `check-links` cache directory and Lychee cache. -/.agents/skills/check-links/.cache/ /.lycheecache +# <<< repo-local entries <<< + +# >>> secret ignores re-asserted last (managed by ./config/pull -- do not edit) >>> +credentials.tar +credentials.properties +cloudrepo.properties +deploy_key_rsa +gcs-auth-key.json +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json +*.secret.properties +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 +# <<< secret ignores <<< diff --git a/.gitmodules b/.gitmodules index e7f6727..092dddc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,9 @@ [submodule "config"] path = config url = https://github.com/SpineEventEngine/config +[submodule ".agents/shared"] + path = .agents/shared + url = https://github.com/SpineEventEngine/agents.git + branch = master + update = merge + ignore = all diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 6e6eec1..0f7bc51 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,6 +1,5 @@ - \ No newline at end of file + diff --git a/.idea/live-templates/README.md b/.idea/live-templates/README.md index 66713b3..267008a 100644 --- a/.idea/live-templates/README.md +++ b/.idea/live-templates/README.md @@ -5,12 +5,12 @@ This directory contains two live template groups: 1. `Spine.xml`: shortcuts for the repeated patterns used in the framework. 2. `User.xml`: a single shortcut to generate TODO comments. -### Instlallation +### Installation Live templates are not picked up by IDEA automatically. They should be added manually. In order to add these templates, perform the following steps: -1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA +1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA [settings folder][settings_folder]. 2. Restart IntelliJ IDEA: `File -> Invalidate Caches -> Just restart`. 3. Go to `Preferences -> Editor -> Live Templates`. @@ -22,6 +22,6 @@ In order to add these templates, perform the following steps: 1. Open the corresponding template: `Preferences -> Editor -> Live Templates -> User.todo`. 2. Click on `Edit variables`. -3. Set `USER` variable to your domain email address without `@teamdev.com` ending. For example, - for `jack.sparrow@teamdev.com` use the follwoing expression `"jack.sparrow"`. +3. Set `USER` variable to your domain email address without `@teamdev.com` ending. For example, + for `jack.sparrow@teamdev.com` use the following expression `"jack.sparrow"`. 4. Verify that the template generates expected comments: `// TODO:2022-11-03:jack.sparrow: <...>`. diff --git a/.idea/live-templates/User.xml b/.idea/live-templates/User.xml index cc15650..958e2ea 100644 --- a/.idea/live-templates/User.xml +++ b/.idea/live-templates/User.xml @@ -1,7 +1,7 @@