Skip to content

Commit 2352850

Browse files
authored
Add AGENTS.md & agentskills for directives & integration tests (VirtusLab#4178)
1 parent 7df6c78 commit 2352850

4 files changed

Lines changed: 210 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# AGENTS.md — Guidance for AI agents contributing to Scala CLI
2+
3+
Short reference for AI agents. For task-specific guidance (directives, integration tests), load skills from *
4+
*[agentskills/](agentskills/)** when relevant.
5+
6+
> **LLM Policy**: All AI-assisted contributions must comply with the
7+
> [LLM usage policy](https://github.com/scala/scala3/blob/HEAD/LLM_POLICY.md). The contributor (human) is responsible
8+
> for every line. State LLM usage in the PR description. See [LLM_POLICY.md](LLM_POLICY.md).
9+
10+
## Human-facing docs
11+
12+
- **[DEV.md](DEV.md)** — Setup, run from source, tests, launchers, GraalVM.
13+
- **[CONTRIBUTING.md](CONTRIBUTING.md)** — PR workflow, formatting, reference doc generation.
14+
- **[INTERNALS.md](INTERNALS.md)** — Modules, `Inputs → Sources → Build`, preprocessing.
15+
16+
## Build system
17+
18+
The project uses [Mill](https://mill-build.org/). Mill launchers ship with the repo (`./mill`). JVM 17 required.
19+
Cross-compilation: default `Scala.defaultInternal`; `[]` = default version, `[_]` = all.
20+
21+
### Key build files
22+
23+
| File | Purpose |
24+
|---------------------------------|------------------------------------------------------------------------------------------|
25+
| `build.mill` | Root build definition: all module declarations, CI helper tasks, integration test wiring |
26+
| `project/deps/package.mill` | Dependency versions and definitions (`Deps`, `Scala`, `Java` objects) |
27+
| `project/settings/package.mill` | Shared traits, utils (`HasTests`, `CliLaunchers`, `FormatNativeImageConf`, etc.) |
28+
| `project/publish/package.mill` | Publishing settings |
29+
| `project/website/package.mill` | Website-related build tasks |
30+
31+
### Essential commands
32+
33+
```bash
34+
./mill -i clean # Clean Mill context
35+
./mill -i scala …args… # Run Scala CLI from source
36+
./mill -i __.compile # Compile everything
37+
./mill -i unitTests # All unit tests
38+
./mill -i 'build-module[].test' # Unit tests for a specific module
39+
./mill -i 'build-module[].test' 'scala.build.tests.BuildTestsScalac.*' # Filter by suite
40+
./mill -i 'build-module[].test' 'scala.build.tests.BuildTests.simple' # Single test by name
41+
./mill -i integration.test.jvm # Integration tests (JVM launcher)
42+
./mill -i integration.test.jvm 'scala.cli.integration.RunTestsDefault.*' # Integration: filter by suite
43+
./mill -i 'generate-reference-doc[]'.run # Regenerate reference docs
44+
./mill -i __.fix # Fix import ordering (scalafix)
45+
scala-cli fmt . # Format all code (scalafmt)
46+
```
47+
48+
## Project modules
49+
50+
Modules live under `modules/`. The dependency graph flows roughly as:
51+
52+
```
53+
specification-level → config → core → options → directives → build-module → cli
54+
```
55+
56+
### Module overview
57+
58+
The list below may not be exhaustive — check `modules/` and `build.mill` for the current set.
59+
60+
| Module | Purpose |
61+
|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------|
62+
| `specification-level` | Defines `SpecificationLevel` (MUST / SHOULD / IMPLEMENTATION / RESTRICTED / EXPERIMENTAL) for SIP-46 compliance. |
63+
| `config` | Scala CLI configuration keys and persistence. |
64+
| `build-macros` | Compile-time macros (e.g. `EitherCps`). |
65+
| `core` | Core types: `Inputs`, `Sources`, build constants, Bloop integration, JVM/JS/Native tooling. |
66+
| `options` | `BuildOptions`, `SharedOptions`, and all option types. |
67+
| `directives` | Using directive handlers — the bridge between `//> using` directives and `BuildOptions`. |
68+
| `build-module` (aliased from `build` in mill) | The main build pipeline: preprocessing, compilation, post-processing. Most business logic lives here. |
69+
| `cli` | Command definitions, argument parsing (CaseApp), the `ScalaCli` entry point. Packaged as the native image. |
70+
| `runner` | Lightweight app that runs a main class and pretty-prints exceptions. Fetched at runtime. |
71+
| `test-runner` | Discovers and runs test frameworks/suites. Fetched at runtime. |
72+
| `tasty-lib` | Edits file names in `.tasty` files for source mapping. |
73+
| `scala-cli-bsp` | BSP protocol types. |
74+
| `integration` | Integration tests (see dedicated section below). |
75+
| `docs-tests` | Tests that validate documentation (`Sclicheck`). |
76+
| `generate-reference-doc` | Generates reference documentation from CLI option/directive metadata. |
77+
78+
## Specification levels
79+
80+
Every command, CLI option, and using directive has a `SpecificationLevel`. This is central to how features are exposed.
81+
82+
| Level | In the Scala Runner spec? | Available without `--power`? | Stability |
83+
|------------------|---------------------------|------------------------------|---------------------------------|
84+
| `MUST` | Yes | Yes | Stable |
85+
| `SHOULD` | Yes | Yes | Stable |
86+
| `IMPLEMENTATION` | No | Yes | Stable |
87+
| `RESTRICTED` | No | No (requires `--power`) | Stable |
88+
| `EXPERIMENTAL` | No | No (requires `--power`) | Unstable — may change/disappear |
89+
90+
**New features contributed by agents should generally be marked `EXPERIMENTAL`** unless the maintainers explicitly
91+
request otherwise. This applies to new sub-commands, options, and directives alike.
92+
93+
The specification level is set via:
94+
95+
- **Directives**: `@DirectiveLevel(SpecificationLevel.EXPERIMENTAL)` annotation on the directive case class.
96+
- **CLI options**: `@Tag(tags.experimental)` annotation on option fields.
97+
- **Commands**: Override `scalaSpecificationLevel` in the command class.
98+
99+
## Using directives
100+
101+
Using directives are in-source configuration comments:
102+
103+
```scala
104+
//> using scala 3
105+
//> using dep com.lihaoyi::os-lib:0.11.4
106+
//> using test.dep org.scalameta::munit::1.1.1
107+
```
108+
109+
Directives are parsed by `using_directives`, then `ExtractedDirectives``DirectivesPreprocessor``BuildOptions`/
110+
`BuildRequirements`. **CLI options override directive values.** To add a new directive,
111+
see [agentskills/adding-directives/](agentskills/adding-directives/SKILL.md).
112+
113+
## Testing
114+
115+
> **Every contribution that changes logic must include automated tests.** A PR without tests for
116+
> new or changed behavior will not be accepted. If testing is truly infeasible, explain why in the
117+
> PR description — but this should be exceptional.
118+
119+
> **Unit tests are always preferred over integration tests.** Unit tests are faster, more reliable,
120+
> easier to debug, and cheaper to run on CI. Only add integration tests when the behavior cannot be
121+
> adequately verified at the unit level (e.g. end-to-end CLI invocation, launcher-specific behavior,
122+
> cross-process interactions).
123+
124+
> **Always re-run and verify tests locally before submitting.** After any logic change, run the
125+
> relevant test suites on your machine and confirm they pass. Do not rely on CI to catch failures —
126+
> CI resources are shared, and broken PRs waste maintainer time.
127+
128+
**Unit tests**: munit, in each module’s `test` submodule. Run commands above; add tests in `modules/build/.../tests/` or
129+
`modules/cli/src/test/scala/`. Prefer unit over integration.
130+
131+
**Integration tests**: `modules/integration/`; they run the CLI as a subprocess.
132+
See [agentskills/integration-tests/](agentskills/integration-tests/SKILL.md) for structure and how to add tests.
133+
134+
## Pre-PR checklist
135+
136+
1. Code compiles: `./mill -i __.compile`
137+
2. Tests added and passing locally (unit tests first, integration if needed)
138+
3. Code formatted: `scala-cli fmt .`
139+
4. Imports ordered: `./mill -i __.fix`
140+
5. Reference docs regenerated (if options/directives changed): `./mill -i 'generate-reference-doc[]'.run`
141+
6. PR template filled, LLM usage stated
142+
143+
## Code style
144+
145+
Code style is enforced.
146+
147+
**Scala 3**: Prefer `if … then … else`, `for … do`/`yield`, `enum`, `extension`, `given`/`using`, braceless blocks,
148+
top-level defs. Use union/intersection types when they simplify signatures. Always favor Scala 3 idiomatic syntax.
149+
150+
**Functional**: Prefer `val`, immutable collections, `case class`.copy(). Prefer expressions over statements; prefer
151+
`map`/`flatMap`/`fold`/`for`-comprehensions over loops. Use `@tailrec` for tail recursion. Avoid `null`; use `Option`/
152+
`Either`/`EitherCps` (build-macros). Keep functions small; extract helpers.
153+
154+
**No duplication**: Extract repeated logic into shared traits or utils (`*Options` traits, companion helpers,
155+
`CommandHelpers`, `TestUtil`). Check for existing abstractions before copying.
156+
157+
**Logging**: Use the project `Logger` only — never `System.err` or `System.out`. Logger respects verbosity (`-v`, `-q`).
158+
Use `logger.message(msg)` (default), `logger.log(msg)` (verbose), `logger.debug(msg)` (debug), `logger.error(msg)` (
159+
always). In commands: `options.shared.logging.logger`; in build code it is passed in; in tests use `TestLogger`.
160+
161+
**Mutability**: OK in hot paths or when a Java API requires it; keep scope minimal.
162+
163+
## Further reference
164+
165+
[DEV.md](DEV.md), [CONTRIBUTING.md](CONTRIBUTING.md), [INTERNALS.md](INTERNALS.md).

agentskills/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Agent skills (Scala CLI)
2+
3+
This directory holds **agent skills** — task-specific guidance loaded on demand by AI coding agents. The layout is tool-agnostic; Cursor, Claude Code, Codex, and other tools that support a standard skill directory can use this (e.g. by configuring or symlinking to `.agents/skills/` if required).
4+
5+
Each subdirectory contains a `SKILL.md` with frontmatter and instructions. See [agentskills/agentskills](https://github.com/agentskills/agentskills) for the open standard.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
name: scala-cli-adding-directives
3+
description: Add or change using directives in Scala CLI. Use when adding a new //> using directive, registering a directive handler, or editing directive preprocessing.
4+
---
5+
6+
# Adding a new directive (Scala CLI)
7+
8+
1. **Create a case class** in `modules/directives/src/main/scala/scala/build/preprocessing/directives/` extending one of:
9+
- `HasBuildOptions` — produces `BuildOptions` directly
10+
- `HasBuildOptionsWithRequirements` — produces `BuildOptions` with scoped requirements (e.g. `test.dep`)
11+
- `HasBuildRequirements` — produces `BuildRequirements` (for `//> require`)
12+
13+
2. **Annotate**: `@DirectiveLevel(SpecificationLevel.EXPERIMENTAL)`, `@DirectiveDescription("…")`, `@DirectiveUsage("…")`, `@DirectiveExamples("…")`, `@DirectiveName("key")` on fields.
14+
15+
3. **Companion**: `val handler: DirectiveHandler[YourDirective] = DirectiveHandler.derive`
16+
17+
4. **Register** in `modules/build/.../DirectivesPreprocessingUtils.scala` in the right list: `usingDirectiveHandlers`, `usingDirectiveWithReqsHandlers`, or `requireDirectiveHandlers`.
18+
19+
5. **Regenerate reference docs**: `./mill -i 'generate-reference-doc[]'.run`
20+
21+
CLI options always override directive values when both set the same thing.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
name: scala-cli-integration-tests
3+
description: Add or run Scala CLI integration tests. Use when adding integration tests, debugging RunTests/CompileTests/etc., or working in modules/integration.
4+
---
5+
6+
# Integration tests (Scala CLI)
7+
8+
**Location**: `modules/integration/`. Tests invoke the CLI as an external process.
9+
10+
**Run**: `./mill -i integration.test.jvm` (all). Filter: `./mill -i integration.test.jvm 'scala.cli.integration.RunTestsDefault.*'` or by test name. Native: `./mill -i integration.test.native`.
11+
12+
**Structure**: `*TestDefinitions.scala` (abstract, holds test logic) → `*TestsDefault`, `*Tests213`, etc. (concrete, Scala version trait). Traits: `TestDefault`, `Test212`, `Test213`, `Test3Lts`, `Test3NextRc`.
13+
14+
**Adding a test**:
15+
1. Open the right `*TestDefinitions` (e.g. `RunTestDefinitions` for `run`).
16+
2. Add `test("description") { … }` using `TestInputs(os.rel / "Main.scala" -> "…").fromRoot { root => … }` and `os.proc(TestUtil.cli, "run", …).call(cwd = root)`.
17+
3. Assert on stdout/stderr.
18+
19+
**Helpers**: `TestInputs(...).fromRoot`, `TestUtil.cli`. Test groups (CI): `SCALA_CLI_IT_GROUP=1..5`; see `modules/integration/` for group mapping.

0 commit comments

Comments
 (0)