@niroai/niro 0.16.0 → 0.18.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 (177) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +123 -302
  3. package/bin/niro.js +12 -1
  4. package/docs/agent-antigravity.md +37 -0
  5. package/docs/agent-claude-code.md +48 -0
  6. package/docs/agent-claude-desktop.md +47 -0
  7. package/docs/agent-cline.md +42 -0
  8. package/docs/agent-codex.md +45 -0
  9. package/docs/agent-copilot-cli.md +42 -0
  10. package/docs/agent-cursor.md +44 -0
  11. package/docs/agent-factory.md +42 -0
  12. package/docs/agent-gemini-cli.md +42 -0
  13. package/docs/agent-goose.md +42 -0
  14. package/docs/agent-kiro.md +37 -0
  15. package/docs/agent-opencode.md +37 -0
  16. package/docs/agent-vscode.md +45 -0
  17. package/docs/agent-windsurf.md +38 -0
  18. package/docs/answer-shapes.md +69 -0
  19. package/docs/builds-and-updates.md +68 -0
  20. package/docs/cloud-README.md +372 -0
  21. package/docs/configuring-a-folder.md +51 -0
  22. package/docs/connect-ci.md +24 -0
  23. package/docs/connect-overview.md +20 -0
  24. package/docs/connect-review-bots.md +17 -0
  25. package/docs/connect-team-server.md +12 -0
  26. package/docs/connect-terminal.md +49 -0
  27. package/docs/connect-web-agents.md +9 -0
  28. package/docs/connect-your-ai-assistant.md +59 -175
  29. package/docs/coverage-audit-and-consent.md +51 -0
  30. package/docs/evaluate-what-niro-did.md +271 -0
  31. package/docs/faq-assistants-and-mcp.md +122 -0
  32. package/docs/faq-feedback-and-receipts.md +33 -0
  33. package/docs/faq-indexing.md +127 -0
  34. package/docs/faq-install-and-setup.md +103 -0
  35. package/docs/faq-licence-and-plans.md +86 -0
  36. package/docs/faq-login-and-account.md +69 -0
  37. package/docs/faq-offline.md +34 -0
  38. package/docs/faq-performance.md +33 -0
  39. package/docs/faq-privacy-and-security.md +113 -0
  40. package/docs/faq-uninstall.md +47 -0
  41. package/docs/faq-updates.md +59 -0
  42. package/docs/faq-worktrees-and-branches.md +59 -0
  43. package/docs/faq-wrong-or-missing-answers.md +275 -0
  44. package/docs/hosted-only.md +33 -0
  45. package/docs/how-the-graph-works.md +47 -0
  46. package/docs/index.md +157 -0
  47. package/docs/licence-and-plans.md +72 -0
  48. package/docs/lifecycle-debug.md +48 -0
  49. package/docs/lifecycle-onboarding.md +32 -0
  50. package/docs/lifecycle-overview.md +77 -0
  51. package/docs/lifecycle-plan-and-design.md +44 -0
  52. package/docs/lifecycle-review.md +30 -0
  53. package/docs/lifecycle-security-and-privacy.md +35 -0
  54. package/docs/lifecycle-test.md +28 -0
  55. package/docs/lifecycle-write-code.md +35 -0
  56. package/docs/llms-full.txt +8015 -0
  57. package/docs/llms.txt +126 -0
  58. package/docs/playbook-add-a-feature.md +108 -0
  59. package/docs/playbook-change-safely.md +93 -0
  60. package/docs/playbook-data-and-events.md +107 -0
  61. package/docs/playbook-debug-after-a-change.md +96 -0
  62. package/docs/playbook-debug-config.md +95 -0
  63. package/docs/playbook-debug-exception.md +124 -0
  64. package/docs/playbook-debug-failing-service-call.md +119 -0
  65. package/docs/playbook-debug-slow-path.md +80 -0
  66. package/docs/playbook-debug-wrong-value.md +137 -0
  67. package/docs/playbook-onboard.md +95 -0
  68. package/docs/playbook-review-a-change.md +72 -0
  69. package/docs/playbook-test-a-change.md +73 -0
  70. package/docs/reference-cli.md +113 -0
  71. package/docs/reference-config-files.md +42 -0
  72. package/docs/reference-engine-commands.md +56 -0
  73. package/docs/reference-environment-variables.md +89 -0
  74. package/docs/reference-exit-codes.md +67 -0
  75. package/docs/reference-limits.md +75 -0
  76. package/docs/reference-mcp-tools.md +326 -0
  77. package/docs/reference-messages.md +180 -0
  78. package/docs/secrets-and-masking.md +48 -0
  79. package/docs/supported-c.md +130 -0
  80. package/docs/supported-cpp.md +157 -0
  81. package/docs/supported-csharp.md +211 -0
  82. package/docs/supported-go.md +258 -0
  83. package/docs/supported-java.md +226 -0
  84. package/docs/supported-javascript.md +223 -0
  85. package/docs/supported-kotlin.md +124 -0
  86. package/docs/supported-overview.md +89 -0
  87. package/docs/supported-php.md +157 -0
  88. package/docs/supported-python.md +224 -0
  89. package/docs/supported-ruby.md +162 -0
  90. package/docs/supported-rust.md +150 -0
  91. package/docs/supported-swift.md +133 -0
  92. package/docs/supported-typescript.md +220 -0
  93. package/docs/tokens-speed-and-accuracy.md +99 -0
  94. package/docs/troubleshooting.md +42 -0
  95. package/docs/uninstall.md +74 -0
  96. package/docs/what-leaves-your-machine.md +71 -0
  97. package/docs/where-things-are-stored.md +59 -0
  98. package/docs/worktrees-and-branches.md +55 -0
  99. package/package.json +5 -3
  100. package/src/commands/add-repo.js +5 -0
  101. package/src/commands/auditSend.js +67 -0
  102. package/src/commands/build.js +6 -0
  103. package/src/commands/docsSend.js +73 -0
  104. package/src/commands/engine.js +120 -0
  105. package/src/commands/engineRoutes.js +105 -0
  106. package/src/commands/guide.js +254 -0
  107. package/src/commands/init.js +16 -7
  108. package/src/commands/initLocal.js +96 -0
  109. package/src/commands/journalSend.js +47 -0
  110. package/src/commands/login.js +89 -12
  111. package/src/commands/mcp.js +22 -6
  112. package/src/commands/plan.js +99 -0
  113. package/src/commands/repoPush.js +33 -0
  114. package/src/commands/requestIntegration.js +38 -0
  115. package/src/commands/share.js +56 -0
  116. package/src/commands/start.js +451 -371
  117. package/src/commands/status.js +57 -0
  118. package/src/commands/takeover.js +6 -0
  119. package/src/commands/uninstall.js +180 -0
  120. package/src/commands/upgrade.js +38 -8
  121. package/src/commands/watch.js +420 -6
  122. package/src/index.js +174 -3
  123. package/src/lib/agentApi.js +32 -1
  124. package/src/lib/api.js +20 -6
  125. package/src/lib/auditUpload.js +293 -0
  126. package/src/lib/branchDrift.js +149 -0
  127. package/src/lib/choose.js +116 -0
  128. package/src/lib/cloudGuard.js +74 -0
  129. package/src/lib/credentials.js +2 -2
  130. package/src/lib/docsMarked.js +55 -0
  131. package/src/lib/docsState.js +213 -0
  132. package/src/lib/docsUpload.js +481 -0
  133. package/src/lib/engine.js +619 -0
  134. package/src/lib/folderSync.js +2 -1
  135. package/src/lib/graphCheck.js +40 -0
  136. package/src/lib/guide.js +293 -0
  137. package/src/lib/guideSearch.js +252 -0
  138. package/src/lib/integrationRequest.js +89 -0
  139. package/src/lib/journalUpload.js +147 -0
  140. package/src/lib/languages.js +129 -0
  141. package/src/lib/lease.js +375 -0
  142. package/src/lib/localBuild.js +285 -0
  143. package/src/lib/localEngine.js +210 -0
  144. package/src/lib/localOnboard.js +124 -0
  145. package/src/lib/localWorktrees.js +243 -0
  146. package/src/lib/machineCheck.js +58 -0
  147. package/src/lib/mcpClients.js +10 -1
  148. package/src/lib/migrationNotice.js +118 -0
  149. package/src/lib/modelFetch.js +153 -0
  150. package/src/lib/nestedOutput.js +23 -9
  151. package/src/lib/onboardingLog.js +34 -0
  152. package/src/lib/planUsage.js +165 -0
  153. package/src/lib/progressBar.js +46 -0
  154. package/src/lib/prompt.js +1 -1
  155. package/src/lib/redact.js +155 -0
  156. package/src/lib/release.js +33 -0
  157. package/src/lib/repoPush.js +495 -0
  158. package/src/lib/secret.js +1 -1
  159. package/src/lib/service.js +372 -63
  160. package/src/lib/shareCode.js +367 -0
  161. package/src/lib/shareExclusions.js +114 -0
  162. package/src/lib/shareFiles.js +129 -0
  163. package/src/lib/spinner.js +9 -5
  164. package/src/lib/watchDaemon.js +33 -10
  165. package/src/lib/watchState.js +12 -1
  166. package/src/lib/windowsPaths.js +67 -0
  167. package/src/mcp/lineCheck.js +382 -0
  168. package/src/mcp/lineName.js +92 -0
  169. package/src/mcp/localBackend.js +751 -0
  170. package/src/mcp/localMode.js +383 -0
  171. package/src/mcp/localSkill.js +189 -0
  172. package/src/mcp/server.js +610 -79
  173. package/src/mcp/setup.js +519 -131
  174. package/src/mcp/shellSearchHook.js +509 -0
  175. package/src/mcp/teardown.js +173 -91
  176. package/src/mcp/usingNiro.js +39 -0
  177. package/docs/working-on-branches.md +0 -162
@@ -0,0 +1,124 @@
1
+ # Playbook: debug an error message or a stack trace
2
+
3
+ **The question, in the user's words:** "Our server logs `Multiple users with email 'x' exist in Keycloak.` Where does that come from, what leads to it, and who catches it?"
4
+
5
+ **What you get at the end:** the method that throws it, the exception type, every method that calls the thrower, and which callers catch it or turn it into an HTTP error. Every step below was run on Keycloak, a public Java project, with the release engine.
6
+ <!-- source: every output on this page: niro-local 43cce717 (the engine niro-cli installs), run as `niro-local ask` from /home/niraj/projects/github/keycloak at 968726fe66, scratch home, 2026-11-10; `niro ask` passes the same words to the same engine command (src/commands/engineRoutes.js:28, :90-101). Saved answers: scratchpad niro-cli-task-playbooks/runs/exc-*.txt -->
7
+
8
+ Words used on this page: an **exception** is an error a method throws; a **caller** is a method that calls another; a **stack trace** is the list of calls that were running when the error happened.
9
+
10
+ ## The steps
11
+
12
+ ### 1. Find the line that writes the message
13
+
14
+ ```
15
+ niro ask code_grep pattern="exist in Keycloak" path_pattern=model/ answer=locations
16
+
17
+ 1 matching line in 1 file
18
+ model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java:1183 org.keycloak.models.jpa.JpaUserProvider.ensureEmailConstraint_NbykxN
19
+ [shown all 1 matching line; ask without answer= for the whole lines]
20
+ ```
21
+ <!-- source: runs/exc-10-msg.txt (239 characters) -->
22
+
23
+ `answer=locations` gives the place and the exact id of the method that holds it, which the next step takes as it is. Use the fixed part of the message ("exist in Keycloak"), not the part that changes ("x").
24
+
25
+ **If you have a stack trace instead,** its top line already names the method and the line (`at ...JpaUserProvider.ensureEmailConstraint(JpaUserProvider.java:1183)`). Skip to step 2 with that name.
26
+
27
+ ### 2. Read only that method
28
+
29
+ ```
30
+ niro ask get_source_code fqn=org.keycloak.models.jpa.JpaUserProvider.ensureEmailConstraint
31
+
32
+ 1177: protected void ensureEmailConstraint(List<UserEntity> users, RealmModel realm) {
33
+ 1178: UserEntity user = users.get(0);
34
+ 1180: if (users.size() > 1) {
35
+ 1183: throw new ModelDuplicateException("Multiple users with email '" + user.getEmail() + "' exist in Keycloak.");
36
+ ...
37
+ 1196: }
38
+ ```
39
+ <!-- source: runs/exc-09-src.txt (20 lines, 1,035 characters); the file is 1,369 lines and 63,528 characters (wc on JpaUserProvider.java) -->
40
+
41
+ So the exception type is `ModelDuplicateException`, thrown when more than one user row has that email.
42
+
43
+ ### 3. Who calls the thrower, and does anyone catch it on the way?
44
+
45
+ ```
46
+ niro ask get_exception_flow method_fqn=org.keycloak.models.jpa.JpaUserProvider.ensureEmailConstraint direction=upstream exception_type=ModelDuplicateException
47
+
48
+ Callers of JpaUserProvider.ensureEmailConstraint reached while tracing ModelDuplicateException — 1 caller, 0 catch it.
49
+ ...
50
+ JpaUserProvider.getUserByEmail (line 704)
51
+ ```
52
+ <!-- source: runs/exc-08-upstream.txt (764 characters) -->
53
+
54
+ ### 4. Who calls `getUserByEmail`?
55
+
56
+ Ask for the callers as places, not as full entries:
57
+
58
+ ```
59
+ niro ask get_reverse_call_chain method_fqn=org.keycloak.models.UserProvider.getUserByEmail answer=locations
60
+
61
+ UserProvider.getUserByEmail has 17 callers.
62
+ confidence: complete | has_more: false — this is the COMPLETE caller chain. Use it directly.
63
+ services/src/main/java/org/keycloak/userprofile/validator/DuplicateEmailValidator.java:71 ...DuplicateEmailValidator.validate_NwtfyN
64
+ services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java:82 ...UsersPartialImport.userEmailExists_NuvfpN
65
+ server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java:331 ...KeycloakModelUtils.findUserByNameOrEmail_Nh6f5N
66
+ ... 14 more
67
+ [shown all 17 callers; ask without answer= for signatures and code]
68
+ ```
69
+ <!-- source: runs/chg-07-iface-loc.txt (17 callers, 3,244 characters) -->
70
+
71
+ **Ask the name the callers use.** The callers write `session.users().getUserByEmail(...)`, and `users()` returns the `UserProvider` interface. In this release, asking the JPA class (`JpaUserProvider.getUserByEmail`) answers "0 callers", because the method is declared on a parent interface of `UserProvider`. This is a known problem, see [faq-wrong-or-missing-answers.md](faq-wrong-or-missing-answers.md).
72
+ <!-- source: runs/chg-03-impl-full.txt ("has 0 callers"), runs/chg-06-decl-count.txt; niro-local docs/NIRO-LOCAL-ISSUES.md entry "2026-11-10: a call to an interface method inherited from a parent interface lands on a stand-in, so the declaring method and its implementations read '0 callers, complete'" (OPEN, committed f1a9fe59); keycloak server-spi/.../UserProvider.java:33-37 (extends UserLookupProvider) -->
73
+
74
+ ### 5. Where is it turned into an HTTP error?
75
+
76
+ For a REST method you suspect, ask what escapes it and what it catches:
77
+
78
+ ```
79
+ niro ask get_exception_flow method_fqn=org.keycloak.services.resources.admin.UsersResource.createUser
80
+
81
+ Exceptions escaping UsersResource.createUser - 3 types, 2 swallow candidates, 6 wrapped.
82
+ ...
83
+ Wrapped (caught and thrown on as a new exception of another type ...):
84
+ ModelDuplicateException
85
+ caught at UsersResource.createUser
86
+ ...
87
+ requires_runtime: yes - treat the escaping set as "at least these", never as "exactly these".
88
+ ```
89
+ <!-- source: runs/exc-04-flow.txt (3,682 characters) -->
90
+
91
+ The code at that catch is `throw ErrorResponse.exists("User exists with same username or email");`, which a `code_grep` for the message confirms at `UsersResource.java:180`.
92
+ <!-- source: runs/exc-02-grep-loc.txt (UsersResource.java:180 createUser); keycloak services/.../UsersResource.java:179-180 -->
93
+
94
+ ## Why the answer is complete, and where it is not
95
+
96
+ - **The caller lists are complete for calls the code states.** "confidence: complete" means every call to that name in indexed code was placed. Calls made through reflection or built at run time are not in any list.
97
+ <!-- source: docs/how-the-graph-works.md "What it cannot see"; niro-local src/niro_local/mcp/chains.py:1945-1947 -->
98
+ - **The exception list is a floor, and says so.** It sees `throw new` and declared `throws` in your code. It does not see what a library throws, so the answer says "requires_runtime: yes".
99
+ <!-- source: runs/exc-04-flow.txt ("scope: declared throws plus `throw new` ... which is a FLOOR") -->
100
+ - **The graph calls in steps 3, 4 and 5 give the same answer every time** on the same graph, because they are read from it, not guessed. The `code_grep` check of the catch line in step 5 is a text search with a time limit, so on a busy machine follow its `continue_from`. A few answers can change between two builds of the same code (a known problem).
101
+ - **The text search in step 1 can stop early on a busy machine.** `code_grep` has a time limit, and then says PARTIAL with a `continue_from` to carry on. Follow it before you decide a message is not in the code.
102
+ <!-- source: niro-local docs/NIRO-LOCAL-ISSUES.md entry "2026-11-10: code_grep counts change with machine load, and a busy machine can give '0 matching lines ... PARTIAL'" (OPEN, committed f1a9fe59); scratchpad niro-cli-task-playbooks-check/reruns (claim check round 1: 59 of 59 graph-tool reruns byte-identical, 7 code_grep reruns differed under load 17 to 30) -->
103
+ <!-- source: docs/faq-wrong-or-missing-answers.md "Answers changed between two builds of the same code" (niro-local docs/NIRO-LOCAL-ISSUES.md entry "2026-10-30, three answers change between two builds of the same code", OPEN) -->
104
+
105
+ ## What grep and file reads would do instead
106
+
107
+ | Step | With grep | What it misses or costs |
108
+ |---|---|---|
109
+ | 1 | `grep -rn "exist in Keycloak"` finds the line too | nothing: a plain text search is the right tool here |
110
+ | 3 to 4 | `grep -rn "getUserByEmail("` gives 46 lines in 35 files | at least 15 of those lines are other classes' own `getUserByEmail` methods, not calls. grep cannot tell which `getUserByEmail` a call reaches, because the call site names only `session.users()` |
111
+ | 5 | `grep -rn "catch (ModelDuplicateException"` gives 34 lines in 29 files | it cannot say which of them are on the path from the thrower, so each must be read |
112
+ <!-- source: grep counts on Keycloak 968726fe66, 2026-11-10: `grep -rn --include=*.java "getUserByEmail(" .` 46 lines (35 files); `"public UserModel getUserByEmail("` 15 lines; `"catch (ModelDuplicateException"` 34 lines in 29 files -->
113
+
114
+ **An everyday comparison:** grep is like searching a phone book for everyone called "Smith". The graph is like asking the exchange who actually rang this number.
115
+
116
+ ## Cost
117
+
118
+ What these steps cost depends on your code and your question. How Niro counts the size of its answers, and how to measure the cost on your own tasks, is in [tokens-speed-and-accuracy.md](tokens-speed-and-accuracy.md).
119
+
120
+ ## Related
121
+
122
+ - Exceptions in general, and the `get_exception_flow` limits: [lifecycle-debug.md](lifecycle-debug.md).
123
+ - Asking for short answers: [answer-shapes.md](answer-shapes.md).
124
+ - Other debugging playbooks: [a wrong value](playbook-debug-wrong-value.md), [a failing call between services](playbook-debug-failing-service-call.md), [it broke after my change](playbook-debug-after-a-change.md), [config and environment](playbook-debug-config.md), [a slow path](playbook-debug-slow-path.md).
@@ -0,0 +1,119 @@
1
+ # Playbook: debug a failing call between services
2
+
3
+ **The question, in the user's words:** "The POST to `/api/shipments` from our main service is failing. Which code makes the call, which service is meant to answer it, and why does Niro think so?"
4
+
5
+ **What you get at the end:** the calling method, the service folder and method that handle the route, and what in the repository ties the address to that folder. Then, for a call that two services could answer, how to settle which one.
6
+ <!-- source: every output on this page: niro-local 43cce717, run as `niro-local ask` from /home/niraj/projects/github/niro-coverage-corpus/java-mono, scratch home, 2026-11-10 (scratchpad niro-cli-task-playbooks/runs/xs-*.txt); the corpus is synthetic (niro-coverage-corpus/README.md); `niro ask` runs the same engine command (src/commands/engineRoutes.js:28) -->
7
+
8
+ Words used on this page: a **route** is a web address a service answers, such as `POST /api/shipments`; a **handler** is the method that answers it; an **outbound call** is a call your code makes to another service's route.
9
+
10
+ ## The steps
11
+
12
+ ### 1. Find the call
13
+
14
+ ```
15
+ niro ask find_outbound_http_calls path_pattern=/api/shipments
16
+
17
+ Found 2 outbound HTTP calls matching '/api/shipments'.
18
+ 1. POST /api/shipments -> com.example.shop.shipping.ShippingMain.a_f_Ng3hlN
19
+ caller: ApacheHttpClient5Calls.createShipment
20
+ source: .../java-mono/main/src/main/java/com/example/shop/main/http/ApacheHttpClient5Calls.java
21
+ 2. GET /api/shipments/{param} -> com.example.shop.shipping.ShippingMain.shipment_Np2ojN
22
+ caller: ApacheHttpClient5Calls.shipment
23
+ confidence: complete - this is the COMPLETE list.
24
+ ```
25
+ <!-- source: runs/xs-02-out.txt (651 characters) -->
26
+
27
+ Each line already says where the call lands. `a_f_...` is a function written in place: here a lambda, `(req, res) -> res.status(201).send()`.
28
+ <!-- source: java-mono services/helidon-se/.../ShippingMain.java:19-20 -->
29
+
30
+ ### 2. Who answers it?
31
+
32
+ ```
33
+ niro ask find_handler fqn=ApacheHttpClient5Calls.createShipment
34
+
35
+ Found 1 handler for 'ApacheHttpClient5Calls.createShipment'.
36
+ confidence: complete | has_more: false — this is the complete list.
37
+ 1. ShippingMain.a_f [helidon-se]
38
+ source: services/helidon-se/src/main/java/com/example/shop/shipping/ShippingMain.java
39
+ ```
40
+ <!-- source: runs/xs-04-handler.txt (254 characters) -->
41
+
42
+ ### 3. Why that service?
43
+
44
+ The caller dials `http://shipping-api:8109/api/shipments`. Nothing in the Java code says that `shipping-api` is the `helidon-se` folder. The deploy file does:
45
+
46
+ ```
47
+ # docker-compose.yml
48
+ shipping-api:
49
+ build: ./services/helidon-se
50
+ ports: ["8109:8109"]
51
+ ```
52
+ <!-- source: java-mono main/.../ApacheHttpClient5Calls.java:19; java-mono docker-compose.yml:45-47 -->
53
+
54
+ Niro reads deploy files like this one to give each folder its address, then joins the call to the route. See [configuring-a-folder.md](configuring-a-folder.md) to set an address by hand.
55
+ <!-- source: docs/configuring-a-folder.md (base address); niro-local src/niro_local/modules/compose_address.py:1-3 ("The address a compose file gives a module: `http://<service name>:<container port>`") -->
56
+
57
+ ### 4. The whole map, when you do not know where to start
58
+
59
+ ```
60
+ niro ask get_service_topology
61
+
62
+ 18 link(s) between parts of this project:
63
+ main -> helidon-se (2 route(s))
64
+ GET /api/shipments/{param}
65
+ POST /api/shipments
66
+ main -> javalin (3 route(s))
67
+ GET /api/stock/{param} (not confirmed)
68
+ ...
69
+ ```
70
+ <!-- source: runs/xs-01-topo.txt (2,624 characters) -->
71
+
72
+ ### 5. When two services declare the same route
73
+
74
+ "(not confirmed)" means the call was matched by its path alone, and more than one service declares that path. The engine lists every such call and the command that settles it:
75
+
76
+ ```
77
+ niro route-rule unconnected
78
+
79
+ 1 call(s) joined only as candidates: more than one route could be meant ...
80
+ java-mono/main GET /api/stock/{param}: 2 services declare it (java-mono/services/javalin, java-mono/services/sparkjava)
81
+ niro-local route-rule add '/api/stock/{param}' --from java-mono/main --to java-mono/services/javalin
82
+ 5 address(es) dialled that no service here answers on:
83
+ http://gift-cards-api:8113: 1 call(s) from java-mono/main; no service here declares their paths
84
+ ...
85
+ ```
86
+ <!-- source: `niro-local route-rule unconnected` in java-mono, 2026-11-10 (run before the rule below was added); `niro route-rule` passes to the engine (src/commands/engineRoutes.js:32) -->
87
+
88
+ Before the rule, `StockRetrofitApi.stock` was listed twice, once for each service, both "(one of several routes with this path, not confirmed)". After `niro route-rule add ...` and `niro build`, it is listed once, to the service named:
89
+
90
+ ```
91
+ Before: 4. GET /api/stock/{param} -> ...stockspark.StockSparkApp.a_f_N657yN (one of several routes with this path, not confirmed)
92
+ 5. GET /api/stock/{param} -> ...stock.StockApp.a_f_Nes1yN (one of several routes with this path, not confirmed)
93
+ After: 3. GET /api/stock/{param} -> com.example.shop.stock.StockApp.a_f_Nes1yN
94
+ ```
95
+ <!-- source: runs/xs-10-before.txt and runs/xs-11-after.txt (find_outbound_http_calls path_pattern=/api/stock), 2026-11-10; the rule was saved with "1 rule(s) saved. The next `niro-local build` applies them, with no reindexing." -->
96
+
97
+ **"No service here answers"** usually means the service lives in another repository that is not indexed, or its address is wrong. Check the address with `niro base-url` in the service's folder.
98
+ <!-- source: docs/configuring-a-folder.md; src/commands/engineRoutes.js:31 (base-url) -->
99
+
100
+ ## Why the answer is deterministic
101
+
102
+ The link between a call and a handler comes from three things Niro read: the call's method and path, the route a framework declares, and the address of each folder. The same files always give the same links. Where the path alone matched and two routes fit, the answer says "not confirmed" and keeps it out of the risk counts, rather than guessing.
103
+ <!-- source: docs/how-the-graph-works.md ("inferred, target unconfirmed" is listed separately and not counted in the risk); runs/xs-01-topo.txt note on 'not confirmed' -->
104
+
105
+ ## What grep would do instead
106
+
107
+ - **grep finds the caller's URL string,** `http://shipping-api:8109/api/shipments`: the host name is in 3 lines, all in one file of main (two calls and a comment).
108
+ - **It does not find the handler by that string.** The handler file has only `"/api/shipments"` next to a lambda, and the host name `shipping-api` is in the compose file, not in any Java file of helidon-se. So the agent has to read the compose file and match the name itself. An agent using Niro can miss that line too, so when a call names a host, check the compose file as well.
109
+ - **It cannot tell which of two services answers `/api/stock/{id}`.** Both declare it; only an address or a rule settles it.
110
+ - **For routes declared by folder names it finds nothing.** In the TypeScript corpus, `GET /api/bookings/{param}` comes from the folder path `src/app/api/bookings/[bookingId]/route.ts`. `grep -rn "api/bookings"` finds 0 lines; `find_api_endpoints path_pattern=/api/bookings` finds 2 routes.
111
+ <!-- source: `grep -rn "shipping-api" java-mono --include=*.java` = 3 lines, all in main/.../ApacheHttpClient5Calls.java (:10 comment, :15, :19); docker-compose.yml:45; `grep -rn "api/bookings" typescript-mono --include=*.ts --include=*.tsx` = 0 lines; runs/ts-01-routes.txt (2 endpoints), typescript-mono built 2026-11-10 -->
112
+
113
+ ## Cost
114
+
115
+ What these steps cost depends on your code and your question. How Niro counts the size of its answers, and how to measure the cost on your own tasks, is in [tokens-speed-and-accuracy.md](tokens-speed-and-accuracy.md).
116
+
117
+ ## Related
118
+
119
+ [playbook-debug-config.md](playbook-debug-config.md) (addresses that come from configuration), [playbook-onboard.md](playbook-onboard.md).
@@ -0,0 +1,80 @@
1
+ # Playbook: debug a slow path
2
+
3
+ **The question, in the user's words:** "Adding a credential to a user is slow. What does that code do, and where does it go to the database?"
4
+
5
+ **What you get at the end:** what the slow method calls, a few levels down, which of those touch a data store, and the few methods worth reading. Niro does not time anything: it tells you where to look, and a profiler tells you how long it takes.
6
+ <!-- source: outputs on this page: niro-local 43cce717 as `niro-local ask` from /home/niraj/projects/github/keycloak at 968726fe66, scratch home, 2026-11-10 (scratchpad niro-cli-task-playbooks/runs/slow-*.txt, data-02-usage.txt, exc-09-src.txt); `niro ask` runs the same engine command (src/commands/engineRoutes.js:28) -->
7
+
8
+ Words used on this page:
9
+ - a **callee** is a method that another method calls;
10
+ - a **profiler** is a tool that measures where a running program spends its time;
11
+ - a **named query** is a database query written once on a JPA entity class and run by its name (JPA is Java's standard library that maps classes to tables).
12
+
13
+ ## The steps
14
+
15
+ ### 1. How much does the method reach?
16
+
17
+ ```
18
+ niro ask get_call_chain method_fqn=org.keycloak.models.jpa.JpaUserCredentialStore.createCredentialEntity direction=callees scope=transitive transitive_depth=2 answer=count
19
+
20
+ 18 callees
21
+ confidence: complete | has_more: false — this is the COMPLETE call chain. Use it directly.
22
+ ```
23
+ <!-- source: runs/slow-01-count.txt (152 characters) -->
24
+
25
+ ### 2. Which callees, and where is each called?
26
+
27
+ The full answer gives the line of every call, which matters for a slow path: a call inside a loop, or the same query twice.
28
+
29
+ ```
30
+ niro ask get_call_chain method_fqn=org.keycloak.models.jpa.JpaUserCredentialStore.createCredentialEntity direction=callees
31
+
32
+ JpaUserCredentialStore.createCredentialEntity has 17 callees.
33
+ 1. JpaUserCredentialStore.validateDuplicateCredential(RealmModel, UserModel, String, String, String) (line 147) private — called at line 162 — .../JpaUserCredentialStore.java:147-159
34
+ ...
35
+ 16. JpaUserCredentialStore.getStoredCredentialEntities(RealmModel, UserModel) (line 123) private — called at line 175 — .../JpaUserCredentialStore.java:123-128
36
+ 17. CredentialEntity.setPriority(int) (line 144) public — called at line 177 — ...
37
+ ```
38
+ <!-- source: runs/slow-03-full.txt (3,890 characters) -->
39
+
40
+ Most of the 17 are getters and setters. Two are worth reading: `validateDuplicateCredential` and `getStoredCredentialEntities`.
41
+
42
+ ### 3. Read the two, and only the two
43
+
44
+ ```
45
+ niro ask get_source_code fqn=org.keycloak.models.jpa.JpaUserCredentialStore.getStoredCredentialEntities
46
+ ```
47
+
48
+ `getStoredCredentialEntities` runs the named query `credentialByUser`, which loads every credential of the user. `validateDuplicateCredential` calls it when the new credential has a label (line 149), and `createCredentialEntity` calls it again at line 175 to work out the priority. So adding a labelled credential loads the user's whole credential list twice.
49
+ <!-- source: keycloak model/jpa/.../JpaUserCredentialStore.java:123-128 (createNamedQuery("credentialByUser")), :147-159 (validateDuplicateCredential calls getStoredCredentialEntities at :149), :175; runs/slow-03-full.txt (called at line 175) -->
50
+
51
+ That is a lead, not a measurement. Confirm it with a profiler, or by logging the SQL the query runs.
52
+
53
+ ### 4. Which stores does the path touch?
54
+
55
+ ```
56
+ niro ask get_data_resource_usage resource_fqn=CREDENTIAL
57
+ ```
58
+
59
+ In this release the answer lists the `em.persist` and `em.find` calls but not the named query, so `getStoredCredentialEntities` is missing from its readers. That is a known gap; step 3 found the read by reading the method.
60
+ <!-- source: runs/data-02-usage.txt (no getStoredCredentialEntities); niro-local docs/NIRO-LOCAL-ISSUES.md entry "2026-11-10: a JPA named query (`em.createNamedQuery('name', Entity.class)`) is not a read of the entity's table" (OPEN, committed f1a9fe59) -->
61
+
62
+ ## Why the answer is complete, and where it is not
63
+
64
+ - **The callee list is complete for calls the code states,** with the line of each call. Library calls (`em.persist`) are not listed as callees of your code, because the library is not indexed.
65
+ - **Niro holds no timings.** A slow path found here is a place to measure, not a measured fact.
66
+ - **The `answer=names` form of a deeper walk can show a direct callee at depth 2** when another direct callee also calls it (a known display problem). The count is right; use the full answer when the depth matters.
67
+ <!-- source: runs/slow-02-names.txt (getStoredCredentialEntities "(depth 2)") against runs/slow-03-full.txt ("called at line 175", a direct callee); niro-local docs/NIRO-LOCAL-ISSUES.md entry "2026-11-10: `get_call_chain ... scope=transitive answer=names` labels a direct callee '(depth 2)' when a direct callee also calls it" (OPEN, committed f1a9fe59) -->
68
+
69
+ ## What grep would do instead
70
+
71
+ To list what a method calls, grep has nothing to search for: the callees are inside the method. The by-hand route is to read the method, then each method it calls. Here that is the 21-line method plus two private methods in the same file, which is cheap. The graph pays off when the path crosses files and interfaces, as in [playbook-change-safely.md](playbook-change-safely.md).
72
+ <!-- source: runs/slow-03-full.txt (createCredentialEntity :161-181, validateDuplicateCredential :147-159, getStoredCredentialEntities :123-128, all in one file) -->
73
+
74
+ ## Cost
75
+
76
+ For a short method, reading the whole method by hand can cost less than asking Niro. This playbook is about finding the methods worth a closer look, not about tokens. What these steps cost depends on your code and your question. How Niro counts the size of its answers, and how to measure the cost on your own tasks, is in [tokens-speed-and-accuracy.md](tokens-speed-and-accuracy.md).
77
+
78
+ ## Related
79
+
80
+ [playbook-debug-wrong-value.md](playbook-debug-wrong-value.md), [playbook-data-and-events.md](playbook-data-and-events.md).
@@ -0,0 +1,137 @@
1
+ # Playbook: debug a wrong value
2
+
3
+ **The question, in the user's words:** "A user's credentials come back in the wrong order. The `PRIORITY` column of the `CREDENTIAL` table looks wrong. What writes it?"
4
+
5
+ **What you get at the end:** every method that reads, writes or deletes the table, the methods that set the field, and the routes and callers that reach them. Then, for a value that arrives as a message, who publishes it and who handles it.
6
+ <!-- source: every Keycloak output on this page: niro-local 43cce717, run as `niro-local ask` from /home/niraj/projects/github/keycloak at 968726fe66, scratch home, 2026-11-10 (scratchpad niro-cli-task-playbooks/runs/data-*.txt, wv-*.txt); `niro ask` runs the same engine command (src/commands/engineRoutes.js:28) -->
7
+
8
+ Words used on this page:
9
+ - a **data store** is a table, collection, cache or queue;
10
+ - an **ORM** is a library that maps classes to tables, so the code saves an object and never writes the table's name;
11
+ - **JPA** is Java's standard ORM, and its **EntityManager** is the object code calls to load, save and delete rows (`em.find`, `em.persist`, `em.remove`);
12
+ - a **loaded entity** is an object JPA has read from a row: changing its fields changes the row when the **transaction** (the unit of work JPA saves in one go) ends, with no further call;
13
+ - a **named query** is a query written once on the entity class and run by its name (`em.createNamedQuery("credentialByUser", ...)`).
14
+
15
+ ## The steps
16
+
17
+ ### 1. Which stores does the code use?
18
+
19
+ ```
20
+ niro ask list_data_resources limit=15
21
+
22
+ 67 data resource(s):
23
+ sql table USER_ENTITY 20 use(s)
24
+ sql table REALM 10 use(s)
25
+ ...
26
+ sql table CREDENTIAL 7 use(s)
27
+ ```
28
+ <!-- source: runs/data-01-list.txt -->
29
+
30
+ ### 2. Who reads, writes and deletes that table?
31
+
32
+ ```
33
+ niro ask get_data_resource_usage resource_fqn=CREDENTIAL
34
+
35
+ sql:table:CREDENTIAL
36
+ read by (5):
37
+ JpaUserCredentialStore.getStoredCredentialById
38
+ JpaUserCredentialStore.removeCredentialEntity
39
+ JpaUserCredentialStore.updateCredential
40
+ CredentialModelTest.testCredentialUpdateWithDuplicateLabel
41
+ PasswordHashingTest.testPasswordRehashedOnAlgorithmChangedWithMigratedSalt
42
+ written by (1):
43
+ JpaUserCredentialStore.createCredentialEntity
44
+ deleted by (1):
45
+ JpaUserCredentialStore.removeCredentialEntity
46
+ ```
47
+ <!-- source: runs/data-02-usage.txt (460 characters) -->
48
+
49
+ The listed write is `em.persist(entity)` on a `CredentialEntity`, and the table name appears only once in the code, in `@Table(name="CREDENTIAL")` on the entity class. Niro joins the two.
50
+
51
+ **This list is not complete, and here is what it leaves out.** The answer key for this same question (A/B task p-04) has four methods that write the table, and step 2 shows one insert and one delete:
52
+ - `updateCredential` loads a row with `em.find` and changes its fields, so it writes the row; Niro lists it only under "read by".
53
+ - `moveCredentialTo` changes the priority of rows it loaded earlier, and is not listed at all.
54
+ - `getStoredCredentialEntities` reads the table through a named query, and is not listed either.
55
+
56
+ Both kinds of gap are known problems in this release: see [faq-wrong-or-missing-answers.md](faq-wrong-or-missing-answers.md). Use step 3 to find them.
57
+ <!-- source: keycloak model/jpa/.../JpaUserCredentialStore.java:179 (em.persist), :63-75 (updateCredential: em.find then setters), :245 (moveCredentialTo sets priority), :123-128 (getStoredCredentialEntities, createNamedQuery); model/jpa/.../entities/CredentialEntity.java:43 (@Table); scratchpad niro-cli-task-playbooks/ab/tasks.yaml p-04 (4 writers); niro-local docs/NIRO-LOCAL-ISSUES.md entry "2026-11-10: a JPA named query (`em.createNamedQuery("name", Entity.class)`) is not a read of the entity's table" (OPEN, committed f1a9fe59) -->
58
+
59
+ ### 3. Who sets the field?
60
+
61
+ The graph records which methods touch a table, not which fields they change. So search for the setter, and ask for the methods that hold it:
62
+ <!-- source: niro-local server instructions: "SQL no query call runs, config and reads or writes of a field are not" (src/niro_local/mcp/server.py, the instructions text) -->
63
+
64
+ ```
65
+ niro ask code_grep pattern="setPriority(" path_pattern=model/jpa/ answer=names
66
+
67
+ 18 matching lines in 9 files
68
+ JpaUserCredentialStore.createCredentialEntity(RealmModel, UserModel, CredentialModel)
69
+ JpaUserCredentialStore.removeCredentialEntity(RealmModel, UserModel, String)
70
+ JpaUserCredentialStore.moveCredentialTo(RealmModel, UserModel, String, String)
71
+ RealmAdapter.entityToModel(AuthenticationExecutionEntity)
72
+ ...
73
+ [shown 17 names from the first 5 hits a file; raise max_matches_per_file (up to 50) for more hits a file]
74
+ ```
75
+ <!-- source: runs/wv-01-setprio.txt (1,424 characters) -->
76
+
77
+ Three methods of `JpaUserCredentialStore` set a credential's priority. `moveCredentialTo` is not in step 2's list, and `updateCredential` is listed only as a reader. Both change loaded entities, and the database update happens when the transaction ends. That kind of write has no call Niro can see.
78
+ <!-- source: runs/data-02-usage.txt (moveCredentialTo absent, updateCredential under read by); keycloak JpaUserCredentialStore.java:63-75, (credential.setPriority(expectedPriority) inside moveCredentialTo) -->
79
+
80
+ ### 4. What reaches the suspect method?
81
+
82
+ ```
83
+ niro ask get_impact_analysis method_fqn=org.keycloak.models.jpa.JpaUserCredentialStore.moveCredentialTo depth=5 answer=names
84
+
85
+ Impact analysis for JpaUserCredentialStore.moveCredentialTo — 5 affected functions, risk: medium (+2 in tests, not counted in risk) (upstream 7)
86
+ upstream:
87
+ JpaUserProvider.moveCredentialTo (depth 1)
88
+ UserCredentialManager.moveStoredCredentialTo (depth 2)
89
+ SubjectCredentialManagerCacheAdapter.moveStoredCredentialTo (depth 3)
90
+ UserResource.moveCredentialAfter (depth 4)
91
+ UserResource.moveCredentialToFirst (depth 5)
92
+ CredentialModelTest.testCredentialCRUD [test] (depth 4)
93
+ UserStorageTest.testCredentialCRUD [test] (depth 4)
94
+ ```
95
+ <!-- source: runs/wv-03-impact.txt (1,058 characters) -->
96
+
97
+ So the admin REST methods `UserResource.moveCredentialAfter` and `moveCredentialToFirst` reorder credentials, and two existing tests already reach the code. The path goes through `SubjectCredentialManagerCacheAdapter`, a class that implements an interface: the call site names only the interface.
98
+
99
+ ### 5. When the value arrives as a message
100
+
101
+ If the wrong value comes from another service, ask who sends and who receives it. From the synthetic java-mono corpus (68 small Java services and a `main` that calls them):
102
+ <!-- source: /home/niraj/projects/github/niro-coverage-corpus/README.md (synthetic code, made-up names); java-mono built with niro-local 43cce717, 2026-11-10 -->
103
+
104
+ ```
105
+ niro ask get_event_producers_and_handlers name=price-changes
106
+
107
+ redis-pubsub:price-changes [jedis]
108
+ published by (1):
109
+ BrokerClients.priceChanged
110
+ handled by (1):
111
+ PriceWatch.listen
112
+ ```
113
+ <!-- source: runs/wv-10-ev.txt -->
114
+
115
+ Then `get_blast_radius event_fqn=price-changes answer=names` lists what the handler goes on to call. For events in general, see [playbook-data-and-events.md](playbook-data-and-events.md).
116
+ <!-- source: runs/wv-11-evblast.txt -->
117
+
118
+ ## Why the answer is complete, and where it is not
119
+
120
+ - **Step 2 lists the calls Niro recognises** (here `em.persist`, `em.find` and `em.remove`), including access through an ORM where the table is never named. It is not complete: writes made by changing a loaded entity, and reads through a named query, are missing in this release.
121
+ - **It does not hold field writes, or SQL that no call runs.** Step 3 fills the field gap with a text search, which is the right tool for a setter name.
122
+ - **An event's two sides are matched by the topic name.** A consumer in a form Niro does not read yet is missing. See the known gaps in [faq-wrong-or-missing-answers.md](faq-wrong-or-missing-answers.md).
123
+ <!-- source: niro-local server instructions (tables reached through a recognised data-access library are in the graph; field reads and writes are not); docs/how-the-graph-works.md -->
124
+
125
+ ## What grep would do instead
126
+
127
+ - **Searching for the table name finds one line:** `grep -rnw CREDENTIAL --include=*.java` in Keycloak prints only the `@Table` annotation. Every read and write goes through `em.persist`, `em.find` and named queries on the entity class.
128
+ - **Searching for the entity class finds 13 files,** and grep cannot tell which of them write and which only read.
129
+ <!-- source: `grep -rn --include=*.java -w "CREDENTIAL" .` = 1 line (CredentialEntity.java:43); `grep -rln --include=*.java "CredentialEntity" .` = 13 files (Keycloak 968726fe66, 2026-11-10) -->
130
+
131
+ ## Cost
132
+
133
+ What these steps cost depends on your code and your question. How Niro counts the size of its answers, and how to measure the cost on your own tasks, is in [tokens-speed-and-accuracy.md](tokens-speed-and-accuracy.md).
134
+
135
+ ## Related
136
+
137
+ [playbook-debug-exception.md](playbook-debug-exception.md), [playbook-data-and-events.md](playbook-data-and-events.md), [answer-shapes.md](answer-shapes.md).
@@ -0,0 +1,95 @@
1
+ # Playbook: learn a new codebase
2
+
3
+ **The question, in the user's words:** "I am new to this repository. What services are in it, how do they talk to each other, what routes do they serve, and what do they store?"
4
+
5
+ **What you get at the end:** the shape of the repository in a handful of answers, each small, before you open a file. Then the one or two areas to read, chosen from those answers.
6
+ <!-- source: Keycloak outputs: niro-local 43cce717 from /home/niraj/projects/github/keycloak at 968726fe66; java-mono outputs from /home/niraj/projects/github/niro-coverage-corpus/java-mono (synthetic); scratch home, 2026-11-10 (scratchpad niro-cli-task-playbooks/runs/onb-*.txt, xs-01-topo.txt, data-01-list.txt, de-01-events.txt) -->
7
+
8
+ In recorded agent sessions (these recordings are mostly Niro's own development, labelled by hand by one agent, about plus or minus 10%), understanding existing code was the most common use of Niro: 329 of 794 calls (41%), almost all of them `code_grep` (207) and `get_source_code` (93). The structure tools below were rarely used, although they answer the first questions with less reading.
9
+ <!-- source: scratchpad niro-cli-task-playbooks/TASKS.md (hand labels of 794 recorded calls, about plus or minus 10%) -->
10
+
11
+ ## The steps
12
+
13
+ ### 1. How is the repository split?
14
+
15
+ ```
16
+ niro modules
17
+
18
+ /home/niraj/projects/github/keycloak holds 51 modules:
19
+ 0.6 dockerfile .
20
+ 0.5 entrypoint authz/policy/common
21
+ 0.9 depended_on_library authzen/services
22
+ ...
23
+ ```
24
+ <!-- source: `niro-local modules` in Keycloak, 2026-11-10 (51 modules); `niro modules` passes to the engine (src/commands/engineRoutes.js:33) -->
25
+
26
+ Each line is a folder Niro treats as its own unit, and the kind of signal that made it one: here a Dockerfile, a runnable entry point, or a library other modules depend on.
27
+
28
+ ### 2. Which service calls which?
29
+
30
+ ```
31
+ niro ask get_service_topology
32
+
33
+ 18 link(s) between parts of this project:
34
+ main -> armeria (3 route(s))
35
+ GET /api/prices/{param}
36
+ POST /api/prices/recalculate
37
+ POST /shop.pricing.v1.PricingDesk/Quote (grpc)
38
+ main -> helidon-se (2 route(s))
39
+ ...
40
+ ```
41
+ <!-- source: runs/xs-01-topo.txt (java-mono, 2,624 characters) -->
42
+
43
+ Keycloak is mostly one server, and its topology has one link, from a test. That is an answer too: there is little to learn here about calls between services.
44
+ <!-- source: runs/onb-01-topo.txt ("1 link(s) between parts of this project, 1 only from tests") -->
45
+
46
+ ### 3. What does it serve?
47
+
48
+ ```
49
+ niro ask find_api_endpoints limit=10
50
+ Found 938 HTTP endpoints.
51
+ ```
52
+
53
+ Narrow it with `path_pattern=/admin` or `service_filter=<folder>`. In Keycloak, 938 routes is too many to read: pick the area you will work on.
54
+ <!-- source: runs/onb-02-ep.txt (Keycloak, "Found 938 HTTP endpoints."); docs/reference-mcp-tools.md (find_api_endpoints inputs) -->
55
+
56
+ ### 4. What does it store, and what messages does it send?
57
+
58
+ ```
59
+ niro ask list_data_resources limit=15 # Keycloak: 67 stores, USER_ENTITY used 20 times, REALM 10, ...
60
+ niro ask list_events limit=12 # java-mono: 34 events, with how many publish and handle each
61
+ ```
62
+ <!-- source: runs/data-01-list.txt (67 data resources); runs/de-01-events.txt (34 events) -->
63
+
64
+ ### 5. What types are there?
65
+
66
+ ```
67
+ niro ask list_types limit=50
68
+ 11220 type(s):
69
+ ```
70
+
71
+ Use it to find a class by name. For one class, `get_class_structure answer=count` gives its size and how many subtypes it has.
72
+ <!-- source: `niro-local ask list_types limit=3` in Keycloak ("11220 type(s):"), 2026-11-10; runs/feat-01-count.txt -->
73
+
74
+ ### 6. See it in a browser
75
+
76
+ `niro ui` draws the graph on a page served only on this computer. See [lifecycle-onboarding.md](lifecycle-onboarding.md).
77
+
78
+ ## Why the answer is trustworthy, and where it is not
79
+
80
+ - **Modules, routes, stores and events come from the code and its build and deploy files,** read by tables of known frameworks and libraries. A framework Niro has no table for is missing from these lists. See [supported-overview.md](supported-overview.md).
81
+ - **If services are joined wrongly,** tell Niro where they live: [configuring-a-folder.md](configuring-a-folder.md).
82
+ <!-- source: docs/how-the-graph-works.md ("It recognises routes, outbound calls, data access and events from tables of known libraries and frameworks") -->
83
+
84
+ ## What grep would do instead
85
+
86
+ grep can list files, but it has no idea of a service, a route declared by a folder name, or a store reached through an ORM. For example, in the TypeScript corpus `find_api_endpoints path_pattern=/api/bookings` finds 2 routes declared only by folder names, where `grep -rn "api/bookings"` finds 0 lines.
87
+ <!-- source: runs/ts-01-routes.txt; `grep -rn "api/bookings" typescript-mono --include=*.ts --include=*.tsx` = 0 lines (2026-11-10) -->
88
+
89
+ ## Cost
90
+
91
+ What these steps cost depends on your code and your question. How Niro counts the size of its answers, and how to measure the cost on your own tasks, is in [tokens-speed-and-accuracy.md](tokens-speed-and-accuracy.md).
92
+
93
+ ## Related
94
+
95
+ [lifecycle-onboarding.md](lifecycle-onboarding.md), [how-the-graph-works.md](how-the-graph-works.md), [playbook-data-and-events.md](playbook-data-and-events.md).
@@ -0,0 +1,72 @@
1
+ # Playbook: review a change
2
+
3
+ **The question, in the user's words:** "This branch changes two credential methods. Before I approve it: what does it reach, which routes and services are affected, and is it tested?"
4
+
5
+ **What you get at the end:** for the changed methods together, the size of the change, its callers by name, the routes and other services on the way, and the tests that reach it. Run it from the branch's own folder: a new branch of an indexed folder gets its own copy of the graph.
6
+ <!-- source: Keycloak outputs: niro-local 43cce717 as `niro-local ask` from /home/niraj/projects/github/keycloak at 968726fe66, scratch home, 2026-11-10 (scratchpad niro-cli-task-playbooks/runs/brk-*.txt); docs/worktrees-and-branches.md (temporary projects); `niro ask` runs the same engine command (src/commands/engineRoutes.js:28) -->
7
+
8
+ ## The steps
9
+
10
+ ### 1. List the changed methods, from the diff
11
+
12
+ Take the method names from `git diff --stat` and the diff itself. Niro does not read git history; it answers about the code as it is on disk.
13
+ <!-- source: niro-local CLAUDE.md ("There is no git access at all: change detection reads the filesystem") -->
14
+
15
+ ### 2. One call for the whole change, then the names
16
+
17
+ ```
18
+ niro ask get_blast_radius method_fqns='["org.keycloak.models.jpa.JpaUserCredentialStore.createCredentialEntity","org.keycloak.models.jpa.JpaUserCredentialStore.removeCredentialEntity"]' answer=count
19
+
20
+ Blast radius for 2 methods — 46 affected functions, risk: high (+6 in tests, not counted in risk) (upstream 27, downstream 25)
21
+ ```
22
+
23
+ ```
24
+ niro ask get_blast_radius method_fqns='[...same two...]' direction=upstream answer=names
25
+
26
+ upstream:
27
+ ...
28
+ PasswordCredentialProvider.deleteCredential (depth 3)
29
+ UserResource.removeCredential (depth 3)
30
+ CredentialModelTest.testCredentialCRUD [test] (depth 3)
31
+ ...
32
+ [shown all 27 entries; ask without answer= for signatures and code]
33
+ ```
34
+ <!-- source: runs/brk-01-multi.txt, runs/brk-02-names.txt -->
35
+
36
+ What to look for in review: a REST method on the list (`UserResource.removeCredential` is the admin API), entries marked "over HTTP" (another service calls it), and whether `[test]` entries cover both changed methods. Here the six tests sit on the `createCredentialEntity` side (two of them reach it only in the code as written, not at run time: see [playbook-test-a-change.md](playbook-test-a-change.md)). For the other method, ask it alone one level further: `get_reverse_call_chain method_fqn=...removeCredentialEntity depth=4 answer=locations` lists 3 tests at depth 4, among them `CredentialModelTest.testCredentialCRUD`. The same run-time check applies: the third, at `UserStorageTest.java:1017`, is inside `UserStorageTest.testCredentialCRUD`, which uses the federated user, so at run time it does not reach this method either.
37
+ <!-- source: runs/brk-02-names.txt; runs/rev-01-remove.txt (3 [test] callers at depth 4: CredentialModelTest.java:136, :205, UserStorageTest.java:1017); keycloak UserStorageTest.java:914 (testCredentialCRUD starts), :923-924 (thor, not local storage) -->
38
+
39
+ ### 3. Did a route change?
40
+
41
+ Compare the routes of the branch with the main branch, each from its own folder:
42
+
43
+ ```
44
+ niro ask find_api_endpoints path_pattern=/credentials
45
+ ```
46
+
47
+ Run it in both folders and compare the two lists. A route that disappears is a breaking change for every caller.
48
+ <!-- source: docs/lifecycle-review.md ("Whether a route was removed or changed: find_api_endpoints, compared with the main branch's folder") -->
49
+
50
+ ### 4. Did a call to another service change?
51
+
52
+ `find_outbound_http_calls` in both folders, compared the same way. For a monorepo, `get_service_topology` shows the links between its services.
53
+ <!-- source: docs/lifecycle-review.md; runs/xs-01-topo.txt -->
54
+
55
+ ## Why the answer is deterministic
56
+
57
+ The reviewer and the author get the same list for the same code, because it is read from the graph rather than from whatever each of them thought to search. The answer says when it stopped at a depth, and how many entries were cut there.
58
+ <!-- source: runs/brk-01-multi.txt ("22 node(s) marked '[+N more beyond depth]'") -->
59
+
60
+ ## What it does not do
61
+
62
+ - **It is not a pull request review product.** Automatic review is hosted Niro only, and review bots cannot reach the local engine. See [connect-review-bots.md](connect-review-bots.md) and [hosted-only.md](hosted-only.md).
63
+ - **It does not judge whether the change is right,** only what it reaches.
64
+ <!-- source: docs/lifecycle-review.md ("Review bots ... cannot use the local graph in this release"; "Automatic pull request review is a hosted Niro feature") -->
65
+
66
+ ## Cost
67
+
68
+ What these steps cost depends on your code and your question. How Niro counts the size of its answers, and how to measure the cost on your own tasks, is in [tokens-speed-and-accuracy.md](tokens-speed-and-accuracy.md).
69
+
70
+ ## Related
71
+
72
+ [lifecycle-review.md](lifecycle-review.md), [playbook-debug-after-a-change.md](playbook-debug-after-a-change.md), [playbook-test-a-change.md](playbook-test-a-change.md).