geneseed 4.9.4 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +16 -2
  3. package/SHIPPED.md +1 -1
  4. package/docs/README.md +1 -1
  5. package/docs/concepts/collaboration.md +1 -1
  6. package/docs/concepts/enforcement-table.md +1 -1
  7. package/docs/concepts/foreman-mode.md +1 -1
  8. package/docs/concepts/kits.md +91 -0
  9. package/docs/concepts/loop-branch.md +1 -1
  10. package/docs/concepts/loop-bricks.md +1 -1
  11. package/docs/concepts/loop-contracts.md +1 -1
  12. package/docs/concepts/loop-gates.md +1 -1
  13. package/docs/concepts/loop-notes.md +1 -1
  14. package/docs/concepts/loop-risk.md +1 -1
  15. package/docs/concepts/loop-templates.md +1 -1
  16. package/docs/concepts/loops.md +1 -1
  17. package/docs/concepts/lsp.md +1 -1
  18. package/docs/concepts/skills.md +1 -1
  19. package/docs/extending.md +107 -4
  20. package/docs/guides/install-scripted.md +1 -1
  21. package/docs/guides/run-anywhere.md +2 -2
  22. package/docs/guides/upgrade.md +2 -2
  23. package/docs/reference/glossary.md +1 -0
  24. package/docs/reference/troubleshoot-install.md +1 -1
  25. package/docs/web-ui.md +9 -0
  26. package/harness.config.json +1 -1
  27. package/js/build/args.mjs +27 -6
  28. package/js/build/driver.mjs +29 -8
  29. package/js/build/emit-claude.mjs +3 -1
  30. package/js/build/generate.mjs +10 -5
  31. package/js/build/render.mjs +94 -11
  32. package/js/build/source.mjs +117 -4
  33. package/js/cli-table.json +15 -0
  34. package/js/hosts/installs.mjs +35 -2
  35. package/js/hosts/link.mjs +88 -1
  36. package/js/hosts/native.mjs +14 -4
  37. package/js/inspect/checks-authoring.mjs +6 -3
  38. package/js/inspect/checks-build.mjs +15 -3
  39. package/js/inspect/checks-counts.mjs +153 -6
  40. package/js/inspect/checks-repo.mjs +95 -20
  41. package/js/inspect/cost.mjs +1 -1
  42. package/js/inspect/diff.mjs +56 -8
  43. package/js/inspect/inventory.mjs +2 -0
  44. package/js/inspect/status.mjs +44 -3
  45. package/js/inspect/validate.mjs +3 -3
  46. package/js/lib/prompt.mjs +19 -2
  47. package/js/maintain/setup.mjs +111 -23
  48. package/js/maintain/update.mjs +3 -3
  49. package/js/web/actions.mjs +38 -6
  50. package/js/web/api.mjs +76 -17
  51. package/js/web/catalog.mjs +17 -2
  52. package/js/web/handler.mjs +1 -0
  53. package/js/web/routes.mjs +2 -1
  54. package/js/web/server.mjs +11 -5
  55. package/js/web/state.mjs +38 -3
  56. package/package.json +1 -1
  57. package/registry.json +91 -0
  58. package/src/AGENT.md.tmpl +7 -7
  59. package/src/agents/_self-improvement.md +11 -0
  60. package/src/agents/_template.md +1 -8
  61. package/src/agents/advocate.md +1 -8
  62. package/src/agents/architect.md +1 -8
  63. package/src/agents/developer.md +1 -8
  64. package/src/agents/devops-engineer.md +37 -0
  65. package/src/agents/docker-expert.md +37 -0
  66. package/src/agents/docs.md +1 -8
  67. package/src/agents/empiricist.md +1 -8
  68. package/src/agents/explorer.md +1 -8
  69. package/src/agents/framer.md +1 -8
  70. package/src/agents/historian.md +1 -8
  71. package/src/agents/kubernetes-specialist.md +37 -0
  72. package/src/agents/memory-curator.md +1 -8
  73. package/src/agents/operator.md +1 -8
  74. package/src/agents/performance.md +1 -8
  75. package/src/agents/pragmatist.md +1 -8
  76. package/src/agents/react-engineer.md +38 -0
  77. package/src/agents/react-reviewer.md +42 -0
  78. package/src/agents/researcher.md +5 -10
  79. package/src/agents/reviewer.md +3 -8
  80. package/src/agents/security.md +1 -8
  81. package/src/agents/skeptic.md +1 -8
  82. package/src/agents/spring-boot-engineer.md +40 -0
  83. package/src/agents/spring-reviewer.md +41 -0
  84. package/src/agents/spring-security.md +42 -0
  85. package/src/agents/steward.md +1 -8
  86. package/src/agents/tester.md +1 -8
  87. package/src/agents/user-advocate.md +1 -8
  88. package/src/agents/visionary.md +1 -8
  89. package/src/kits/react/rules.md +13 -0
  90. package/src/kits/react.json +18 -0
  91. package/src/kits/spring-boot/rules.md +10 -0
  92. package/src/kits/spring-boot.json +23 -0
  93. package/src/licenses/everything-claude-code.txt +21 -0
  94. package/src/licenses/mattpocock-skills.txt +21 -0
  95. package/src/licenses/piomin-spring-boot.txt +190 -0
  96. package/src/licenses/superpowers.txt +21 -0
  97. package/src/licenses/vibe-wise.txt +21 -0
  98. package/src/skills/_self-improvement.md +22 -5
  99. package/src/skills/brainstorm/builder-check.md +2 -0
  100. package/src/skills/brainstorm.md +11 -2
  101. package/src/skills/codebase-design.md +10 -2
  102. package/src/skills/daydream/VENDOR.md +7 -7
  103. package/src/skills/debug.md +6 -2
  104. package/src/skills/develop.md +6 -2
  105. package/src/skills/geneseed-code-review.md +6 -2
  106. package/src/skills/git-history.md +2 -0
  107. package/src/skills/handoff.md +3 -1
  108. package/src/skills/learn-mode.md +3 -2
  109. package/src/skills/parallel-agents.md +5 -1
  110. package/src/skills/pipeline.md +2 -0
  111. package/src/skills/plan.md +9 -2
  112. package/src/skills/prototype.md +6 -2
  113. package/src/skills/react/a11y.md +33 -0
  114. package/src/skills/react/vite.md +35 -0
  115. package/src/skills/react/webpack.md +40 -0
  116. package/src/skills/react-testing.md +68 -0
  117. package/src/skills/react-view-transitions/AGENTS.md +646 -221
  118. package/src/skills/react-view-transitions/SKILL.md +28 -16
  119. package/src/skills/react-view-transitions/VENDOR.md +10 -8
  120. package/src/skills/react-view-transitions/references/css-recipes.md +72 -2
  121. package/src/skills/react-view-transitions/references/implementation.md +14 -26
  122. package/src/skills/react-view-transitions/references/nextjs.md +94 -37
  123. package/src/skills/react-view-transitions/references/patterns.md +60 -48
  124. package/src/skills/react-view-transitions/references/troubleshooting.md +47 -0
  125. package/src/skills/react.md +63 -0
  126. package/src/skills/research.md +6 -2
  127. package/src/skills/review-response.md +2 -0
  128. package/src/skills/rule.md +6 -2
  129. package/src/skills/ship.md +8 -4
  130. package/src/skills/skill-forge.md +11 -5
  131. package/src/skills/spring-boot/cloud.md +103 -0
  132. package/src/skills/spring-boot/java-modern.md +71 -0
  133. package/src/skills/spring-boot/logging.md +89 -0
  134. package/src/skills/spring-boot/security.md +112 -0
  135. package/src/skills/spring-boot/web.md +112 -0
  136. package/src/skills/spring-boot.md +57 -0
  137. package/src/skills/spring-data-jpa/patterns.md +68 -0
  138. package/src/skills/spring-data-jpa.md +81 -0
  139. package/src/skills/spring-testing.md +93 -0
  140. package/src/skills/wayfinder.md +8 -2
  141. package/src/skills/wiki.md +18 -5
  142. package/src/upstreams.json +66 -0
  143. package/themes/_TEMPLATE.json +13 -0
  144. package/themes/biker.json +13 -0
  145. package/themes/commentator.json +13 -0
  146. package/themes/cyberpunk.json +13 -0
  147. package/themes/gamer.json +13 -0
  148. package/themes/imperial.json +13 -0
  149. package/themes/joker.json +13 -0
  150. package/themes/marvin.json +13 -0
  151. package/themes/mean.json +13 -0
  152. package/themes/military.json +13 -0
  153. package/themes/neutral.json +13 -0
  154. package/themes/pirate.json +13 -0
  155. package/themes/sports.json +13 -0
  156. package/themes/verstappen.json +13 -0
  157. package/themes/wizard.json +13 -0
  158. package/web/dist/assets/Activity-44rbWUQI.js +1 -0
  159. package/web/dist/assets/ActivityDetail-Dk9k893a.js +1 -0
  160. package/web/dist/assets/Dashboard-DCoUN60W.js +2 -0
  161. package/web/dist/assets/{Docs-tDW7i7mo.js → Docs-C_je2lRG.js} +3 -3
  162. package/web/dist/assets/FilterInput-CZREvrjX.js +1 -0
  163. package/web/dist/assets/GreenhouseView-BXAOIznW.js +2 -0
  164. package/web/dist/assets/Installs-DAXF403D.js +3 -0
  165. package/web/dist/assets/JournalView-DZ184wKB.js +1 -0
  166. package/web/dist/assets/KitApply-pjhvGha0.js +1 -0
  167. package/web/dist/assets/Kits-BCCKjSIY.js +1 -0
  168. package/web/dist/assets/{Laws-BA3CzUf5.js → Laws-BgBUpRkE.js} +1 -1
  169. package/web/dist/assets/Library-CW0Zl6RF.js +1 -0
  170. package/web/dist/assets/Loops-BvFOzjEw.js +2 -0
  171. package/web/dist/assets/{Markdown-iHvBrpRN.js → Markdown-zaXXXHz9.js} +13 -13
  172. package/web/dist/assets/OperatorHudView-Bs4unPNi.js +2 -0
  173. package/web/dist/assets/Personal-C23XaT-a.js +1 -0
  174. package/web/dist/assets/Tabs-b2XFu4OS.js +1 -0
  175. package/web/dist/assets/activity-Jw8XcPiP.js +1 -0
  176. package/web/dist/assets/api-BfpZmUMI.js +1 -0
  177. package/web/dist/assets/{drawable-CyLe1gtW.js → drawable-BERkKhx_.js} +1 -1
  178. package/web/dist/assets/index-C9R_bRw_.js +2 -0
  179. package/web/dist/assets/{index-BCJz4_8C.css → index-jYlWihN-.css} +1 -1
  180. package/web/dist/assets/{react-BOF6yL40.js → react-DlYQp10C.js} +1 -1
  181. package/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  182. package/web/dist/assets/sections-ChwaobfW.js +1 -0
  183. package/web/dist/assets/usePoll-bDFN-Hr6.js +1 -0
  184. package/web/dist/index.html +6 -5
  185. package/web/dist/assets/Activity-C8sZVIUU.js +0 -1
  186. package/web/dist/assets/ActivityDetail-Cc6vWUS4.js +0 -1
  187. package/web/dist/assets/Dashboard-C5ATa3ow.js +0 -2
  188. package/web/dist/assets/FilterInput-zLFXoSt6.js +0 -1
  189. package/web/dist/assets/GreenhouseView-zk2Uzc3A.js +0 -2
  190. package/web/dist/assets/Installs-CB_rvbBP.js +0 -3
  191. package/web/dist/assets/JournalView-Bfj27XHC.js +0 -1
  192. package/web/dist/assets/Library-BWwiy9Vb.js +0 -1
  193. package/web/dist/assets/Loops-DaPk-ckL.js +0 -2
  194. package/web/dist/assets/OperatorHudView-CyiGhiHk.js +0 -2
  195. package/web/dist/assets/Personal-Bwd27CmT.js +0 -1
  196. package/web/dist/assets/Tabs-BOD7Kaz1.js +0 -1
  197. package/web/dist/assets/activity-DvPxoS4Y.js +0 -1
  198. package/web/dist/assets/api-CiXlkbe0.js +0 -1
  199. package/web/dist/assets/index-DQXG_mqo.js +0 -2
  200. package/web/dist/assets/sections-BET3c1WT.js +0 -1
  201. package/web/dist/assets/usePoll-NSTzLOF4.js +0 -1
@@ -0,0 +1,89 @@
1
+ # {{SKILL}}: spring-boot — logging
2
+
3
+ {{CREDIT:piomin-spring-boot:skills/logging-patterns/SKILL.md}}
4
+
5
+ SLF4J through Spring Boot's logging, with structured (JSON) output where logs are machine-read. Structured output is a good default for deployed environments and CI because fields parse directly (`jq`, log platforms, an AI assistant reading logs); keep readable text for local development.
6
+
7
+ ## Structured logging (built in)
8
+ No extra dependency. Pick a format per target:
9
+
10
+ ```yaml
11
+ logging:
12
+ structured:
13
+ format:
14
+ console: logstash # also: ecs, gelf
15
+ ```
16
+
17
+ `logging.structured.format.file` does the same for file output. A custom Logback or Log4j2 config must use Boot's structured encoder or layout (`StructuredLogEncoder` / `StructuredLogLayout` fed by `CONSOLE_LOG_STRUCTURED_FORMAT`), or the property has no effect.
18
+
19
+ Profile split so developers keep readable logs:
20
+
21
+ ```yaml
22
+ ---
23
+ spring:
24
+ config:
25
+ activate:
26
+ on-profile: "!local"
27
+ logging:
28
+ structured:
29
+ format:
30
+ console: logstash
31
+ ```
32
+
33
+ Run locally with the `local` profile for plain text. With Micrometer Tracing present, Boot adds `[traceId-spanId]` to every log line by default; change the format with `logging.pattern.correlation`. Whether they appear as JSON fields depends on the format; confirm in a real log line.
34
+
35
+ ## Writing log statements
36
+ ```java
37
+ private static final Logger log = LoggerFactory.getLogger(OrderService.class);
38
+
39
+ log.info("Order created id={} customer={}", order.id(), order.customerId()); // parameterized
40
+ log.error("Payment failed for order {}", order.id(), e); // exception last, stack trace kept
41
+ ```
42
+ - Placeholders, never string concatenation. Guard only genuinely expensive arguments with `isDebugEnabled()`.
43
+ - Pass the exception as the final argument; do not log `e.getMessage()` alone.
44
+ - Log an exception once, where it is handled, not at every layer it passes through.
45
+ - Never log secrets, tokens, passwords or personal data; log identifiers.
46
+ - Lombok's `@Slf4j` is fine where the project uses Lombok; otherwise declare the logger as above.
47
+
48
+ | Level | Meaning |
49
+ |-------|---------|
50
+ | ERROR | Something failed and needs attention |
51
+ | WARN | Unexpected but handled (fallback used, retry succeeded) |
52
+ | INFO | Business events (order created, job finished) |
53
+ | DEBUG | Technical detail (queries, outbound calls) |
54
+ | TRACE | Verbose internals |
55
+
56
+ ## MDC for correlation
57
+ MDC attaches key-value context to every log line written by the current thread. Set it at the edge in a filter and always clear it in `finally`.
58
+
59
+ ```java
60
+ @Component
61
+ @Order(Ordered.HIGHEST_PRECEDENCE)
62
+ class MdcFilter extends OncePerRequestFilter {
63
+ @Override
64
+ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
65
+ throws ServletException, IOException {
66
+ String header = req.getHeader("X-Request-Id");
67
+ // client-supplied: accept only a short, safe value
68
+ String id = header != null && header.matches("[A-Za-z0-9-]{1,64}") ? header : UUID.randomUUID().toString();
69
+ MDC.put("requestId", id);
70
+ try {
71
+ chain.doFilter(req, res);
72
+ } finally {
73
+ MDC.remove("requestId");
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ MDC is thread-local, so it does not follow work handed to another thread (virtual or platform). For executor tasks, copy the context with a `TaskDecorator` (`MDC.getCopyOfContextMap()` before, `MDC.setContextMap(...)` inside the task, `MDC.clear()` in `finally`) and register it on the executor you use; check that Boot applies it to your executor in the version you run.
80
+
81
+ Useful fields for incident analysis: `requestId`, user or tenant id, and a duration on slow-path logs. Keep them to identifiers, not payloads.
82
+
83
+ ## Quick reference
84
+ | Need | Use |
85
+ |------|-----|
86
+ | JSON console | `logging.structured.format.console: logstash` |
87
+ | Per-package level | `logging.level.com.example.orders: DEBUG` |
88
+ | Correlate a request | MDC `requestId` set in a filter |
89
+ | Follow one request in JSON logs | `jq 'select(.requestId == "abc")' app.log` (field name depends on MDC key and format) |
@@ -0,0 +1,112 @@
1
+ # {{SKILL}}: spring-boot — security
2
+
3
+ {{CREDIT:piomin-spring-boot:skills/spring-boot/references/security.md}}
4
+
5
+ Spring Security 7. Default posture for an API: a stateless OAuth2 resource server that validates JWTs issued by an identity provider. Do not write your own token issuer, JWT filter or password store unless the task is explicitly to build an authorization server; that code is where the security bugs live.
6
+
7
+ ## Resource server
8
+ Properties first (Boot builds the decoder from them):
9
+
10
+ ```yaml
11
+ spring:
12
+ security:
13
+ oauth2:
14
+ resourceserver:
15
+ jwt:
16
+ issuer-uri: https://auth.example.com
17
+ audiences:
18
+ - orders-api
19
+ ```
20
+
21
+ ```java
22
+ @Configuration
23
+ @EnableWebSecurity
24
+ @EnableMethodSecurity
25
+ class SecurityConfig {
26
+
27
+ @Bean
28
+ SecurityFilterChain api(HttpSecurity http) throws Exception {
29
+ http
30
+ .cors(Customizer.withDefaults())
31
+ .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
32
+ .authorizeHttpRequests(auth -> auth
33
+ .requestMatchers("/actuator/health/**").permitAll()
34
+ .requestMatchers("/api/admin/**").hasRole("ADMIN")
35
+ .anyRequest().authenticated())
36
+ .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
37
+ return http.build();
38
+ }
39
+ }
40
+ ```
41
+
42
+ - Lambda DSL only; `.and()` chaining and `authorizeRequests` are gone in Security 7.
43
+ - Disabling CSRF is acceptable only for a stateless, bearer-token API with no cookie-based session. If any endpoint authenticates with a cookie, keep CSRF on.
44
+ - `cors(Customizer.withDefaults())` picks up a `CorsConfigurationSource` bean; define that bean with explicit origins.
45
+ - Default JWT algorithm trust is RS256 only; widen it deliberately if the provider signs otherwise.
46
+
47
+ ## Mapping claims to authorities
48
+ The default converter maps the `scope` claim to `SCOPE_` authorities. For roles in another claim:
49
+
50
+ ```java
51
+ @Bean
52
+ JwtAuthenticationConverter jwtAuthenticationConverter() {
53
+ var authorities = new JwtGrantedAuthoritiesConverter();
54
+ authorities.setAuthoritiesClaimName("roles");
55
+ authorities.setAuthorityPrefix("ROLE_");
56
+ var converter = new JwtAuthenticationConverter();
57
+ converter.setJwtGrantedAuthoritiesConverter(authorities);
58
+ return converter;
59
+ }
60
+ ```
61
+
62
+ Wire it with `oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))`.
63
+
64
+ ## Validate audience
65
+ An issuer check alone accepts any token that issuer minted for any API. The `audiences` property in the YAML above makes Boot require the `aud` claim. Define your own `JwtDecoder` bean only when you need custom validators; it replaces the property-driven decoder, audiences included.
66
+
67
+ ## Method security
68
+ `@EnableMethodSecurity` turns on `@PreAuthorize` and `@PostAuthorize`. Authorize in the service layer, next to the data, not only on URL patterns. The legacy Access API (`AccessDecisionManager`, voters) moved to the separate `spring-security-access` module; `@Secured` still works via `@EnableMethodSecurity(securedEnabled = true)`, but prefer `@PreAuthorize`.
69
+
70
+ ```java
71
+ @PreAuthorize("hasRole('ADMIN') or #ownerId == authentication.name")
72
+ public Order find(String ownerId, Long id) { ... }
73
+ ```
74
+
75
+ ## Testing
76
+ Use the `jwt()` request post processor with MockMvc (`SecurityMockMvcRequestPostProcessors.jwt()`; default token has `scope=read`), or `@WithMockUser` for method security. Always test the 401 (no token) and 403 (wrong authority) paths, not only success.
77
+
78
+ ## TLS with SSL bundles
79
+ Configure server and client TLS with SSL bundles (named key material). Reloading works only when the consuming component supports it (Tomcat and Netty servers) and only through the file watcher, so the material must be on the file system with a `file:` location; a `classpath:` bundle cannot reload.
80
+
81
+ ```yaml
82
+ spring:
83
+ ssl:
84
+ bundle:
85
+ pem:
86
+ server:
87
+ reload-on-update: true
88
+ keystore:
89
+ certificate: "file:/etc/ssl/app/tls.crt"
90
+ private-key: "file:/etc/ssl/app/tls.key"
91
+ server:
92
+ ssl:
93
+ bundle: server
94
+ ```
95
+
96
+ Tune detection with `spring.ssl.bundle.watch.file.quiet-period` (default 10s). Apply a bundle to an outbound `RestClient` with the `RestClientSsl` helper (it replaces any request factory you set earlier; use a separate `client` bundle with a `truststore` for the upstream, not the server's keystore bundle), or build a factory when you need more than SSL:
97
+
98
+ ```java
99
+ RestClient client(RestClient.Builder builder, RestClientSsl ssl) {
100
+ return builder.baseUrl("https://internal-service").apply(ssl.fromBundle("client")).build();
101
+ }
102
+ // more than SSL:
103
+ // var settings = HttpClientSettings.ofSslBundle(bundles.getBundle("client")).withReadTimeout(Duration.ofSeconds(5));
104
+ // builder.requestFactory(ClientHttpRequestFactoryBuilder.detect().build(settings))
105
+ ```
106
+
107
+ ## Checklist
108
+ - HTTPS everywhere outside local dev; secrets from env or mounted files ([spring-boot/cloud.md](cloud.md) covers config trees).
109
+ - Validate issuer and audience; keep token lifetimes short; never log tokens.
110
+ - Passwords (if you must store any): `PasswordEncoder` from `PasswordEncoderFactories`/BCrypt, never a plain hash.
111
+ - Rate-limit authentication endpoints at the gateway or proxy.
112
+ - Ask the `spring-security` agent for review (it hands the generic audit to `security`) when the change touches authentication or authorization.
@@ -0,0 +1,112 @@
1
+ # {{SKILL}}: spring-boot — web layer
2
+
3
+ {{CREDIT:piomin-spring-boot:skills/spring-boot/references/web.md}}
4
+
5
+ Controllers, validation, error handling and outbound HTTP for a Spring MVC service.
6
+
7
+ ## Controller
8
+ Thin: bind, validate, delegate, shape the response. Create returns `201` with a `Location` header; delete returns `204`.
9
+
10
+ ```java
11
+ @RestController
12
+ @RequestMapping("/api/v1/users")
13
+ class UserController {
14
+ private final UserService service;
15
+
16
+ UserController(UserService service) { this.service = service; }
17
+
18
+ @GetMapping
19
+ PagedModel<UserResponse> list(@PageableDefault(size = 20, sort = "createdAt") Pageable pageable) {
20
+ return new PagedModel<>(service.findAll(pageable));
21
+ }
22
+
23
+ @PostMapping
24
+ ResponseEntity<UserResponse> create(@Valid @RequestBody UserCreateRequest request) {
25
+ UserResponse user = service.create(request);
26
+ URI location = ServletUriComponentsBuilder.fromCurrentRequest()
27
+ .path("/{id}").buildAndExpand(user.id()).toUri();
28
+ return ResponseEntity.created(location).body(user);
29
+ }
30
+
31
+ @DeleteMapping("/{id}")
32
+ @ResponseStatus(HttpStatus.NO_CONTENT)
33
+ void delete(@PathVariable Long id) { service.delete(id); }
34
+ }
35
+ ```
36
+
37
+ Version the path (`/api/v1`). Use GET for reads, POST/PUT/PATCH/DELETE for state changes; never change state on GET.
38
+
39
+ ## DTOs and validation
40
+ Records for request and response. Constraints sit on the record components (`jakarta.validation.constraints`); `@Valid` on the controller parameter triggers them. Put the entity-to-DTO mapping in a static factory or a mapper, not in the controller.
41
+
42
+ ```java
43
+ public record UserCreateRequest(
44
+ @NotBlank @Email String email,
45
+ @NotBlank @Size(min = 8, max = 100) String password,
46
+ @Min(18) Integer age) {}
47
+ ```
48
+
49
+ A custom constraint is an annotation with `@Constraint(validatedBy = ...)` plus a `ConstraintValidator`; the validator may be a Spring bean and inject a repository. A uniqueness check in a validator is advisory only; the database constraint is the truth, so also handle `DataIntegrityViolationException` (409).
50
+
51
+ ## Errors: Problem Details
52
+ Enable RFC 9457 responses for Spring's own exceptions:
53
+
54
+ ```yaml
55
+ spring:
56
+ mvc:
57
+ problemdetails:
58
+ enabled: true
59
+ ```
60
+
61
+ Return `ProblemDetail` from your own handlers so every error shares one shape. Extending `ResponseEntityExceptionHandler` keeps the framework exceptions on that shape too.
62
+
63
+ ```java
64
+ @RestControllerAdvice
65
+ class ApiExceptionHandler extends ResponseEntityExceptionHandler {
66
+
67
+ @ExceptionHandler(ResourceNotFoundException.class)
68
+ ProblemDetail notFound(ResourceNotFoundException ex) {
69
+ return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
70
+ }
71
+
72
+ @ExceptionHandler(DataIntegrityViolationException.class)
73
+ ProblemDetail conflict(DataIntegrityViolationException ex) {
74
+ return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Resource already exists");
75
+ }
76
+ }
77
+ ```
78
+
79
+ A domain exception can carry its own problem by extending `ErrorResponseException`. Never put stack traces or SQL text in `detail`; log the exception server-side and return a generic message for the catch-all 500.
80
+
81
+ ## Outbound HTTP
82
+ Blocking calls use `RestClient`; build it from the injected `RestClient.Builder` so Boot's customizers (timeouts, SSL, observability) apply.
83
+
84
+ ```java
85
+ @Bean
86
+ RestClient pricingClient(RestClient.Builder builder) {
87
+ return builder.baseUrl("https://api.example.com").build();
88
+ }
89
+
90
+ Quote fetch(String id) {
91
+ return pricingClient.get().uri("/quotes/{id}", id)
92
+ .retrieve()
93
+ .onStatus(status -> status.value() == 404, (req, res) -> {
94
+ throw new ResourceNotFoundException("Quote " + id);
95
+ })
96
+ .body(Quote.class);
97
+ }
98
+ ```
99
+
100
+ Prefer an `@HttpExchange` interface when the remote API has several operations (see [spring-boot/cloud.md](cloud.md)). `WebClient` is for reactive or streaming code only.
101
+
102
+ ## CORS
103
+ Prefer one bean over scattered annotations. With Spring Security on the classpath configure CORS in the security chain ([spring-boot/security.md](security.md)); otherwise `WebMvcConfigurer.addCorsMappings`. List explicit origins; `allowedOrigins("*")` cannot be combined with credentials.
104
+
105
+ ## Quick reference
106
+ | Need | Use |
107
+ |------|-----|
108
+ | Page and sort | `Pageable` + `@PageableDefault` |
109
+ | Bad input | `@Valid` -> 400 Problem Details |
110
+ | Missing resource | 404 via your exception handler |
111
+ | Duplicate | 409 from the DB constraint |
112
+ | Slow upstream | `spring.http.clients.connect-timeout` / `read-timeout` |
@@ -0,0 +1,57 @@
1
+ # {{SKILL}}: spring-boot
2
+
3
+ > {{DESC_SPRING_BOOT}}
4
+
5
+ {{CREDIT:piomin-spring-boot:skills/spring-boot/SKILL.md}}
6
+
7
+ {{CREDIT:piomin-spring-boot:skills/conventions/SKILL.md}}
8
+
9
+ **Trigger:** writing or changing a Spring Boot application (Spring Boot 4, Spring Framework 7, Java 21+) — controllers, services, configuration, security, HTTP clients, logging, cloud-native deployment. Load it for any Spring Boot task, then read the side file the task needs. It does not replace the core review or refactor skills; for a Spring review or refactor, load those and use this one for the framework facts.
10
+
11
+ Target stack: Spring Boot 4 / Spring Framework 7 / Spring Security 7 / Hibernate 7 / Jakarta EE 11 / Java 21+. Check the project's actual versions first (`pom.xml` or `build.gradle`); if it is still on Boot 3, say so and do not apply the Boot 4 rows below blindly.
12
+
13
+ ## Working loop
14
+ 1. Read the build file, `application.yml` and the existing package layout. Match what the project already does (Maven or Gradle, its CI, its test style) before adding anything.
15
+ 2. Confirm the design for anything beyond a small change: endpoints, DTOs, transaction boundaries, who authenticates.
16
+ 3. Implement in layers, then run the project's own test command and read the output.
17
+
18
+ ## Conventions
19
+ - Layering: controller (HTTP, validation) -> service (business logic, transactions) -> repository. Keep the package layout the project already has; for a new app a flat feature-or-layer split under one root package is fine (`com.example.<app>`).
20
+ - Constructor injection only; one constructor needs no `@Autowired`. Lombok is allowed where the project uses it.
21
+ - DTOs are records. JPA entities are not records. Never return an entity from a controller.
22
+ - `@Valid` on every request body; `@Transactional(readOnly = true)` on read services, `@Transactional` on mutating methods.
23
+ - Typed config: `@ConfigurationProperties` records instead of scattered `@Value`.
24
+ - Errors: one `@RestControllerAdvice`, Problem Details responses.
25
+ - Blocking outbound HTTP: `RestClient`, or an `@HttpExchange` interface. `WebClient` only for reactive or streaming code. Do not mix blocking and reactive in one call path.
26
+ - Jakarta namespaces only (`jakarta.persistence`, `jakarta.validation`, `jakarta.servlet`).
27
+ - Secrets from the environment or mounted files, never committed config.
28
+ - Tests: positive and negative cases; Testcontainers with `@ServiceConnection`; `@MockitoBean` for slice-test collaborators.
29
+ - Baseline properties worth enabling: `spring.threads.virtual.enabled: true`, `spring.mvc.problemdetails.enabled: true`, `server.shutdown: graceful`.
30
+
31
+ ## Boot 4 pitfalls
32
+ Removed or renamed in Boot 4 (do not write them): `@MockBean` and `@SpyBean`, `ClientHttpRequestFactories`, `javax.persistence` (now `jakarta.persistence`), Spring Security 6 idioms such as `authorizeRequests` and `.and()` chains. <!-- banned: ok -->
33
+
34
+ | Old habit | Boot 4 / Framework 7 reality |
35
+ |-----------|------------------------------|
36
+ | Mock beans in slice tests | `@MockitoBean`, `@MockitoSpyBean` (`org.springframework.test.context.bean.override.mockito`) |
37
+ | Hand-built request factories | `ClientHttpRequestFactoryBuilder` with `HttpClientSettings` |
38
+ | Security configuration | Spring Security 7: lambda DSL, `authorizeHttpRequests` |
39
+ | `org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest` | `org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest`: slices moved to per-technology modules; import from the new packages |
40
+ | Jackson 2 (`com.fasterxml.jackson`) | Jackson 3 is the default (`tools.jackson`); Jackson 2 support is deprecated, `spring.jackson.use-jackson2-defaults` eases migration |
41
+ | `@DynamicPropertySource` for container URLs | `@ServiceConnection` on the container |
42
+ | MockMvc status assertions via Hamcrest | `MockMvcTester` (AssertJ) or `RestTestClient` with `@AutoConfigureRestTestClient` |
43
+ | OpenFeign for declarative clients | `@HttpExchange` interfaces, registered with `@ImportHttpServices` |
44
+ | Zipkin-only tracing setup | `spring-boot-starter-opentelemetry`, `management.opentelemetry.tracing.export.otlp.*` |
45
+ | Starters assumed to pull everything | Boot 4 starters are modular; add the matching starter or test starter for each technology, and read the migration guide when a class vanishes |
46
+
47
+ ## Side files
48
+ - **Web/REST** -> read [spring-boot/web.md](spring-boot/web.md) and follow it.
49
+ - **Security (OAuth2 resource server, method security, SSL bundles)** -> read [spring-boot/security.md](spring-boot/security.md) and follow it.
50
+ - **Cloud-native (config, gateway, resilience, tracing, Kubernetes, images)** -> read [spring-boot/cloud.md](spring-boot/cloud.md) and follow it.
51
+ - **Modern Java in Spring code (records, sealed types, pattern matching, null safety)** -> read [spring-boot/java-modern.md](spring-boot/java-modern.md) and follow it.
52
+ - **Logging and structured logs** -> read [spring-boot/logging.md](spring-boot/logging.md) and follow it.
53
+
54
+ ## Done when
55
+ - The project's build and tests pass, read from real output; no deprecated Boot 2/3 idiom was introduced; every new endpoint validates input and has a negative test.
56
+
57
+ <!-- INCLUDE: skills/_self-improvement.md -->
@@ -0,0 +1,68 @@
1
+ # {{SKILL}}: spring-data-jpa — outbox, saga, CQRS
2
+
3
+ {{CREDIT:piomin-spring-boot:skills/design-patterns/SKILL.md}}
4
+
5
+ Use when a write must also publish an event, a business process spans services, or reads and writes need separate models. If a plain `@Transactional` method does the job, do not add these.
6
+
7
+ ## Transactional outbox
8
+
9
+ Dual write (save, then publish to a broker) can lose or duplicate the event. Write the event row in the same transaction as the state change; a separate relay publishes it.
10
+
11
+ ```java
12
+ @Entity
13
+ public class OutboxEvent {
14
+ @Id @GeneratedValue(strategy = GenerationType.UUID) UUID id;
15
+ String aggregateId;
16
+ String type;
17
+ @Column(columnDefinition = "text") String payload;
18
+ boolean published;
19
+ // constructor, getters
20
+ }
21
+
22
+ @Service
23
+ class OrderService {
24
+ @Transactional
25
+ public Order place(OrderRequest req) {
26
+ Order order = orders.save(Order.from(req));
27
+ outbox.save(new OutboxEvent(order.getId().toString(), "OrderPlaced", json(order)));
28
+ return order; // both rows commit or neither does
29
+ }
30
+ }
31
+
32
+ @Component
33
+ class OutboxRelay {
34
+ @Scheduled(fixedDelay = 1000) // needs @EnableScheduling on a config class
35
+ @Transactional
36
+ void relay() {
37
+ for (OutboxEvent e : outbox.findTop100ByPublishedFalseOrderByIdAsc()) {
38
+ publisher.send(e); // broker client; may throw, row stays unpublished
39
+ e.setPublished(true);
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ - Delivery is at-least-once: consumers dedupe on the event id.
46
+ - Several relay instances will pick the same rows. Use a single relay, or a row-claiming query verified for your database (not covered here), or a lock.
47
+ - Delete or archive published rows on a schedule.
48
+ - In-process alternative: publish a Spring `ApplicationEvent` and handle it with `@TransactionalEventListener` (default phase `AFTER_COMMIT`). The handler runs only if the transaction commits, and it is not durable: a crash after commit loses the event. Use it for in-JVM side effects, the outbox table for anything that must survive a crash. If the listener must write to the database again, annotate it `@Transactional(propagation = Propagation.REQUIRES_NEW)`.
49
+
50
+ ## Saga
51
+
52
+ | Style | Mechanism | Pick when |
53
+ |-------|-----------|-----------|
54
+ | Choreography | each service reacts to the previous service's event and emits its own (events through the outbox) | few steps, no central owner |
55
+ | Orchestration | one `OrderSaga` component sends commands, tracks state in its own table, issues compensations | many steps, branching, need to see where a flow is stuck |
56
+
57
+ - Every step has a compensating action (cancel, refund, release); compensations must be idempotent.
58
+ - Persist saga state (`STARTED`, `PAYMENT_DONE`, `FAILED`, ...) with `@Version` so concurrent events cannot both advance it.
59
+ - Timeouts are events too: a scheduled job moves stale sagas to compensation.
60
+ - Messaging infrastructure (Spring Cloud Stream, plain Kafka or AMQP clients) is project-specific and not verified here; keep the saga logic behind a small publisher interface.
61
+
62
+ ## CQRS
63
+
64
+ Split only when read and write shapes diverge. Smallest useful form, same database:
65
+
66
+ - Write side: aggregate entities and `@Transactional` command services.
67
+ - Read side: projection or DTO-record repository methods (or a view) behind a `@Transactional(readOnly = true)` query service; never return entities.
68
+ - A separate read store fed by outbox events adds eventual consistency: show it in the API (version or timestamp) and accept stale reads.
@@ -0,0 +1,81 @@
1
+ # {{SKILL}}: spring-data-jpa
2
+
3
+ > {{DESC_SPRING_DATA_JPA}}
4
+
5
+ {{CREDIT:piomin-spring-boot:skills/jpa-patterns/SKILL.md}}
6
+
7
+ {{CREDIT:piomin-spring-boot:skills/spring-boot/references/data.md}}
8
+
9
+ **Trigger:** a Spring Data JPA / Hibernate task in a Spring Boot project: entities, repositories, N+1 queries, `LazyInitializationException`, transactions, locking, projections, Flyway or Liquibase migrations, soft delete, or a transactional outbox. Spring-specific; for generic query or schema work outside Spring, do not load it.
10
+
11
+ Target stack: Spring Boot 4, Hibernate 7, Jakarta Persistence (`jakarta.persistence.*` only).
12
+
13
+ ## Symptom to fix
14
+
15
+ | Symptom | Fix |
16
+ |---------|-----|
17
+ | N+1 SELECTs | `@EntityGraph(attributePaths = "books")` or `JOIN FETCH`; as a mitigation only, `hibernate.default_batch_fetch_size` (batch fetching mitigates N+1, it does not solve it) |
18
+ | `LazyInitializationException` | fetch what the use case needs inside the transaction (graph / `JOIN FETCH`) or return a DTO; do not flip associations to `EAGER` |
19
+ | Slow list endpoints | `Pageable` (`Slice` when no total is needed), interface or record projections, indexes |
20
+ | Lost updates | `@Version` (optimistic); `@Lock(LockModeType.PESSIMISTIC_WRITE)` only for hot rows |
21
+ | Slow read-only service | `@Transactional(readOnly = true)` on the class, override on writes |
22
+ | Transaction ignored | self-invocation bypasses the proxy: call through another bean |
23
+
24
+ ## Entities
25
+
26
+ ```java
27
+ @Entity
28
+ @SoftDelete // Hibernate: adds a deleted-flag column, filters reads
29
+ public class Author {
30
+ @Id @GeneratedValue(strategy = GenerationType.UUID)
31
+ private UUID id;
32
+ @Version private long version;
33
+ @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
34
+ private List<Book> books = new ArrayList<>();
35
+ // getters/setters, or Lombok if the project uses it
36
+ }
37
+ ```
38
+
39
+ - Every to-one and to-many is `LAZY`; decide fetching per query, not on the mapping.
40
+ - `@SoftDelete(strategy = ...)` takes `ACTIVE`, `DELETED`, or `TIMESTAMP`; the default stores a deleted flag.
41
+ - `GenerationType.UUID` generates the id in Java; `IDENTITY` and `SEQUENCE` need the database.
42
+ - Equality on entities: id-based with a stable hash, or business key. Never include lazy collections in `equals`/`toString`.
43
+
44
+ ## Repositories and projections
45
+
46
+ ```java
47
+ public interface AuthorRepository extends JpaRepository<Author, UUID> {
48
+ @EntityGraph(attributePaths = "books")
49
+ List<Author> findByNameStartingWith(String prefix);
50
+
51
+ record AuthorName(UUID id, String name) {}
52
+ List<AuthorName> findAllBy(Pageable pageable); // DTO projection
53
+
54
+ @Modifying(clearAutomatically = true)
55
+ @Query("update Author a set a.name = :name where a.id = :id")
56
+ int rename(UUID id, String name);
57
+ }
58
+ ```
59
+
60
+ - Interface projections are built from tuple queries; DTO records need an all-args constructor and no column aliases in `@Query` JPQL.
61
+ - Bulk `@Modifying` queries bypass the persistence context: `clearAutomatically = true` or reload.
62
+ - Return DTOs from the web layer, never entities.
63
+
64
+ ## Transactions and open-in-view
65
+
66
+ - Put `@Transactional` on service methods, not repositories or controllers.
67
+ - `spring.jpa.open-in-view` defaults to `true` in a web app (the EntityManager stays open through view rendering). Set it to `false` and fetch explicitly; it hides N+1 otherwise.
68
+ - Detect N+1: `spring.jpa.show-sql=true` is enough for a quick look; assert query counts in a `@DataJpaTest` for regressions.
69
+
70
+ ## Migrations
71
+
72
+ Schema is owned by migrations, not `ddl-auto`: `spring.jpa.hibernate.ddl-auto=validate` (or `none`) with Flyway or Liquibase, whichever the project already uses. In Boot 4 add the starter (`spring-boot-starter-flyway`), not just the Flyway core jar, or auto-configuration does not apply; Postgres and MySQL also need `flyway-database-postgresql` / `flyway-mysql`. Migration files are immutable once merged; add a new one.
73
+
74
+ ## Outbox, saga, CQRS
75
+
76
+ Event-publishing alongside a DB write (dual-write problem), distributed workflows, read/write model split: read [spring-data-jpa/patterns.md](spring-data-jpa/patterns.md).
77
+
78
+ ## Done when
79
+ - No lazy collection is touched outside a transaction; hot queries show one SELECT per use case in the SQL log; schema changes ship as a migration; imports are `jakarta.persistence`.
80
+
81
+ <!-- INCLUDE: skills/_self-improvement.md -->
@@ -0,0 +1,93 @@
1
+ # {{SKILL}}: spring-testing
2
+
3
+ > {{DESC_SPRING_TESTING}}
4
+
5
+ {{CREDIT:piomin-spring-boot:skills/testing-patterns/SKILL.md}}
6
+
7
+ {{CREDIT:piomin-spring-boot:skills/spring-boot/references/testing.md}}
8
+
9
+ **Trigger:** writing or fixing tests in a Spring Boot project: unit tests of services, `@WebMvcTest` / `@DataJpaTest` slices, `@SpringBootTest` integration tests, Testcontainers, mocking beans. Spring-specific; core test discipline lives in the test-gates and develop skills.
10
+
11
+ Target stack: Spring Boot 4, JUnit 5, Mockito, AssertJ.
12
+
13
+ ## Pick the narrowest test
14
+
15
+ | Goal | Tool |
16
+ |------|------|
17
+ | Service logic | plain JUnit + `@ExtendWith(MockitoExtension.class)`, no Spring context |
18
+ | Controller mapping, validation, serialization | `@WebMvcTest(X.class)` + `MockMvcTester` + `@MockitoBean` |
19
+ | Repository queries, mappings | `@DataJpaTest` against the real database engine |
20
+ | Whole app over HTTP | `@SpringBootTest(webEnvironment = RANDOM_PORT)` + `@AutoConfigureRestTestClient` |
21
+ | Whole app, no server | `@SpringBootTest` + `@AutoConfigureMockMvc` |
22
+
23
+ Boot 4 split the test support into modules: slice annotations live in `org.springframework.boot.<module>.test.autoconfigure` (e.g. `org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest`); `@AutoConfigureTestDatabase` is in `org.springframework.boot.jdbc.test.autoconfigure`. Let the IDE import them. Use the matching test starter (`spring-boot-starter-data-jpa-test`, and so on) so each slice is on the classpath.
24
+
25
+ ## Mocking beans
26
+
27
+ The Boot-era mock-bean annotations are gone in Boot 4. Use `@MockitoBean` and `@MockitoSpyBean` (from `org.springframework.test.context.bean.override.mockito`).
28
+
29
+ ```java
30
+ @WebMvcTest(UserController.class)
31
+ class UserControllerTests {
32
+ @Autowired MockMvcTester mvc;
33
+ @MockitoBean UserService users;
34
+
35
+ @Test
36
+ void unknownUserIs404() {
37
+ given(users.find("x")).willThrow(new UserNotFoundException("x"));
38
+ assertThat(mvc.get().uri("/users/x")).hasStatus(HttpStatus.NOT_FOUND);
39
+ }
40
+ }
41
+ ```
42
+
43
+ `MockMvcTester` is AssertJ-based (`assertThat(mvc.get().uri(...)).hasStatusOk()`); do not mix it with the `expectStatus()` chain, which belongs to the other clients. Security: `@WithMockUser` still works on slices; `@Import` your security config.
44
+
45
+ ## Full-server test
46
+
47
+ ```java
48
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
49
+ @AutoConfigureRestTestClient
50
+ class UserApiTests {
51
+ @Autowired RestTestClient client;
52
+
53
+ @Test
54
+ void createReturns201() {
55
+ client.post().uri("/users").contentType(MediaType.APPLICATION_JSON)
56
+ .body(new UserRequest("a@example.com"))
57
+ .exchange()
58
+ .expectStatus().isCreated();
59
+ }
60
+ }
61
+ ```
62
+
63
+ `RestTestClient` resolves relative URIs against the running server; it also supports AssertJ-style assertions through its response type. Customize through a `RestTestClientBuilderCustomizer` bean.
64
+
65
+ ## Real dependencies with Testcontainers
66
+
67
+ Prefer `@ServiceConnection`: Boot derives connection details, no property wiring.
68
+
69
+ ```java
70
+ @TestConfiguration(proxyBeanMethods = false)
71
+ class ContainersConfig {
72
+ @Bean @ServiceConnection
73
+ PostgreSQLContainer postgres() { return new PostgreSQLContainer("postgres:16"); }
74
+ }
75
+
76
+ @DataJpaTest
77
+ @Import(ContainersConfig.class)
78
+ class AuthorRepositoryTests { /* ... */ }
79
+ ```
80
+
81
+ - Testcontainers 2 containers are non-generic and live in per-module packages (`org.testcontainers.postgresql.PostgreSQLContainer`, artifact `testcontainers-postgresql`).
82
+ - The default `replace = NON_TEST` already keeps a `@ServiceConnection` data source; `replace = NONE` is only needed for a non-test `DataSource` you defined yourself.
83
+ - Where `@ServiceConnection` has no support for the container, use `@Testcontainers` + `@Container static` + `@DynamicPropertySource` to register the properties.
84
+ - Share one container configuration across tests so the context cache is reused.
85
+
86
+ ## Rules
87
+
88
+ - Test behaviour through the public seam; do not assert on mock call counts unless the call is the behaviour.
89
+ - Slice tests load only what they need; reaching for `@SpringBootTest` to test one controller is a smell.
90
+ - Never replace the database engine with H2 when production runs another one; dialect differences hide bugs.
91
+ - No `Thread.sleep`: wait on the condition with a bounded timeout.
92
+
93
+ <!-- INCLUDE: skills/_self-improvement.md -->
@@ -2,6 +2,8 @@
2
2
 
3
3
  > {{DESC_WAYFINDER}}
4
4
 
5
+ {{CREDIT:mattpocock-skills:skills/engineering/wayfinder/SKILL.md}}
6
+
5
7
  **Trigger:** a piece of work too large for one session and still in the fog —
6
8
  the destination can be named but the route to it cannot; the user says
7
9
  "wayfinder" or asks to chart a big effort before building anything.
@@ -27,7 +29,8 @@ the destination can be named but the route to it cannot; the user says
27
29
  `wayfinder:<type>` label (or a `Type:` line), which names how it resolves:
28
30
  - **research** (agent alone) — a fact the decision waits on, from docs, an API or
29
31
  the web: a subagent runs the [research {{SKILL}}](research.md). Fire every research
30
- ticket in parallel as soon as the map is charted.
32
+ ticket in parallel as soon as the map is charted. Then stop: charting is one session's work and
33
+ resolves no ticket itself.
31
34
  - **prototype** (with the user) — a rough artifact to react to when "how should it
32
35
  look or behave" is the question: the [prototype {{SKILL}}](prototype.md).
33
36
  - **grilling** (with the user, the default) — an interview through the
@@ -45,7 +48,10 @@ the destination can be named but the route to it cannot; the user says
45
48
  `Status: claimed`) so concurrent sessions skip it. Resolve it as its type says — read
46
49
  the label, the body never states it — write the decision into the ticket,
47
50
  gist-and-link it on the map, close it. At most one ticket per session (research
48
- tickets excepted). New questions become tickets.
51
+ tickets excepted). New questions become tickets; fog the answer made specifiable
52
+ graduates to tickets and leaves **Not yet specified**; a ticket the answer invalidates
53
+ is updated or closed, and one now past the destination is closed with an **Out of
54
+ scope** line.
49
55
  5. In everything the user reads, refer to the map and tickets **by name**,
50
56
  never by bare issue number — the id rides inside the name's link.
51
57
  6. Stop when the frontier is empty: nothing left to decide means the way is