diff --git a/.agents/guidelines/writing-style.md b/.agents/guidelines/writing-style.md new file mode 100644 index 0000000..6edb8d1 --- /dev/null +++ b/.agents/guidelines/writing-style.md @@ -0,0 +1,47 @@ +# Writing style + +Apply these rules to changed Markdown and KDoc prose. Preserve a file's valid local +structure when it is more specific. + +## Write for the reader + +- Explain the reader's problem before the implementation or solution details. +- Use direct, active sentences and concrete verbs. +- Prefer precise project terms over synonyms, marketing language, or jargon. +- State prerequisites before steps and expected outcomes after them. +- Verify factual claims against current code, tests, build configuration, or behavior. + +## Format prose consistently + +- Keep changed Markdown and KDoc prose at no more than 100 characters per source line. +- Use sentence case for headings. +- Use one top-level heading and do not skip heading levels. +- Format paths, identifiers, Gradle tasks, properties, flags, commands, and literals as code. +- Put multiline commands, configuration, source, output, and other machine text in fenced + code blocks with a language identifier when one applies. +- Align Markdown tables in source: pad every cell so column pipes line up vertically, + and size separator cells to the padded column widths. Re-align the full table after any edit. +- Use relative links for repository files. +- Prefer reference-style links for external destinations. + +## Use typography deliberately + +- Reserve typographic quotation marks for actual page or section titles, such as “Requirements”. +- Do not use quotation marks for emphasis, identifiers, invented labels, or technical + terms. Use plain prose, italics, or code formatting as appropriate. +- Do not leave runts or orphans. Reflow or rewrite a paragraph, list item, or table cell + whose final source line contains one word or an unusually short fragment. +- Avoid widows in rendered or paginated material. Keep headings with their following + content and avoid isolating a paragraph's opening or closing line. +- Avoid rivers when a rendered layout shows repeated aligned gaps through adjacent lines. + Reflow or rephrase the smallest affected passage. +- Do not add manual line breaks or non-breaking spaces merely to force a Markdown layout. + +## Edit minimally + +- Correct grammar only where needed for correctness, clarity, or consistency. +- Limit rewording and reflow to the affected sentence or paragraph. +- Preserve the author's meaning, the document's ownership, and established terminology. +- Do not restyle unrelated prose while making a focused change. +- Preserve code, machine-generated text, exact command output, logs, URLs, frontmatter, + serialized data, and license or copyright text verbatim unless the task targets it. diff --git a/.agents/skills/gradle-engineer/SKILL.md b/.agents/skills/gradle-engineer/SKILL.md new file mode 100644 index 0000000..57d5fb4 --- /dev/null +++ b/.agents/skills/gradle-engineer/SKILL.md @@ -0,0 +1,58 @@ +--- +name: gradle-engineer +description: >- + Apply repository-specific Gradle engineering policy when changing or reviewing + build scripts, buildSrc, Kotlin DSL, GitHub Actions workflows, CI configuration, + plugin, extension, or task APIs, lazy configuration, task state, + configuration-cache behavior, TestKit coverage, consumer compatibility, + or publication metadata. +--- + +# Gradle engineering + +Preserve Gradle semantics across the plugin and its build. Treat configuration avoidance, +task state, consumer compatibility, and cross-platform behavior as public contracts. + +## Start + +1. Read [`PROJECT.md`](../../../PROJECT.md) for the project map and supported behavior. +2. Read the affected build scripts, plugin API, and task types, along with + nearby unit and functional tests in full. +3. Trace values from the extension through `Provider` wiring into task inputs, + execution behavior, outputs, and local state. +4. Read [Gradle practices](references/gradle-practices.md) before editing; use it + as the source of truth for Gradle implementation and verification policy. +5. Clarify only material ambiguity in the public DSL, compatibility floor, or test target. + +## Preserve CI behavior + +- Read each changed workflow and the Gradle configuration it invokes in full. +- Preserve least-privilege permissions, supported operating-system coverage, + compatibility toolchains, and wrapper-based execution unless the request changes them. +- Keep CI commands aligned with local verification and publication tasks. + +## Cross-domain work + +- Apply [`kotlin-engineer`](../kotlin-engineer/SKILL.md) to Kotlin source and build logic. +- Apply [`security-engineer`](../security-engineer/SKILL.md) to release + downloads, integrity metadata, offline reuse, tokens, archives, and paths. + +## Verify + +Run the narrowest relevant command first, then broaden: + +1. `./gradlew :gradle-plugin:test --tests ` +2. `./gradlew :gradle-plugin:functionalTest --tests ` +3. `./gradlew :gradle-plugin:test :gradle-plugin:functionalTest` +4. `./gradlew :gradle-plugin:validatePlugins` +5. `./gradlew check` + +Add or run the minimum-version TestKit scenario when compatibility-sensitive +code changes. Exercise configuration-cache reuse when plugin or task wiring changes. +Exercise platform-specific behavior on the relevant operating system; report any +unavailable supported-platform probe instead of treating another host as proof. + +## Report + +Summarize the Gradle contract changed, focused and full checks run, supported +consumer/platform coverage, and any remaining unverified behavior. diff --git a/.agents/skills/gradle-engineer/agents/openai.yaml b/.agents/skills/gradle-engineer/agents/openai.yaml new file mode 100644 index 0000000..665a83e --- /dev/null +++ b/.agents/skills/gradle-engineer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Gradle Engineer" + short_description: "Build robust Gradle plugin, build logic, and CI" + default_prompt: "Use $gradle-engineer to implement this Gradle, build-logic, or CI change." diff --git a/.agents/skills/gradle-engineer/references/gradle-practices.md b/.agents/skills/gradle-engineer/references/gradle-practices.md new file mode 100644 index 0000000..c29b53b --- /dev/null +++ b/.agents/skills/gradle-engineer/references/gradle-practices.md @@ -0,0 +1,163 @@ +# Gradle practices + +Use these rules when changing build logic or the Gradle-facing implementation. +Verify current versions and paths in the checkout before relying on this map. + +## Contents + +- [Project ownership](#project-ownership) +- [Lazy configuration](#lazy-configuration) +- [Task state and caching](#task-state-and-caching) +- [Configuration cache](#configuration-cache) +- [Compatibility and Kotlin runtime](#compatibility-and-kotlin-runtime) +- [Kotlin DSL and build logic](#kotlin-dsl-and-build-logic) +- [Cross-platform behavior](#cross-platform-behavior) +- [Publication](#publication) +- [Test strategy](#test-strategy) +- [Repository exclusions](#repository-exclusions) + +## Project ownership + +- Keep repository composition and dependency repositories in `settings.gradle.kts`. +- Keep shared coordinates and version application in the root build and `version.gradle.kts`. +- Keep build-only constants and dependency coordinates in `buildSrc/`. +- Keep shared JVM compilation and test setup in the `jvm-module` convention plugin. +- Keep plugin declarations, generated sources, and functional-test sources in + `gradle-plugin/build.gradle.kts`, together with publication metadata. +- Keep extension defaults and task registration in `EmbedCodePlugin`. +- Keep user-configurable values in `EmbedCodeExtension`. +- Keep execution behavior in typed task classes. Keep pure release, platform, + JSON, and checksum rules in focused Kotlin files. + +## Lazy configuration + +- Use `tasks.register`, `tasks.named`, provider mapping, and `flatMap`. +- Pass `TaskProvider` and provider-backed files through the graph; avoid `get()` + and other eager reads during configuration. +- Use conventions for defaults and preserve later user configuration. +- Use `disallowChanges()` only for values owned entirely by the plugin after wiring. +- Preserve task dependencies carried by `Provider` and `ConfigurableFileCollection`. +- Avoid configuration-time downloads, process execution, directory creation, + and file reads whose result belongs to task execution. +- Select underscore-prefixed fallback task names from names already present when + the plugin is applied. Preserve the documented limitation for tasks registered later. + +## Task state and caching + +- Mark scalar and collection configuration with `@Input`. +- Mark source directories with `@InputDirectory` or `@InputFiles`; declare + `@Optional` only when absence is valid. +- Mark consumed artifacts with `@InputFile`. +- Mark reproducible task products with output annotations. +- Mark retained caches, downloaded assets, and integrity markers that should + not enter the build cache with `@LocalState`. +- Mark services, roots used only to validate other properties, working + directories, and secrets with `@Internal`. +- Choose `PathSensitivity.RELATIVE` for project content and + `PathSensitivity.NONE` when only file bytes matter. +- Disable build caching for in-place document mutation, external mutable + release state, or tasks whose primary purpose is validating local state. +- Override up-to-date behavior only with an explicit invariant; keep the install + task configured to rerun so it authenticates local installation state. + +Classify each property by its role in task state: + +```kotlin +// Correct: configuration is an input, retained cache data is local state, +// and a secret stays out of task fingerprints. +@get:Input +public abstract val version: Property + +@get:LocalState +public abstract val cachedAssetFile: RegularFileProperty + +@get:Internal +public abstract val githubToken: Property +``` + +Do not flatten properties with different roles into inputs or outputs: + +```kotlin +// Incorrect: the secret enters task fingerprints, and mutable cache data +// is presented as a reproducible task output. +@get:Input +public abstract val githubToken: Property + +@get:OutputFile +public abstract val cachedAssetFile: RegularFileProperty +``` + +## Configuration cache + +- Inject `ExecOperations` and other Gradle services into task types. +- Resolve extension values into task properties during configuration. +- Avoid retaining `Project`, extension objects, closures over project state, or + arbitrary mutable objects for task execution. +- Keep task actions dependent only on declared task properties, injected + services, and intentional internal/local state. +- Verify a second invocation reports configuration-cache reuse after changing + plugin application, registration, task properties, or process execution. + +## Compatibility and Kotlin runtime + +- Read the minimum supported Gradle version from `README.md` and keep it covered + by a versioned TestKit scenario. +- Treat the wrapper as the development runtime, not as the consumer floor. +- Read the build JDK and bytecode targets from `BuildSettings`; read Kotlin + language and API levels from `jvm-module.gradle.kts`. +- Keep plugin standard-library dependencies `compileOnly`. Supply explicit test + runtime dependencies where tests need Gradle and Kotlin classes. +- Avoid Kotlin or Gradle APIs introduced after the supported consumer floor + unless a compatible alternative and versioned TestKit coverage protect their use. +- Change the floor together with `README.md`, compatibility tests, relevant + build settings, and publication claims. + +## Kotlin DSL and build logic + +- Prefer typed Kotlin DSL accessors and typed task registration. +- Keep reusable module policy in convention plugins rather than copying blocks between projects. +- Keep dependency coordinates centralized only when that improves ownership; + avoid abstraction for a single opaque use. +- Keep public plugin configuration in `build.gradle.kts`. Do not require users + to maintain an additional YAML file. +- Treat internally generated JSON configuration as task implementation state, + not as a public configuration surface. + +## Cross-platform behavior + +- Use Gradle file properties and Java path APIs instead of string concatenation for paths. +- Preserve Windows `.exe` naming and Linux/macOS executable permissions. +- Keep path comparisons case- and separator-aware. Do not use a probe on one + supported operating system as proof for another. +- Avoid shell-specific execution; pass executable and arguments separately. +- Preserve stable argument ordering and messages when tests or users rely on them. +- Add platform conditions only for unsupported host behavior; do not hide portable failures. + +## Publication + +- Keep `io.spine.embed-code` and its implementation class stable unless migration is requested. +- Keep display name, description, website, VCS URL, and tags consistent with README terminology. +- Keep Maven artifact ID, POM name, description, license, developers, and SCM + coordinates aligned with the plugin declaration. +- Keep `LICENSE` in published JARs and keep sources and Javadoc artifacts. +- Run `validatePlugins` after changing task annotations, plugin declarations, + extension types, or publication configuration. + +## Test strategy + +- Use unit tests for pure version, checksum, JSON, and platform rules. +- Use TestKit functional tests for plugin application, DSL wiring, task + dependencies, task-name collisions, help output, configuration-cache reuse, + publication-facing behavior, and real Gradle failures. +- Keep a versioned TestKit probe on the minimum supported Gradle version. +- Assert task outcomes, generated files, process arguments, and actionable + output rather than internal registration details alone. +- Run focused tests while iterating, then unit and functional tests, `validatePlugins`, and `check`. + +## Repository exclusions + +- Preserve `FAIL_ON_PROJECT_REPOS` in `settings.gradle.kts`; do not add + organization-wide repository content filters unless this repository explicitly adopts them. +- Do not require dependency group names to contain `spine`. +- Keep `installEmbedCode` ungrouped and absent from the standard task list; keep + `checkEmbedding` and `embedCode` as the user-facing tasks. diff --git a/.agents/skills/kotlin-engineer/SKILL.md b/.agents/skills/kotlin-engineer/SKILL.md new file mode 100644 index 0000000..e7cf06e --- /dev/null +++ b/.agents/skills/kotlin-engineer/SKILL.md @@ -0,0 +1,32 @@ +--- +name: kotlin-engineer +description: >- + Use for implementing, refactoring, explaining, or reviewing Kotlin source and + Kotlin DSL files (`.kt` and `.kts`) in this repository, especially public + plugin APIs, Gradle task types, extensions, and build logic. +--- + +# Kotlin engineer + +## Workflow + +1. Read [the project context](../../../PROJECT.md), the complete affected files, + their nearest tests, and the build configuration that defines their runtime. +2. Identify whether the change affects a public Gradle API, task execution, + build configuration, serialization, file handling, or network behavior. +3. Read [the Kotlin policy](references/kotlin-policy.md) before editing; use it + as the source of truth for compatibility, declarations, style, failures, and KDoc. +4. Preserve the existing architecture and behavior outside the requested scope. + Prefer the smallest idiomatic change that makes the contract explicit. +5. Add or update tests through + [test-engineer](../test-engineer/SKILL.md) for every behavioral change. +6. Verify with the narrowest relevant Gradle task, then run the broader check + required by the changed surface. +7. Report the affected contract, files changed, and verification performed. + +## Cross-domain work + +- Apply [gradle-engineer](../gradle-engineer/SKILL.md) when Kotlin declarations + participate in Gradle APIs, task modeling, providers, or plugin lifecycle. +- Apply [security-engineer](../security-engineer/SKILL.md) when Kotlin changes affect + downloads, checksums, archives, paths, caches, processes, or credentials. diff --git a/.agents/skills/kotlin-engineer/agents/openai.yaml b/.agents/skills/kotlin-engineer/agents/openai.yaml new file mode 100644 index 0000000..3631e8f --- /dev/null +++ b/.agents/skills/kotlin-engineer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Kotlin Engineer" + short_description: "Implement idiomatic, compatible Kotlin changes" + default_prompt: "Use $kotlin-engineer to implement this Kotlin change in the repository." diff --git a/.agents/skills/kotlin-engineer/references/kotlin-policy.md b/.agents/skills/kotlin-engineer/references/kotlin-policy.md new file mode 100644 index 0000000..9011c86 --- /dev/null +++ b/.agents/skills/kotlin-engineer/references/kotlin-policy.md @@ -0,0 +1,92 @@ +# Kotlin policy + +Use this reference while changing Kotlin source or Kotlin DSL in this +repository. Prefer nearby established code when it is more specific. + +## Compatibility boundary + +- Build with the configured toolchain while emitting Java 17-compatible plugin bytecode. +- Treat Java 17 as the maximum runtime API surface. A successful build on a + newer JDK does not prove consumer compatibility. +- Preserve the configured Kotlin language and API versions and Gradle-supplied runtime. + Consumers must not need to install Kotlin or apply its plugin. +- Keep the Kotlin standard library non-published as configured. Do not replace + `compileOnly` with `implementation` without an explicit compatibility reason. +- Avoid hard-coded environment-specific absolute paths. Use Gradle file + properties and resolve absolute paths only at the task or generated-configuration boundary. + +## Declarations and APIs + +- Add explicit visibility to public declarations and any declaration whose + intended boundary could be ambiguous. +- Add explicit return and property types to public APIs. Prefer an explicit + type internally when it communicates a Gradle or domain contract. +- Avoid exposing mutable collections or implementation-specific mutable state. +- Prefer these Gradle managed properties for configuration: + `Property`, `ListProperty`, `MapProperty`, `DirectoryProperty`, + and `RegularFileProperty` instead of mutable or eagerly resolved values. +- Accept `Provider` when callers should retain lazy evaluation. Do not + realize a provider merely to convert it into an eager value. +- Keep task and extension APIs declarative. Put execution work in task actions, + not property accessors or plugin application. +- Keep user-facing validation close to the configuration or execution boundary. + Use Gradle exception types when the failure is actionable to a build user. + +## Kotlin style + +- Follow the official Kotlin conventions and match the closest file. +- Prefer `val`, expression bodies when they remain readable, exhaustive `when`, + safe calls, Elvis expressions, `require`, `check`, and `requireNotNull`. +- Use a data class only for a value with value-based equality. Do not convert a + service, task, plugin, or stateful object into a data class. +- Keep functions focused. Extract a helper when it gives a rule or invariant a + name; avoid helpers that only obscure one straightforward expression. +- Prefer named arguments when adjacent arguments have the same type or the + call's meaning is otherwise unclear. +- Keep Java interop intentional. Do not add `@JvmStatic`, `@JvmOverloads`, or + other bridge annotations unless a verified consumer needs them. +- Preserve local constant naming instead of normalizing unrelated declarations. +- Do not introduce unrelated coroutines, Flow, multiplatform, Android, Protobuf, or frameworks. + +## Nullability and failures + +- Model optional data as nullable only when absence has one clear meaning. +- Avoid `!!`; prefer validation or a non-null type. If an external API makes an + invariant unrepresentable, document the invariant next to the assertion. +- Catch the narrowest useful exception. Preserve the original cause when + wrapping an internal failure in an actionable Gradle error. +- Keep credentials and tokens out of user-facing messages. Include a filesystem + path only when it identifies an actionable value; do not echo low-level exception text. + +## KDoc + +Document every named type and every public API. Named types include regular, +data, value, sealed, enum, and annotation classes; interfaces; objects; +companion objects; and type aliases. Document every public enum entry. Make the +documentation useful to a plugin consumer or future maintainer. + +Include the applicable parts of this contract: + +- what the declaration represents or performs; +- defaults and whether configuration is required; +- input, output, caching, offline, or network behavior; +- accepted formats and validation rules; +- observable side effects and failures; +- Java 17, Gradle, or Kotlin-runtime compatibility constraints. + +Avoid repeating the declaration in prose: + +```kotlin +/** + * Selects the exact Embed Code release tag to install. + * + * Rejects blank or unsafe tags before resolving an installation path. + */ +public abstract val version: Property +``` + +Use KDoc links only for symbols visible to the documented source set. Do not link published +API documentation to `.agents/`, `buildSrc`, issues, branches, or transient implementation notes. + +For private and internal non-type declarations, add KDoc only when it explains +a non-obvious contract. Prefer a short inline comment for a local invariant. diff --git a/.agents/skills/reviewer/SKILL.md b/.agents/skills/reviewer/SKILL.md new file mode 100644 index 0000000..87a8cbd --- /dev/null +++ b/.agents/skills/reviewer/SKILL.md @@ -0,0 +1,80 @@ +--- +name: reviewer +description: >- + Use when reviewing a diff, pull request, commit, or proposed repository change; + perform a strictly read-only, evidence-based review of the complete changed + files and report prioritized findings followed by a verdict. +--- + +# Repository reviewer + +Keep source, configuration, documentation, Git state, and remote state read-only. Run tests +and read-only diagnostics only when useful, and limit their filesystem side effects to normal +ignored build and test outputs. Never edit files, apply patches, stage, commit, push, publish, +reply to review comments, or resolve review threads. Route implementation to a separate task. + +Read the [project context](../../../PROJECT.md) before reviewing. + +## Establish the review scope + +1. Resolve the requested base, head, commit, or working-tree diff exactly. +2. Inspect repository status without cleaning, staging, switching branches, or fetching. +3. Read the complete diff and every changed file in full. +4. Identify the acceptance source: request, issue, review comment, contract, test, or docs. +5. Trace affected callers, task wiring, data flows, tests, and documentation + far enough to establish the actual impact. +6. Inspect resolved or outdated review threads when the request includes prior comments. + +## Gather evidence + +- Reproduce a reported defect when practical before treating it as open. +- Run focused tests and read-only diagnostics when they materially improve confidence. +- Distinguish observed facts from inference. +- Confirm reachability and user impact before reporting a correctness or security issue. +- Omit speculation; do not turn an incomplete probe or theoretical concern into a defect. +- Reconcile historical findings against the current checkout before repeating them. +- Preserve unrelated local changes and untracked files. + +## Select review lenses + +Load only the guidance relevant to the changed files. Use companion skills as review lenses, +not as permission to implement. Skip their implementation workflows and any command that +exceeds this skill's read-only boundary. + +- Use [Kotlin engineer](../kotlin-engineer/SKILL.md) for Kotlin implementation and API conventions. +- Use [Gradle engineer](../gradle-engineer/SKILL.md) for build logic, task modeling, + configuration cache, compatibility, and publication configuration. +- Use [test engineer](../test-engineer/SKILL.md) for test design and verification depth. +- Use [security engineer](../security-engineer/SKILL.md) for executable, checksum, cache, + path, archive, token, network, and offline trust boundaries. +- Use [writer](../writer/SKILL.md) and the shared + [writing style](../../guidelines/writing-style.md) when reviewing prose, KDoc, + examples, errors, comments, or agent instructions. + +## Report only actionable findings + +Lead with findings. For each finding: + +- Give a concise, specific title. +- Cite the tightest file and line range. +- State the evidence, affected scenario, and concrete impact. +- Explain the smallest safe correction without writing or applying it. +- Avoid praise, summaries of correct code, or style preferences unsupported by project policy. + +Return these sections in order: + +- **Must fix** — confirmed defects that can break behavior, compatibility, security, + published API, builds, or materially false user guidance. +- **Should fix** — confirmed maintainability, testing, diagnostics, or documentation gaps + with meaningful future cost or user confusion. +- **Nits** — optional, non-blocking local improvements. Keep this section short. + +Write `None.` under an empty section. + +Conclude the findings with one verdict: + +- `REQUEST CHANGES` when **Must fix** is non-empty. +- `APPROVE WITH CHANGES` when only **Should fix** is non-empty. +- `APPROVE` when only **Nits** or no findings remain. + +After the verdict, append the [required `Used skills` section](../../../AGENTS.md#reporting). diff --git a/.agents/skills/reviewer/agents/openai.yaml b/.agents/skills/reviewer/agents/openai.yaml new file mode 100644 index 0000000..ec28e0a --- /dev/null +++ b/.agents/skills/reviewer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Reviewer" + short_description: "Review changes without modifying the repository" + default_prompt: "Use $reviewer to review these changes and report actionable findings." diff --git a/.agents/skills/security-engineer/SKILL.md b/.agents/skills/security-engineer/SKILL.md new file mode 100644 index 0000000..19ff127 --- /dev/null +++ b/.agents/skills/security-engineer/SKILL.md @@ -0,0 +1,52 @@ +--- +name: security-engineer +description: >- + Apply repository-specific security engineering policy when changing or + reviewing release URLs or tags, SHA-256 trust, executable caches, offline + reuse, temporary files, archive extraction, redirects, GitHub tokens, path + containment, or symbolic-link and Windows-junction handling. +--- + +# Security engineering + +Protect the boundary between remote release data and an executable run by a +consumer build. Require evidence for security claims and preserve fail-closed +behavior when authenticity or filesystem containment cannot be established. + +## Start + +1. Read [`PROJECT.md`](../../../PROJECT.md) and the affected release, checksum, + installation, platform, and functional-test code. +2. Identify the attacker-controlled value, trusted value, filesystem boundary, + online/offline state, and final executable before proposing a change. +3. Reproduce a reported weakness with a concrete regression probe. Do not claim + exploitability when the probe is unavailable or an existing guard blocks it. +4. Verify the current flow against source and tests, then apply + [Trust boundaries](references/trust-boundaries.md) to download, cache, redirect, + archive, token, or path work. + +## Cross-domain work + +- Apply [`gradle-engineer`](../gradle-engineer/SKILL.md) to task properties, + local state, configuration cache, and TestKit wiring. +- Use [`test-engineer`](../test-engineer/SKILL.md) for + deterministic regression fixtures and assertions. +- Apply [`writer`](../writer/SKILL.md) when security behavior changes public + configuration, diagnostics, or release documentation. + +## Verify + +Run the smallest relevant unit or TestKit test first. Then run: + +1. `./gradlew :gradle-plugin:test` +2. `./gradlew :gradle-plugin:functionalTest` +3. `./gradlew check` + +Run filesystem probes on the operating system whose semantics they claim to +cover. Report an unrun Windows junction or Linux permission probe explicitly. + +## Report + +For implementation, state the trust invariant, local-write assumptions, regression probe, +and verification result. For review, report only reproducible vulnerabilities or concrete +defense-in-depth gaps; omit speculative exploitability and identify unrun probes. diff --git a/.agents/skills/security-engineer/agents/openai.yaml b/.agents/skills/security-engineer/agents/openai.yaml new file mode 100644 index 0000000..91869e8 --- /dev/null +++ b/.agents/skills/security-engineer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Security Engineer" + short_description: "Protect download, cache, and filesystem trust" + default_prompt: "Use $security-engineer to implement or assess this security-sensitive change." diff --git a/.agents/skills/security-engineer/references/trust-boundaries.md b/.agents/skills/security-engineer/references/trust-boundaries.md new file mode 100644 index 0000000..7585060 --- /dev/null +++ b/.agents/skills/security-engineer/references/trust-boundaries.md @@ -0,0 +1,209 @@ +# Trust boundaries + +Use this reference to reason about release installation and executable reuse. +Re-read current implementation and tests before assuming the flow is unchanged. + +## Contents + +- [Fresh installation or rebuild flow](#fresh-installation-or-rebuild-flow) +- [Trust inputs](#trust-inputs) +- [Release identity](#release-identity) +- [SHA-256 trust](#sha-256-trust) +- [Cache and offline reuse](#cache-and-offline-reuse) +- [Temporary files and promotion](#temporary-files-and-promotion) +- [Archive extraction](#archive-extraction) +- [Path containment](#path-containment) +- [Tokens and redirects](#tokens-and-redirects) +- [Regression probes](#regression-probes) +- [Review standard](#review-standard) + +## Fresh installation or rebuild flow + +When installing or rebuilding from an asset, preserve this order: + +1. Validate the exact release tag and select the platform asset. +2. Normalize the release base URL and derive source identity from its URL, exact tag, and asset. +3. Resolve a trusted SHA-256 from explicit configuration or supported GitHub release metadata. +4. Download into an unpredictable task-temporary file, or stage a retained + cached asset without following links. +5. Hash staged bytes and compare them with the trusted digest. +6. Extract or copy into a separate staged executable. +7. Mark the staged file executable and calculate its digest. +8. Move it into a checked installation path. +9. Write asset and executable digests plus source identity through checked temporary files. + +Do not execute or expose an artifact as valid, and do not persist its trust metadata, +until the corresponding verification succeeds. + +For direct reuse, require the stored source identity to match current release inputs and require +a valid stored asset digest. If SHA-256 is configured, it must match that digest. Rehash the +installed executable and compare it with its stored digest. This path requires no network. +It relies on markers from a prior verified installation and detects executable changes only +while those markers remain intact. It does not resist replacement of both executable and markers. + +## Trust inputs + +Treat these values as untrusted until validated: + +- user-configured release tag; +- custom release base URL; +- operating-system and architecture strings when task inputs are overridden; +- remote response status, headers, metadata JSON, and asset bytes; +- archive entry names and content; +- retained cache files and marker files; +- public task output and local-state paths when consumers reconfigure them; +- every existing filesystem component below the installation root. + +Treat these values as trust anchors only within their intended scope: + +- an explicitly configured normalized SHA-256; +- the digest for the named asset returned by the supported GitHub Releases API. + +Stored source-identity and digest markers record prior verification; they are not independent +trust anchors against an actor who can replace both the executable and its markers. + +## Release identity + +- Keep release tags exact and case-sensitive. +- Reject empty, rolling `latest`, slash-containing, traversal-shaped, and + unsupported-character tags before URL or path construction. +- Use a filesystem-portable hash of the tag for cache directories; do not use + the raw tag as a directory name. +- Bind cache state to release base URL, exact tag, and asset name. +- Invalidate reuse when any identity component changes, regardless of an existing executable. +- Preserve explicit checksum configuration as authoritative for custom mirrors. + +## SHA-256 trust + +- Normalize accepted SHA-256 text to exactly 64 lowercase hexadecimal digits. +- Prefer an explicitly configured digest when present. +- Resolve automatic digests only for the supported HTTPS `github.com` release + URL shape and the exact named asset from `api.github.com`. +- Require explicit SHA-256 configuration for unsupported custom release sources. +- Hash staged bytes with no-follow file access before extraction. +- Hash the prepared executable separately; asset and executable digests guard different reuse paths. +- Report expected and observed mismatch values only when they contain no secrets. + +## Cache and offline reuse + +- Treat cached assets and integrity markers as mutable local state. +- Use source-identity and executable-digest markers to detect stale or altered files. +- Do not claim that direct reuse resists an actor who can replace both + the executable and its markers. +- Treat write access to the installation directory as part of the local trust boundary. +- Reauthenticate a cached asset before rebuilding a missing or modified executable. +- In offline mode, reuse an executable only when current state matches prior local markers. +- Rebuild from cache offline only when a trusted configured digest authenticates the asset. +- Fail closed when neither permitted offline path verifies its required state. +- Never treat a file in the build directory as trusted merely because Gradle is offline. + +Require a configured trust anchor before restoring an executable from a retained asset offline. +In schematic Kotlin, keep staging and use of the verified bytes under one helper: + +```kotlin +// Correct: missing trust data stops recovery before cached bytes are staged. +val trustedDigest = configuredSha256 + ?: throw GradleException("Configure `embedCode.sha256` for offline cache recovery.") + +restoreFromVerifiedStagedCopy(cachedAsset, trustedDigest) +``` + +The helper must copy without following links, verify the staged bytes, restore from that +same staged file, and delete it on every exit. + +Do not make authentication conditional while allowing recovery to continue: + +```kotlin +// Incorrect: a missing digest bypasses verification, and recovery rereads mutable bytes. +configuredSha256?.let { digest -> + verifySha256(cachedAsset, digest) +} +restoreExecutable(cachedAsset) +``` + +## Temporary files and promotion + +- Create unpredictable download and preparation filenames in the task temporary directory. +- Create metadata replacement files with unpredictable names beside checked destinations. +- Open retained sources with `NOFOLLOW_LINKS`. +- Verify staged data before moving it into the installation tree. +- Prefer atomic replacement and retain a safe fallback when atomic moves are unsupported. +- Recheck destination components before and after promotion. +- Delete temporary files in `finally`, including failure paths. +- Write integrity metadata only after preparing and promoting the executable successfully. + +## Archive extraction + +- Treat every ZIP entry name as untrusted. +- Stream the expected platform executable's bytes into a caller-selected staging file. +- Never resolve archive paths into the installation directory or extract arbitrary structure. +- Reject archives that lack the expected executable. +- Add explicit handling and probes before accepting archives with ambiguous + duplicate basenames, unexpected link metadata, or resource-exhaustion risk. +- Do not report zip-slip from a `../` entry alone when the implementation never + uses the entry path as an output path; prove an out-of-bound write. + +## Path containment + +- Normalize the installation root and every configured destination. +- Require destinations to be strict descendants of the installation root. +- Do not rely on lexical `startsWith` alone. +- Reject a symbolic-link or redirecting installation root. +- Walk every existing path component without following links. +- Reject symbolic links, Windows junctions, other redirecting filesystem + entries, and non-directory intermediate components. +- Create missing directories one component at a time and verify their real + paths remain below the real installation root. +- Check containment for executable, cached asset, digest, source identity, and metadata paths. +- Recheck after moves to reduce time-of-check/time-of-use exposure. +- Test Windows junction semantics on Windows; Unix symlink tests do not replace that probe. + +## Tokens and redirects + +- Read a GitHub token only from explicit plugin configuration. +- Mark it internal and keep it out of task fingerprints, cache identity, + persisted metadata, logs, exception messages, and generated configuration. +- Attach authorization only to the exact trusted GitHub API host for checksum metadata. +- Disable redirects for authenticated metadata requests. +- If redirect handling changes, validate every hop and never forward + authorization across a host boundary. +- Allow unauthenticated asset redirects only while final downloaded bytes remain + subject to trusted SHA-256 verification before use. +- Keep actionable HTTP status and source context without echoing credentials. + +## Regression probes + +- **Tag:** Try `../../escaped`, slash, encoded slash, empty, and `latest`. Reject + them before URL or path use. +- **Digest:** Serve bytes that differ from the trusted SHA-256; install no trusted output. +- **Identity:** Change the base URL, tag case, or platform asset with a cache + present. Reject stale reuse or reauthenticate it. +- **Executable:** Modify installed bytes after verification. Reject direct reuse. +- **Local markers:** Replace the executable and its markers together. Do not claim + resistance unless an independent trust anchor authenticates the resulting state. +- **Offline:** Remove metadata or modify the cache without a configured digest; fail closed. +- **Temporary files:** Pre-create likely names and force a mid-install failure. + Avoid collisions and clean staging files. +- **Archive:** Include traversal entries and the expected executable; write only + to the controlled staging file. +- **Output path:** Reconfigure executable or metadata outside the root; reject it before writing. +- **Symbolic link:** Link an intermediate directory, asset, marker, or + destination. Reject it without touching the target. +- **Junction:** Redirect the installation root or a child on Windows; leave the target untouched. +- **Metadata redirect:** Return a `3xx` response from the authenticated API + endpoint. Do not follow it or forward the token. +- **Asset redirect:** Redirect to bytes with a bad digest. Follow only as + allowed, then reject the bytes. +- **Token:** Run failures with verbose logging. Never expose the token text. + +Assert both the failure message and filesystem aftermath. A failing build alone +does not prove containment or cleanup. + +## Review standard + +- Reproduce against the current checkout and actual public configuration path. +- Identify the trust boundary, attacker capability, reachable operation, and concrete impact. +- Distinguish a vulnerability from hardening, misleading diagnostics, or an unreachable edge. +- Omit exploitability claims when the required host-specific or network probe is unavailable. +- Prefer a minimal regression fixture that fails before the fix and passes afterward. +- Preserve unrelated cache, release, and compatibility behavior when fixing confirmed issues. diff --git a/.agents/skills/test-engineer/SKILL.md b/.agents/skills/test-engineer/SKILL.md new file mode 100644 index 0000000..ea0f71c --- /dev/null +++ b/.agents/skills/test-engineer/SKILL.md @@ -0,0 +1,39 @@ +--- +name: test-engineer +description: >- + Use for adding, changing, diagnosing, or reviewing Kotlin tests in this + repository, including JUnit Jupiter unit specs, Gradle TestKit functional + specs, bug reproductions, and security or compatibility regressions. +--- + +# Test engineer + +## Workflow + +1. Read [the project context](../../../PROJECT.md), the production code in full, + and the closest specs in the same source set. +2. Reproduce a reported defect deterministically before changing production code when possible. +3. Classify the behavior as a focused unit contract or a consuming-build contract. Use + [the test patterns](references/test-patterns.md) for source-set selection, + deterministic fixtures, TestKit setup, and verification commands. +4. Add the smallest test that proves the requested behavior and its important + failure boundary. Keep unrelated coverage work out of scope. +5. After the focused spec or source-set task passes, run the full repository check. +6. Report the covered behavior, test location, and result of every verification command. + +## Test style + +- Use JUnit Jupiter for test structure and assertions. +- Do not add Kotest, a mocking framework, or another assertion dependency. +- Name a suite for subject `X` as `XSpec`; make Kotlin suites `internal`. +- Annotate every suite with `@DisplayName` describing the subject's expected behavior. +- Name test functions as backticked behavioral specifications. +- Prefer hand-written stubs, fixed inputs, and observable outputs to mocks and call-order checks. +- Keep setup close to the behavior; extract a helper only when it removes substantial noise. + +## Cross-domain work + +- Apply [gradle-engineer](../gradle-engineer/SKILL.md) when expected behavior depends + on Gradle lifecycle, task annotations, configuration cache, or compatibility. +- Apply [security-engineer](../security-engineer/SKILL.md) to identify adversarial + path, archive, download, checksum, cache, process, and credential cases. diff --git a/.agents/skills/test-engineer/agents/openai.yaml b/.agents/skills/test-engineer/agents/openai.yaml new file mode 100644 index 0000000..1f7378d --- /dev/null +++ b/.agents/skills/test-engineer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Test Engineer" + short_description: "Write focused JUnit and TestKit coverage" + default_prompt: "Use $test-engineer to add focused regression coverage for this change." diff --git a/.agents/skills/test-engineer/references/test-patterns.md b/.agents/skills/test-engineer/references/test-patterns.md new file mode 100644 index 0000000..335003c --- /dev/null +++ b/.agents/skills/test-engineer/references/test-patterns.md @@ -0,0 +1,134 @@ +# Test patterns + +Match the nearest existing spec before introducing a new helper or layout. + +## Contents + +- [Unit spec](#unit-spec) +- [Unit or functional](#unit-or-functional) +- [TestKit project](#testkit-project) +- [Fake releases and HTTP](#fake-releases-and-http) +- [Filesystem and process fixtures](#filesystem-and-process-fixtures) +- [Compatibility cases](#compatibility-cases) +- [Verification commands](#verification-commands) + +## Unit spec + +Use JUnit Jupiter structure and assertions: + +```kotlin +import org.gradle.api.InvalidUserDataException +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("Embed Code release-tag support should") +internal class EmbedCodeVersionSpec { + + @Test + fun `reject a missing exact release tag`() { + assertThrows(InvalidUserDataException::class.java) { + validateVersion(" ") + } + } +} +``` + +Use several assertions when they describe one behavior. Split a test when a +failure would leave the broken contract unclear. + +## Unit or functional + +| Behavior | Source set | +|--------------------------------------------------|-----------------------------| +| Parse, normalize, hash, validate, or map a value | `src/test/kotlin` | +| Select a platform name without executing Gradle | `src/test/kotlin` | +| Apply the plugin or configure its extension | `src/functionalTest/kotlin` | +| Register, wire, execute, or inspect a task | `src/functionalTest/kotlin` | +| Verify configuration-cache reuse | `src/functionalTest/kotlin` | +| Verify behavior across Gradle versions | `src/functionalTest/kotlin` | +| Launch the fake Embed Code executable | `src/functionalTest/kotlin` | + +Do not use TestKit when a direct unit call proves the contract. Do not replace +a consuming-build test with a unit test when Gradle wiring is the behavior. + +## TestKit project + +Create the consuming project under a JUnit `@TempDir`. Write only the settings, +build script, source, documentation, and fake assets required by the case. + +Create runners with the plugin-under-test classpath: + +```kotlin +private fun runner(vararg arguments: String): GradleRunner = + GradleRunner.create() + .withProjectDir(projectDirectory.toFile()) + .withArguments( + *arguments, + "--configuration-cache", + "--stacktrace", + ) + .withPluginClasspath() +``` + +Match the repository's established runner helper when it differs. Keep +configuration cache enabled by default so new behavior does not silently break +the plugin's compatibility claim. Disable it only in a test that explicitly +proves a non-configuration-cache path, and state why. + +Assert task outcomes and consumer-visible files or messages: + +```kotlin +val result = runner(":checkEmbedding").build() + +assertEquals(TaskOutcome.SUCCESS, result.task(":checkEmbedding")?.outcome) +assertEquals("check", Files.readString(projectDirectory.resolve("mode.txt")).trim()) +``` + +Use `buildAndFail()` only when failure is the expected contract. Assert the +actionable message rather than a complete stack trace. + +## Fake releases and HTTP + +- Build fake release assets inside the temporary project. +- Use fixed payloads and calculate expected SHA-256 values locally. +- Use `HttpServer` on `127.0.0.1` with port `0` when request behavior matters. +- Record request paths, headers, or counts in thread-safe test state. +- Return only the status and body needed by the test. +- Stop the server in `finally`, `@AfterEach`, or another guaranteed cleanup path. +- Use a local file URI instead of HTTP when transport behavior is irrelevant. + +Never call GitHub or another public endpoint from a test. A live service makes the result +depend on rate limits, credentials, network failures, mutable releases, and remote state. + +## Filesystem and process fixtures + +- Resolve every fixture below the temporary directory. +- Use fixed file contents and explicit UTF-8 where an API requires a charset. +- Create a minimal fake executable with fixed output that records its arguments and exit status. +- Test the expected path and important containment or replacement failures. +- Avoid sleeps. Wait on an observable process, file, or request condition with + a bounded timeout when synchronization is necessary. +- Apply JUnit OS conditions only to behavior that truly depends on executable + permissions or platform process semantics. + +## Compatibility cases + +Use `withGradleVersion(...)` only within the plugin's stated compatibility range. +Keep the ordinary runner on the wrapper version. Cover another Gradle version when +the changed API or behavior varies between releases. + +Keep Java 17 consumer compatibility distinct from the JDK used to build the project. +A test passing on the build JDK alone does not prove Java 17 consumer compatibility. + +## Verification commands + +Prefer this order: + +```bash +./gradlew :gradle-plugin:test --tests io.spine.embedcode.gradle.EmbedCodeVersionSpec +./gradlew :gradle-plugin:functionalTest --tests io.spine.embedcode.gradle.EmbedCodePluginSpec +./gradlew check +``` + +Run the applicable focused command first, then run the full check before claiming completion. diff --git a/.agents/skills/writer/SKILL.md b/.agents/skills/writer/SKILL.md new file mode 100644 index 0000000..0485284 --- /dev/null +++ b/.agents/skills/writer/SKILL.md @@ -0,0 +1,69 @@ +--- +name: writer +description: >- + Use when creating, reviewing, or revising README.md, PROJECT.md, AGENTS.md, + skill routes, Markdown, Kotlin KDoc, Gradle task descriptions, public errors, + or explanatory comments; write verified project documentation in the Spine style. +--- + +# Documentation writer + +Read the [project context](../../../PROJECT.md) and the +[writing style](../../guidelines/writing-style.md) before reviewing or editing. + +## Establish the audience and owner + +- Identify the reader, the problem to solve, and the outcome that makes the text useful. +- Prefer updating the document that already owns the topic. +- Keep `README.md` focused on the user problem, setup, Kotlin DSL configuration, + tasks, and short contributor commands. +- Keep `PROJECT.md` focused on stable architecture, compatibility, context, and source pointers. +- Keep `AGENTS.md`, skill descriptions, and other agent routes focused on dispatch and + repository-wide policy. Link to the owning skill instead of duplicating its guidance. +- Keep KDoc with its declaration, task descriptions with their task registrations, and + public error messages at the boundary that reports the failure. + +## Verify before writing + +1. Read the complete target file and the related source, tests, build logic, and routes. +2. Treat current repository behavior as authoritative. +3. Verify every path, command, version, default, and platform, along with every task + name, property, and failure claim, against current evidence. +4. Apply the specialist skill that owns each technical claim being changed. +5. Run the narrowest practical command when prose claims executable behavior or output. +6. Mark any remaining unverified claim explicitly; do not turn an assumption into fact. + +## Write user-facing documentation + +- Explain the user's problem before introducing implementation details. +- Use Kotlin DSL in Gradle examples. Do not add Groovy DSL examples unless requested. +- Keep examples small, copyable, and consistent with the supported Gradle and Java ranges. +- Describe exact release-tag, checksum, cache, token, and offline behavior without + weakening or overstating the trust guarantees. +- Prefer direct instructions and observable outcomes over promotional language. +- Keep one source of truth for detailed guidance and link to it from shorter entry points. + +## Write Kotlin documentation + +Use the [KDoc policy](../kotlin-engineer/references/kotlin-policy.md#kdoc) as the source of +truth for Kotlin documentation, and apply +[kotlin-engineer](../kotlin-engineer/SKILL.md) to affected Kotlin source. + +## Write operational text + +- Make task descriptions concise and action-oriented. +- Make public errors state what failed, identify the relevant user-facing value or + property, and provide a recovery path when one exists. +- Keep credentials and other sensitive values out of errors, examples, and logs. +- Use inline comments to explain why a constraint exists. Do not narrate the next statement. +- Preserve exact machine text, command output, code, generated content, and license text. + +## Validate the edit + +- Check heading hierarchy, links, paths, terminology, code fences, table alignment, and wrapping. +- Check that changed Markdown and KDoc follow the shared writing style. +- Leave no orphans: reflow or rewrite a paragraph, list item, table cell, or KDoc block + whose final source line contains one word or an unusually short fragment. +- Run `git diff --check`. +- Run focused Gradle verification when a changed example or comment depends on behavior. +- Report the evidence used, commands run, and any claim that remains unverified. diff --git a/.agents/skills/writer/agents/openai.yaml b/.agents/skills/writer/agents/openai.yaml new file mode 100644 index 0000000..3703617 --- /dev/null +++ b/.agents/skills/writer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Writer" + short_description: "Write clear project docs and Kotlin KDoc" + default_prompt: "Use $writer to write or update this repository documentation." diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000..2b7a412 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index e238014..808c52d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ gradlew text eol=lf +*.md text eol=lf *.bat text eol=crlf *.jar binary diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..484f6de --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,11 @@ +# GitHub Copilot instructions + +This is the Copilot-specific route for `embed-code-gradle-plugin`. Keep detailed policy in its +owning repository document instead of duplicating it here. + +## Read first + +- Read [`AGENTS.md`](../AGENTS.md) for repository-wide operating policy. +- Read [`PROJECT.md`](../PROJECT.md) for the project map, runtime flow, compatibility + constraints, test strategy, and trust boundaries. +- Load every matching [repository skill](../.agents/skills/) through the `AGENTS.md` routing map. diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 87eb376..f09fef4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -11,6 +11,25 @@ concurrency: cancel-in-progress: true jobs: + agents-lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout Repository + uses: actions/checkout@v7 + + - name: Set Up Python 3.14 + uses: actions/setup-python@v7 + with: + python-version: "3.14" + + - name: Test Agent Configuration Validator + run: python3 -m unittest discover -s scripts/tests -t . -p "test_*.py" + + - name: Validate Agent Configuration + run: python3 scripts/check_agent_config.py + build: strategy: fail-fast: false diff --git a/.gitignore b/.gitignore index f29958d..843673d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ .idea/ *.iml **/build/ +__pycache__/ +*.py[cod] local.properties diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..90d86c2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,88 @@ +# Agent instructions + +These instructions apply to the whole repository. Keep this file focused on repository-wide +operating policy; keep task-specific procedures in the skills under `.agents/skills/`. + +## Orientation + +- Read [PROJECT.md](PROJECT.md) before making a non-trivial change. +- Use every discovered skill whose description matches the task. +- Treat each skill's frontmatter description as the routing source of truth. +- Read the affected implementation, nearby tests, and relevant build configuration before editing. + +## Skill routing + +Use all matching skills. The links are an index; each frontmatter description defines its scope: + +- [`kotlin-engineer`](.agents/skills/kotlin-engineer/SKILL.md) +- [`gradle-engineer`](.agents/skills/gradle-engineer/SKILL.md) +- [`test-engineer`](.agents/skills/test-engineer/SKILL.md) +- [`writer`](.agents/skills/writer/SKILL.md) +- [`security-engineer`](.agents/skills/security-engineer/SKILL.md) +- [`reviewer`](.agents/skills/reviewer/SKILL.md) + +Combine skills only when their scopes overlap: + +- Kotlin in plugin or build logic normally requires both `kotlin-engineer` and `gradle-engineer`. +- Any behavioral change requires `test-engineer` with the owning implementation skill. +- Security work requires `security-engineer` and `test-engineer`, plus the skill + responsible for the affected implementation. +- Documentation and public API text require `writer` plus the skill owning the contract. +- Review uses `reviewer` plus matching specialist skills; the reviewer remains read-only. + +## Operating policy + +- Preserve unrelated local changes and untracked files. +- Keep changes narrowly scoped. Do not combine opportunistic cleanup with the requested work. +- Ask a question only when an unresolved decision would materially change the result. +- Do not commit, push, tag, merge, rebase, cherry-pick, reply to review threads, or resolve + threads unless the user's current request explicitly asks for that action. +- Do not publish plugins, change release versions, or use credentials unless explicitly asked. +- Do not add telemetry, analytics, hidden network access, or automatic dependency updates. + +## Project defaults + +- Use Kotlin and Gradle Kotlin DSL. Do not add Groovy build scripts. +- Use the Gradle wrapper and preserve the compatibility targets declared by the build. +- Preserve lazy Gradle configuration, its supplied Kotlin runtime, and configuration-cache support. +- Preserve JUnit Jupiter and Gradle TestKit. Do not introduce Kotest or a mocking framework + without an explicit dependency and style decision. +- Use local fixtures or loopback HTTP servers so tests remain independent of live services. + +## Documentation + +- Apply the [writer skill](.agents/skills/writer/SKILL.md) and + [writing style](.agents/guidelines/writing-style.md) to documentation and user-facing text. +- Follow the + [Kotlin KDoc policy](.agents/skills/kotlin-engineer/references/kotlin-policy.md#kdoc) + for declarations and public APIs. +- Verify documentation claims against current code, tests, build files, or command output. + +## Verification + +- Agent configuration validation requires Python 3.10 or newer. +- After changing agent documentation, routes, or skills, run + `python3 scripts/check_agent_config.py`. +- After changing the validator, run + `python3 -m unittest discover -s scripts/tests -t . -p 'test_*.py'`. +- Start with the narrowest Gradle task or test that proves the change. +- Run `./gradlew check` before handing off a completed code or test change unless the task is + documentation-only or a narrower check is explicitly sufficient. +- Run `git diff --check` after editing. +- Report every command actually run. Separate failures caused by the change from failures + caused by the environment or unrelated work. + +## Reporting + +- Include a `## Used skills` section in every final response. +- List all materially applied skills, including repository and platform-provided skills. +- Do not list skills that were merely discovered, inspected, or considered. +- Write `- None.` when no skill was used. +- Place the section last unless a required machine-readable trailer must remain last. + +## Agent entry points + +- Codex reads this `AGENTS.md` and discovers repository skills under `.agents/skills/`. +- Claude reads [CLAUDE.md](CLAUDE.md), which routes back to this file. +- [GitHub Copilot instructions](.github/copilot-instructions.md) route Copilot back + to this file and the matching skills. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b705f7c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +@AGENTS.md + +# Claude-specific routing + +Load every matching skill directly from `.agents/skills/`. diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..85db818 --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,110 @@ +# Project + +This document gives agents and contributors the project map, runtime flow, compatibility, +test strategy, and trust boundaries. Read [AGENTS.md](AGENTS.md) for operating policy. + +## Overview + +`embed-code-gradle-plugin` publishes the `io.spine.embed-code` Gradle plugin. The plugin +downloads an authenticated Embed Code release for the current platform and exposes Gradle +tasks that either check documentation snippets or update them. + +Consumers configure the plugin through Gradle Kotlin DSL. They do not maintain a separate +Embed Code configuration file and do not need to install the executable or Kotlin. + +## Project map + +- `build.gradle.kts`: root group and version wiring. +- `settings.gradle.kts`: plugin and dependency repositories and the `gradle-plugin` module. +- `version.gradle.kts`: the plugin version and default Embed Code application version. +- `gradle.properties`: Gradle runtime, parallelism, and configuration-cache settings, + plus Kotlin style and dependency defaults. +- `buildSrc/`: build settings, dependency coordinates, and the shared `jvm-module` convention. +- `gradle-plugin/build.gradle.kts`: plugin declaration, generated version source, functional + test source set, publication metadata, and Plugin Portal configuration. +- `gradle-plugin/src/main/kotlin/`: extension, plugin, task, platform, version, JSON, checksum, + download, installation, and execution logic. +- `gradle-plugin/src/main/templates/`: generated default-version source template. +- `gradle-plugin/src/test/kotlin/`: focused unit specifications. +- `gradle-plugin/src/functionalTest/kotlin/`: TestKit consumer-build specifications. +- `scripts/check_agent_config.py` and `scripts/tests/`: deterministic validation and tests. +- `.github/workflows/check.yml`: agent configuration and Ubuntu and Windows build verification. +- `.agents/skills/`: repository engineering, test, writing, review, and security workflows. + +## Runtime flow + +1. `EmbedCodePlugin` creates the `embedCode` extension and lazily registers + installation, check, and update tasks. +2. `InstallEmbedCodeTask` selects the platform asset, establishes its expected SHA-256 digest, + and installs or restores a verified executable under `build/embed-code/`. +3. `EmbedCodeTask` validates the configured source and documentation roots. It passes direct + source settings as command-line arguments or writes temporary JSON for named sources, + then launches the executable in `check` or `embed` mode. +4. `checkEmbedding` reports stale documentation, while `embedCode` updates selected files. + +When behavior changes, trace the complete extension → plugin → task → arguments or generated +configuration → executable flow instead of patching only the first visible symptom. + +## Compatibility and dependency policy + +- The Gradle wrapper version and checksum are defined in `gradle/wrapper/gradle-wrapper.properties`. +- Document the minimum Gradle version in `README.md` and cover it with compatibility tests. +- `BuildSettings` independently defines the build JDK and emitted bytecode versions. +- `jvm-module.gradle.kts` owns Kotlin language/API compatibility, explicit API mode, Java + release settings, and the test launcher. +- `gradle-plugin/build.gradle.kts` uses the Kotlin runtime supplied by Gradle; + it does not publish `kotlin-stdlib`. +- Dependency versions live in Kotlin objects under `buildSrc`; do not introduce a version + catalog as an unrelated refactor. + +Read these source files before changing a version or compatibility claim. Avoid copying +version numbers into agent guidance where a durable source path is sufficient. + +## Test strategy + +- Use unit specifications for pure parsing, validation, mapping, checksums, platforms, and versions. +- Use TestKit functional specifications for plugin application, extension and task wiring, + generated configuration, logging, configuration-cache reuse, process execution, + compatibility, filesystem and cache behavior, and consumer-visible failures. +- Keep tests offline with deterministic fixtures, temporary directories, and loopback HTTP servers. +- Use `./gradlew test` for unit tests, `./gradlew functionalTest` for TestKit tests, and + `./gradlew check` for both plus plugin validation. +- CI validates agent configuration on Ubuntu and independently runs the build and publishes + the plugin to Maven Local on Ubuntu and Windows. Preserve cross-platform paths, permissions, + line endings, and process behavior. + +## Trust boundaries + +Treat executable acquisition and reuse as security-sensitive: + +- Authenticate release assets before extraction or execution. +- Bind cached metadata to the release source, exact tag, platform asset, and digest. +- Rehash installed executables before reuse. Local markers detect changes only while intact. +- Fail closed offline unless one path succeeds: reuse state that matches prior markers, + or rebuild from a cached asset verified by a configured digest. +- Keep all derived installation paths inside the fixed installation root. +- Reject symbolic-link or junction paths that can redirect writes outside that root. +- Keep tokens explicit and secret; exclude them from logs, task inputs, cache keys, and metadata. +- Use unpredictable temporary files and safe replacement when installing assets. + +Apply `gradle-engineer` and `security-engineer` to work spanning Gradle and security boundaries. + +## Documentation ownership + +- `README.md`: user-facing purpose, requirements, Kotlin DSL, execution, and development commands. +- `PROJECT.md`: project map, runtime flow, compatibility, test strategy, and trust boundaries. +- `AGENTS.md`: repository-wide agent operating policy and routing. +- `.agents/guidelines/writing-style.md`: shared Spine writing and typography rules. +- `.agents/skills/*/SKILL.md`: task-specific workflows and project constraints. + +Keep user instructions in `README.md`, contributor and architecture context here, and agent +procedures in the matching skill. + +## Agent routes + +- Codex: `AGENTS.md` → `PROJECT.md` → matching `.agents/skills/` entries. +- Claude: `CLAUDE.md` → `AGENTS.md` → `PROJECT.md` → matching `.agents/skills/` entries. +- GitHub Copilot: `.github/copilot-instructions.md` → `AGENTS.md` → `PROJECT.md` + → matching repository skills under `.agents/skills/`. + +Keep routes thin. Update the owning document instead of duplicating policy across clients. diff --git a/scripts/check_agent_config.py b/scripts/check_agent_config.py new file mode 100644 index 0000000..0a51f06 --- /dev/null +++ b/scripts/check_agent_config.py @@ -0,0 +1,681 @@ +#!/usr/bin/env python3 +"""Validate agent skills, routes, links, anchors, and Markdown formatting.""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path +from urllib.parse import unquote, urlsplit + + +MAX_MARKDOWN_LINE_LENGTH = 100 +SKILLS_DIRECTORY = Path(".agents/skills") +AGENT_DOCUMENTS = ( + Path("AGENTS.md"), + Path("PROJECT.md"), + Path("CLAUDE.md"), + Path(".github/copilot-instructions.md"), +) +SKILL_ROUTE_PATTERN = re.compile( + r"^\s*-\s+\[`(?P