Imported from mabuzax/vending-machine (
vending-machine-api/AGENTS.md). Install upstream withnpx skills add mabuzax/vending-machine --skill vending-machine-api. Copyright stays with the author.
AGENTS.md — vending-machine-api
Guidance for any agent working in vending-machine-api, the API folder of the vending-machine
repository — Kiro discovers this file at the root of the workspace folder and always includes it.
This project is built with Kiro's spec-driven workflow. The rules an agent must obey
live in .kiro/steering/, exactly where Kiro would put them. This file is the loader: read the
steering files it links before doing anything in this folder. They are not background
reading — they are the constraints.
What this folder is
The Spring Boot back end of a vending machine simulation, built as a technical assessment
(docs/business/technical-assignment-1.md). The Angular front end is the
../vending-machine-ui folder of the same repository. The only coupling between them is the REST contract: /v3/api-docs on the running API, with a copy committed at
docs/api/openapi.yaml.
The one rule that overrides everything
.kiro/steering/scope.md is the only authority on what gets built.
Before proposing, designing or writing anything — a requirement, a design element, a task, a class,
a method, a field — name the REQ-n.m it satisfies and the scope id (C-, E-, D-, B-) behind it.
If you cannot name one, you do not build it: say OUT OF SCOPE — not in scope.md, note the idea in
one line for the README's Not built list, and stop. Do not ask permission to add it;
the default is no.
This covers methods and fields, not only features. The realistic creep here is a findByCategory, a
page/size parameter, a description on Product, a ChangeStrategyFactory "for future
strategies", a @Cacheable, a GET /api/v1/state because the state model is interesting. Each is one
line and each is defensible alone; together they are how a POC becomes unreviewable.
Scope changes only by a commit to scope.md, never by a chat message.
Simplicity comes first
The brief scores "simplicity and understandability of the implementation". Write every plan, every class and every comment so that a junior developer can read it without help. If you are choosing between clever and plain, choose plain.
In code
- Use plain
forloops andifstatements. Do not use streams, method references (::),Optionalchains,UnaryOperator,Functionor other functional interfaces to save lines. - Repeating a few simple lines is better than one clever helper that hides what happens.
- Give variables full names:
denomination, notd. - Do not write code that nothing calls. A method needs a caller now, or a named task that will call it.
- Do not add a class, interface, factory or layer "for later". Add it when a requirement needs it.
- Do not add something the framework or compiler already does. Use Spring Boot configuration before writing a class.
- Use the simplest Spring feature that works: Spring Data repositories,
@ConfigurationProperties,@RestControllerAdvice. No custom frameworks.
In comments
- The comments rule is
.kiro/steering/coding-standards.md§10: a comment states a reason, constraint or workaround the code cannot show; nothing else.
In designs
- Start with the simplest design that meets the requirement. Only add more if a requirement forces it, and say which one.
- Record what you chose not to build, one line each, under Deliberately not designed in
design.md.
In commit messages
- Say what changed and why, in plain sentences. A few lines is usually enough.
Before you finish any task, ask: could a junior read this without asking me a question? If not, simplify it.
Steering files
Read in this order. The first four are the ones that change what you write; the rest are checked at
review time. Rules tagged [DEFAULT] were proposed by the team rather than set by the brief; to
override one, edit the file, remove the tag and commit with chore(steering): ....
| File | Inclusion | What it governs |
|---|---|---|
.kiro/steering/scope.md |
always | Read first. The scope guard: the id list, the refusal rule, the mandatory _Scope: and Definition of Done on every requirement and task |
.kiro/steering/product.md |
always | What the product is, its two users, the five capabilities, the nine exception cases |
.kiro/steering/tech.md |
always | Java 21 / Spring Boot 4.1 / Hibernate 7 / H2 / JUnit 5; the seven dependencies and the plugins; the forbidden list (javax.*, Lombok, JUnit 4, double for money, and pinning any Boot 3.x — OSS end-of-life since 30 Jun 2026) |
.kiro/steering/structure.md |
always | Package layout za.co.hyphenai.vending.{domain,application,infrastructure,api,config}, the rule that domain imports no Spring or JPA, naming, the directory tree |
.kiro/steering/coding-standards.md |
always | Google Java Style, records and immutability, constructor injection, null policy, exception rules, Javadoc, size limits |
.kiro/steering/logging.md |
always | SLF4J + Logback, the correlation-id filter and MDC, event.name key=value message style, level policy, what must and must never be logged |
.kiro/steering/git-workflow.md |
always | main/stage/dev → feat/<spec> → task_N-<slug>, Conventional Commits citing [task N] [REQ-x.y], --no-ff merges into feature and dev |
.kiro/steering/security.md |
always | Validation, CORS allow-list (on /actuator/health only), actuator limited to health, no secrets, container hardening, and the documented gaps |
.kiro/steering/api-conventions.md |
fileMatch src/main/java/**/api/** |
The complete endpoint list (nothing else may exist), JSON rules, RFC 7807 Problem Details, the exception→status table, OpenAPI annotation requirements |
.kiro/steering/testing.md |
fileMatch src/test/** |
The test pyramid, should<Expected>_when<Condition>, the @DisplayName must cite its REQ id, mandatory change-strategy and state-machine cases, 80% floor |
fileMatch files apply only when you are working on a matching path; always files apply to every
request.
Specs
Spec-driven work lives in .kiro/specs/<spec-name>/ as requirements.md → design.md →
tasks.md, each approved before the next is written. No phase is approved while
bash scripts/scope-guard.sh exits non-zero.
| Spec | Type | Phase | State |
|---|---|---|---|
vending-core/requirements.md |
Feature | requirements | ✅ approved — 11 requirements, 70 EARS criteria |
vending-core/design.md |
Feature | design | ✅ approved — HLD, ADR-0000…0006, flows |
vending-core/tasks.md |
Feature | tasks | ✅ approved — 18 tasks, all 70 criteria covered |
delivery-pipeline/ |
Quick Spec | tasks | ✅ approved — 3 requirements, 11 criteria, 3 tasks (Docker, CI, Dependabot); its ids are written "DP REQ-1.1" elsewhere |
delivery-pipeline is a Quick Spec because Dockerfiles and a CI workflow have no contentious design.
Guards — the anti-creep system
Five layers, each catching what the one before missed: the scope rule, review against the
allow-lists, the compiler and Checkstyle, the two guard scripts, and CI. The scripts are the
mechanical ones; they run as Kiro hooks (.kiro/hooks/), before a merge, and in CI. A guard is
not trusted until a planted violation has been watched to fail.
| Script | Refuses | Hook |
|---|---|---|
scripts/scope-guard.sh |
A requirement without _Scope: + Definition of Done; a task without _Requirements:, _Design:, _Scope:, _Affects: or a DoD; any cited id that does not exist (Files: is required by scope.md rule 3 but not checked by the script). Fails if it finds no blocks, or if --check names a file it cannot classify — so a renamed heading cannot silently disable it |
scope-guard.json |
scripts/no-secrets.sh |
Credentials, keys and tokens. PreToolUse is one of the three triggers that can block, so the write is refused rather than reported (B-5) |
no-secrets.json |
Both take --check <file>... and --stdin (hook mode). With no arguments scope-guard.sh checks
every spec file under .kiro/specs, while no-secrets.sh checks only the staged files;
no-secrets.sh --all scans every tracked file.
The hooks use Kiro's hook schema ({"version":"v1","hooks":[{name, trigger, matcher, action, timeout, enabled}]}); a command action receives session context as JSON on STDIN, which is what --stdin reads.
.kiro/settings/mcp.json configures the AWS documentation MCP server for the Kiro session; it is a
development-environment setting, not part of the product.
To run the same checks by hand, run the two scripts directly (see Commands below).
scripts/dev/local-check.sh is a local helper, not in the repository: scripts/dev/ is gitignored,
so a clone does not have it. A green guard you have not tried to break is not evidence.
These are the only two guard scripts: the compiler refuses javax.*, Lombok,
WebSecurityConfigurerAdapter and JUnit 4 on Boot 4.1 / Java 21, Checkstyle fails verify on
System.out, printStackTrace and double money, and the endpoint and dependency lists are kept
by review against api-conventions.md and tech.md.
Key documents
| Document | Why you would open it |
|---|---|
docs/business/technical-assignment-1.md |
The original brief, verbatim |
.kiro/steering/scope.md |
The id register — the fence. One copy, and it is this one |
docs/requirements/assumptions.md |
Every gap in the brief and the decision taken, plus the Deviations table. Two assumptions are marked ⚠ risky: Q-14 (no stock reservation) and Q-27 (no authentication) |
docs/architecture/hld.md |
Context, container, component, class and state diagrams |
docs/adr/ |
One ADR per significant decision, starting with ADR-0000 (Java 21 over Java 8) |
docs/design/flows.md |
Seven flows — four with sequence diagrams — each with edge cases, exceptions, log events and the tests that prove them |
docs/design/exception-handling.md |
The hierarchy, the status table and what the UI does with each Problem type |
docs/design/logging.md |
The event catalogue, the correlation-id mechanism and a worked example |
docs/requirements/traceability.md |
Scope id → REQ → design → task → test |
README.md |
Maintained with the code. Carries assumptions, deviations, security gaps and Not built |
Commands
./mvnw -B verify # unit (surefire) + feature (failsafe *IT) + checkstyle + jacoco report
# always verify, never test: test skips every *IT class
./mvnw spring-boot:run # start on :8080
./mvnw javadoc:javadoc # fails on error; CI runs it
curl localhost:8080/actuator/health
# Swagger UI: http://localhost:8080/swagger-ui.html
bash scripts/scope-guard.sh # all specs; --check <file> for one
bash scripts/no-secrets.sh --all
./run.sh --api (or .\run.ps1 --api) starts this API on its own; --docker uses the compose file. The whole
system (this service plus the UI) is started by run.sh in ../vending-machine-ui.
Working agreements
- One task in
tasks.md= one branch = one--no-ffmerge; commits cite[task N] [REQ-x.y](.kiro/steering/git-workflow.md). - A task is not done until
./mvnw -B verifyis green, including the*ITfeature tests.feature-test-after-task.jsonruns it onPostTaskExecution— which reports, it does not block, because onlyPromptSubmit,PreToolUseandPreTaskExecutioncan. The Definition of Done is the gate; the hook is what makes the evidence appear without being asked for. - Do not touch files outside a task's
Files:list. - A change that touches more than one area, or a file other features use, runs the regression set in
its task's
_Affects:line and the full suite before it is merged (.kiro/steering/testing.md). - Do not check a Definition of Done box you have not verified.
- Run every guard in
scripts/before proposing a merge; CI is the backstop, not the gate. - Never trust a green guard you have not tried to break.