@mrciphersmith/keryx 0.2.164 → 0.3.1

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 (182) hide show
  1. package/README.md +4 -1
  2. package/dist/cli.js +82540 -50300
  3. package/dist/core.js +28967 -18937
  4. package/package.json +2 -2
  5. package/src/gdgraph/affected-report.ts +141 -0
  6. package/src/gdgraph/build.ts +170 -23
  7. package/src/gdgraph/service.ts +6 -0
  8. package/src/gdgraph/staleness.ts +253 -45
  9. package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
  10. package/src/gdskills/bundled/agents/design-advisor.md +64 -0
  11. package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
  12. package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
  13. package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
  14. package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
  15. package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
  16. package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
  17. package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
  18. package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
  19. package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
  20. package/src/gdskills/bundled/agents/security-auditor.md +62 -0
  21. package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
  22. package/src/gdskills/bundled/agents/work-planner.md +62 -0
  23. package/src/gdskills/bundled/install-manifest.json +797 -0
  24. package/src/gdskills/bundled/rules/core/model-selection.mdc +51 -0
  25. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
  26. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
  27. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
  28. package/src/gdskills/bundled/skills/review/review-jev-rules/SKILL.md +267 -0
  29. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +26 -0
  30. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +75 -247
  31. package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
  32. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
  33. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
  34. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
  35. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
  36. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
  37. package/src/gdskills/bundled/stacks/angular/agent-refs.json +4 -0
  38. package/src/gdskills/bundled/stacks/angular/governance/eval.json +1751 -0
  39. package/src/gdskills/bundled/stacks/angular/governance/scout.json +32 -0
  40. package/src/gdskills/bundled/stacks/angular/pack.json +55 -0
  41. package/src/gdskills/bundled/stacks/angular/rules/coding-style.mdc +82 -0
  42. package/src/gdskills/bundled/stacks/angular/rules/patterns.mdc +84 -0
  43. package/src/gdskills/bundled/stacks/angular/rules/security.mdc +70 -0
  44. package/src/gdskills/bundled/stacks/angular/rules/testing.mdc +73 -0
  45. package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/SKILL.md +127 -0
  46. package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/evals.json +72 -0
  47. package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/SKILL.md +98 -0
  48. package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/evals.json +73 -0
  49. package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/SKILL.md +112 -0
  50. package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/evals.json +74 -0
  51. package/src/gdskills/bundled/stacks/angular/skills/angular-testing/SKILL.md +102 -0
  52. package/src/gdskills/bundled/stacks/angular/skills/angular-testing/evals.json +71 -0
  53. package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
  54. package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
  55. package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
  56. package/src/gdskills/bundled/stacks/go/pack.json +41 -0
  57. package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
  58. package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
  59. package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
  60. package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
  61. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
  62. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
  63. package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
  64. package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
  65. package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
  66. package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
  67. package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
  68. package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
  69. package/src/gdskills/bundled/stacks/mobx/agent-refs.json +4 -0
  70. package/src/gdskills/bundled/stacks/mobx/governance/eval.json +904 -0
  71. package/src/gdskills/bundled/stacks/mobx/governance/scout.json +18 -0
  72. package/src/gdskills/bundled/stacks/mobx/pack.json +28 -0
  73. package/src/gdskills/bundled/stacks/mobx/rules/coding-style.mdc +91 -0
  74. package/src/gdskills/bundled/stacks/mobx/rules/patterns.mdc +122 -0
  75. package/src/gdskills/bundled/stacks/mobx/rules/security.mdc +56 -0
  76. package/src/gdskills/bundled/stacks/mobx/rules/testing.mdc +63 -0
  77. package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/SKILL.md +124 -0
  78. package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/evals.json +73 -0
  79. package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/SKILL.md +149 -0
  80. package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/evals.json +74 -0
  81. package/src/gdskills/bundled/stacks/nestjs/agent-refs.json +4 -0
  82. package/src/gdskills/bundled/stacks/nestjs/governance/eval.json +1308 -0
  83. package/src/gdskills/bundled/stacks/nestjs/governance/scout.json +34 -0
  84. package/src/gdskills/bundled/stacks/nestjs/pack.json +53 -0
  85. package/src/gdskills/bundled/stacks/nestjs/rules/coding-style.mdc +70 -0
  86. package/src/gdskills/bundled/stacks/nestjs/rules/patterns.mdc +83 -0
  87. package/src/gdskills/bundled/stacks/nestjs/rules/security.mdc +73 -0
  88. package/src/gdskills/bundled/stacks/nestjs/rules/testing.mdc +69 -0
  89. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/SKILL.md +157 -0
  90. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/evals.json +70 -0
  91. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/SKILL.md +129 -0
  92. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/evals.json +71 -0
  93. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/SKILL.md +143 -0
  94. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/evals.json +69 -0
  95. package/src/gdskills/bundled/stacks/nextjs-nuxt/agent-refs.json +4 -0
  96. package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/eval.json +2413 -0
  97. package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/scout.json +42 -0
  98. package/src/gdskills/bundled/stacks/nextjs-nuxt/pack.json +42 -0
  99. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/coding-style.mdc +69 -0
  100. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/patterns.mdc +88 -0
  101. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/security.mdc +72 -0
  102. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/testing.mdc +64 -0
  103. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/SKILL.md +147 -0
  104. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/evals.json +75 -0
  105. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/SKILL.md +118 -0
  106. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/evals.json +76 -0
  107. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/SKILL.md +135 -0
  108. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/evals.json +78 -0
  109. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/SKILL.md +116 -0
  110. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/evals.json +75 -0
  111. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/SKILL.md +134 -0
  112. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/evals.json +76 -0
  113. package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
  114. package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
  115. package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
  116. package/src/gdskills/bundled/stacks/python/pack.json +41 -0
  117. package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
  118. package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
  119. package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
  120. package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
  121. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
  122. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
  123. package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
  124. package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
  125. package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
  126. package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
  127. package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
  128. package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
  129. package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
  130. package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
  131. package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
  132. package/src/gdskills/bundled/stacks/react/pack.json +42 -0
  133. package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
  134. package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
  135. package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
  136. package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
  137. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
  138. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
  139. package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
  140. package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
  141. package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
  142. package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
  143. package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
  144. package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
  145. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
  146. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
  147. package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
  148. package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
  149. package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
  150. package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
  151. package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
  152. package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
  153. package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
  154. package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
  155. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
  156. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
  157. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
  158. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
  159. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
  160. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
  161. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
  162. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
  163. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
  164. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
  165. package/src/gdskills/bundled/stacks/vue/agent-refs.json +4 -0
  166. package/src/gdskills/bundled/stacks/vue/governance/eval.json +2215 -0
  167. package/src/gdskills/bundled/stacks/vue/governance/scout.json +42 -0
  168. package/src/gdskills/bundled/stacks/vue/pack.json +42 -0
  169. package/src/gdskills/bundled/stacks/vue/rules/coding-style.mdc +73 -0
  170. package/src/gdskills/bundled/stacks/vue/rules/patterns.mdc +84 -0
  171. package/src/gdskills/bundled/stacks/vue/rules/security.mdc +60 -0
  172. package/src/gdskills/bundled/stacks/vue/rules/testing.mdc +69 -0
  173. package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/SKILL.md +137 -0
  174. package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/evals.json +72 -0
  175. package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/SKILL.md +120 -0
  176. package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/evals.json +71 -0
  177. package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/SKILL.md +122 -0
  178. package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/evals.json +72 -0
  179. package/src/gdskills/bundled/stacks/vue/skills/vue-testing/SKILL.md +115 -0
  180. package/src/gdskills/bundled/stacks/vue/skills/vue-testing/evals.json +72 -0
  181. package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/SKILL.md +135 -0
  182. package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/evals.json +71 -0
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "query": "Use when writing or extending a MobX store: adding observable state, actions, computed getters, or reactions (autorun/reaction/when), wiring a store into React via observer and a context hook, or fixing a component that stops re-rendering after a store change. Also covers plain-language asks for the same work: keeping a piece of MobX store state automatically in sync wherever it's read, or making a store run something automatically when a value changes and stop when the store is no longer needed. Applies the makeObservable/action/runInAction/observer shape and the store's dispose lifecycle. Not for reviewing an already-written store's structure (use code-mobx-store-review) and not for plain React state/props work with no MobX involved (use react-implementation).",
4
+ "decision": "fork",
5
+ "topMatch": "mobx/mobx-observable-testing",
6
+ "recordedAt": "2026-09-25T06:28:15.470Z",
7
+ "skillName": "mobx-store-implementation",
8
+ "justification": "Top match remains this pack's own sibling mobx-observable-testing -- same domain vocabulary (store/action/computed/observable), different category (implement vs test), not a substitute: that skill only covers writing/fixing tests, never authoring store code. Description was widened (flow 318 follow-up) to also cover plain-language symptom/goal phrasings of the same implement work, not to broaden scope -- no new candidate skill/pack became a closer match after the update; review-frontend, nestjs-implementation, code-mobx-store-review, and angular-implementation remain the next nearest and are all a different stack/activity or review-only."
9
+ },
10
+ {
11
+ "query": "Use when writing or fixing a test for a MobX store: asserting on an async action's post-await state, waiting for a reaction/autorun to fire, testing that dispose() actually stops a store's reactions, or deciding how enforceActions should behave in the test setup. Distinct from rendering-focused React component testing and generic Node/TypeScript test-runner setup. Not for writing the store or action code itself (use mobx-store-implementation) and not for React Testing Library render/interaction patterns with no MobX involved (use react-testing).",
12
+ "decision": "fork",
13
+ "topMatch": "mobx/mobx-store-implementation",
14
+ "recordedAt": "2026-09-25T06:24:35.475Z",
15
+ "skillName": "mobx-observable-testing",
16
+ "justification": "Top match (0.38) is this pack's own sibling mobx-store-implementation -- same domain vocabulary (store/action/reaction/dispose), different category (test vs implement), not a substitute: that skill authors store/action code, this one asserts on the resulting async/reaction behavior in tests. Next matches (vue-testing 0.25, nodejs-implementation 0.22, angular-code-review 0.21, review-frontend 0.21) are all a different stack or activity entirely -- generic test-runner/component-render skills with no MobX-specific reactivity (runInAction seeding, when()-based waits, dispose/reaction cleanup assertions) this skill covers."
17
+ }
18
+ ]
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "mobx",
3
+ "family": "framework",
4
+ "extends": "react",
5
+ "modules": [
6
+ "mobx-rules",
7
+ "mobx-skills"
8
+ ],
9
+ "detectionMarkers": [
10
+ "mobx"
11
+ ],
12
+ "provenance": {
13
+ "origin": "authored",
14
+ "sourceRef": "flow 318, Wave 4 batch 2"
15
+ },
16
+ "stability": "experimental",
17
+ "skills": {
18
+ "implement": [
19
+ "mobx-store-implementation"
20
+ ],
21
+ "test": [
22
+ "mobx-observable-testing"
23
+ ],
24
+ "review": [],
25
+ "build-fix": [],
26
+ "migrate": []
27
+ }
28
+ }
@@ -0,0 +1,91 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.ts", "**/*.tsx"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # MobX coding style
9
+
10
+ Narrows the React/TypeScript common rules to MobX's own idiom for stores,
11
+ observables, and observer components. Everything not MobX-specific still
12
+ comes from the common and React rules this file `extends`.
13
+
14
+ ## Store class shape
15
+
16
+ - Call `makeObservable(this)` (with an explicit annotations map, or
17
+ `makeAutoObservable(this)` for a store with no inheritance and no need to
18
+ keep some fields non-observable) once, in the constructor, before any
19
+ other constructor logic reads `this`.
20
+ - Prefer explicit `@observable`/`@computed`/`@action`/`@action.bound`
21
+ decorators with `makeObservable` over `makeAutoObservable` for any store
22
+ a component subclasses, that mixes injected dependencies with state, or
23
+ that needs a field to stay non-observable — `makeAutoObservable` makes
24
+ every own enumerable property observable and every method an action,
25
+ which is the wrong default once the store has structure to protect.
26
+ - Name store classes `XyzStore` and files `xyz.store.ts`/`XyzStore.ts` to
27
+ match the project's existing convention; keep one store per file.
28
+
29
+ ## Actions
30
+
31
+ - Every method that mutates observable state must be an `action`
32
+ (`@action` or `@action.bound`). A public method invoked from a component
33
+ event handler must be `@action.bound` so it can be passed as a callback
34
+ reference without losing `this`.
35
+ - After an `await` inside an async action, re-enter MobX with
36
+ `runInAction(() => { ... })` before mutating state — the code after the
37
+ `await` runs outside the original action's transaction.
38
+ - Do not call a store method that mutates state directly from a component
39
+ render body; only from an event handler, effect, or another action.
40
+
41
+ ## Computed values
42
+
43
+ - Any value derived purely from other observables belongs in a
44
+ `@computed get` accessor, not in a component-side calculation repeated
45
+ on every render or a store method that recomputes and returns a fresh
46
+ value each call. A `@computed` is cached and only re-evaluates when one
47
+ of its own observable dependencies changes.
48
+ - Keep a `@computed` getter free of side effects; it must be safe to
49
+ evaluate speculatively (MobX may read a computed's value to decide
50
+ whether it changed).
51
+
52
+ ## Observable collections and value types
53
+
54
+ - Use `@observable.ref` for a value the store treats as an opaque
55
+ reference (a large externally-owned object, a DOM/editor instance) it
56
+ does not want deeply proxied, `@observable.shallow` for a container
57
+ whose items should stay plain, and `@observable.struct` when a reaction
58
+ should only fire on a structural (deep-equal) change, not fine-grained.
59
+ Reserve the default deep `@observable` for state the store actually
60
+ wants MobX to track recursively.
61
+ - When you need MobX's own collection API (`.replace()`, `.remove()`,
62
+ `.merge()`, `.has()`) on an array or map field, construct it with
63
+ `observable([...])` / `observable(new Map())` and type it as
64
+ `IObservableArray<T>` / `ObservableMap<K, V>`; a plain array/object
65
+ literal assigned to `@observable` is still deeply observed but does not
66
+ expose those methods.
67
+
68
+ ## Components
69
+
70
+ - Wrap any function component that reads observable state in `observer`
71
+ from `mobx-react-lite`. A component that reads a store field but is not
72
+ wrapped in `observer` will render once and silently never update again
73
+ when that field changes.
74
+ - Read store instances through a typed React context + hook pair
75
+ (`useXyzStore()`), not through a module-level singleton import, so
76
+ components stay testable and providers can swap the instance.
77
+ - Do not destructure observable fields off a store at the top of a
78
+ component body if that breaks fine-grained tracking for a nested
79
+ `<Observer>` boundary further down — read the field where it is
80
+ actually rendered instead.
81
+
82
+ ## Errors and imports
83
+
84
+ - Import MobX identifiers (`observable`, `action`, `computed`, `reaction`,
85
+ `runInAction`, `makeObservable`) from `"mobx"`, and `observer`,
86
+ `useLocalObservable` from `"mobx-react-lite"` — do not import from the
87
+ deprecated combined `mobx-react` package in new code unless the project
88
+ has not migrated off it yet.
89
+ - Catch and store errors from an async action's `try/catch` as
90
+ `err: unknown`, narrowing before assigning to an observable `error`
91
+ field (see `rules/patterns.mdc` for the full async-action shape).
@@ -0,0 +1,122 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.ts", "**/*.tsx"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # MobX patterns
9
+
10
+ Idiomatic MobX design patterns and their anti-patterns, narrowing the
11
+ common architecture rules to how state, actions, and reactions should be
12
+ composed in a MobX + React codebase.
13
+
14
+ ## The async action shape
15
+
16
+ The standard shape for a store method that calls an API and updates
17
+ loading/error/data state:
18
+
19
+ ```typescript
20
+ private performFetch = async () => {
21
+ try {
22
+ runInAction(() => {
23
+ this.loading = true;
24
+ this.error = null;
25
+ });
26
+
27
+ const data = await this.service.getAll();
28
+
29
+ runInAction(() => {
30
+ if (this.disposed) return;
31
+ this.items = data;
32
+ });
33
+ } catch (err: unknown) {
34
+ runInAction(() => {
35
+ if (this.disposed) return;
36
+ this.error = err instanceof Error ? err.message : "Fetch failed";
37
+ });
38
+ } finally {
39
+ runInAction(() => {
40
+ if (this.disposed) return;
41
+ this.loading = false;
42
+ });
43
+ }
44
+ };
45
+ ```
46
+
47
+ Anti-pattern: mutating `this.loading`/`this.items` directly after the
48
+ `await` with no `runInAction` — this throws under `enforceActions: "observed"`
49
+ and silently misses the reaction under looser settings, either way
50
+ producing a UI that does not update.
51
+
52
+ ## `flow` as an alternative to `runInAction`
53
+
54
+ For a store with many async actions, `flow` (a generator-based action)
55
+ avoids repeating `runInAction` around every post-`await` mutation, since
56
+ the whole generator body already runs inside one action:
57
+
58
+ ```typescript
59
+ import { flow } from "mobx";
60
+
61
+ fetchItems = flow(function* (this: ItemStore) {
62
+ this.loading = true;
63
+ try {
64
+ this.items = yield this.service.getAll();
65
+ } finally {
66
+ this.loading = false;
67
+ }
68
+ });
69
+ ```
70
+
71
+ Use `flow` when a store's async methods are mostly sequential awaits with
72
+ state writes in between; keep plain `async`/`runInAction` when a method
73
+ needs to interleave with non-MobX async code (`Promise.all`, cancellation
74
+ tokens) where the generator form adds friction.
75
+
76
+ ## Reaction ownership and disposal
77
+
78
+ - `reaction`/`autorun`/`when` created inside a store belong to that
79
+ store's lifecycle: push the disposer into a `disposers: IReactionDisposer[]`
80
+ array in the constructor (or an `init()` called by the parent) and call
81
+ every disposer in `dispose()`.
82
+ - Prefer `reaction(() => selector, handler)` over `autorun` when the
83
+ handler should only run in response to a specific observable changing,
84
+ not on every observable it happens to read — `autorun` re-subscribes to
85
+ everything it touches on each run, which can widen its dependency set
86
+ unintentionally.
87
+ - A component should not create a raw `autorun`/`reaction` in its render
88
+ body; either derive the value with `@computed` in the store and let
89
+ `observer` handle re-rendering, or start the reaction in a `useEffect`
90
+ and dispose it in the cleanup function.
91
+
92
+ ## Bidirectional store sync needs an equality guard
93
+
94
+ When Store A writes to Store B and Store B writes back to Store A, guard
95
+ the reverse direction with `if (newValue !== currentValue)` before
96
+ propagating, or the two stores bounce a change back and forth forever.
97
+ See `rules/coding-style.mdc` for where that callback belongs (private,
98
+ not a public UI action).
99
+
100
+ ## View↔Store boundary
101
+
102
+ - A component reads store state and calls store actions; it does not
103
+ hold a parallel copy of store state in `useState` "for convenience" —
104
+ that copy silently desyncs from the store the first time an action
105
+ outside that component's tree mutates the same field.
106
+ - Data loading triggered by mounting belongs in the owning store's
107
+ `init()`/`onMount()`, called by the *parent* store or the top-level
108
+ provider — not scattered across every consuming component's
109
+ `useEffect`, which makes it impossible to reason about how many times a
110
+ fetch fires as the tree re-renders.
111
+ - A store must not import a React component or hook; it may accept
112
+ plain callback props for the rare case a parent needs to react to a
113
+ child store's internal event.
114
+
115
+ ## Anti-pattern: computed-as-action
116
+
117
+ A `@computed` getter must never mutate state — a getter with a
118
+ side-effecting write inside it runs outside an explicit action
119
+ transaction and MobX will throw ("computed value is allowed to be
120
+ mutated only through action") the moment `enforceActions` is on. If a
121
+ "derived" value also needs to cache an expensive side effect, that is an
122
+ action-triggered `reaction`, not a computed.
@@ -0,0 +1,56 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.ts", "**/*.tsx"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # MobX security
9
+
10
+ Stack-specific risks narrowing the common OWASP-style rules to MobX state
11
+ management. Most web security concerns (XSS, auth, injection) are
12
+ React/general-web concerns already covered by the common and React rules
13
+ this file `extends`; this file covers the risks that are specific to
14
+ putting sensitive data in observable state.
15
+
16
+ ## Do not put secrets in long-lived observable state
17
+
18
+ - A token, API key, or credential held in an `@observable` field on a
19
+ store that survives the session (not cleared on logout, persisted via
20
+ `autorun`/`reaction` to `localStorage`) is exposed to any code with
21
+ access to that store instance and to any DevTools-based MobX inspector
22
+ attached to the page. Keep secrets out of observable state; pass them
23
+ through a request layer that does not put them on a reactive object a
24
+ component tree (and any debugging tool observing it) can read.
25
+ - If a `reaction`/`autorun` persists store state to `localStorage`/
26
+ `sessionStorage` (a common MobX pattern for "remember this UI state"),
27
+ explicitly exclude any field carrying a token, PII, or payment data from
28
+ what gets serialized — persist a derived, scrubbed snapshot, not the
29
+ whole store.
30
+
31
+ ## Untrusted data into observable state is still untrusted
32
+
33
+ - Assigning server or user-supplied HTML/markdown into an observable
34
+ field does not sanitize it. If that field is later rendered with
35
+ `dangerouslySetInnerHTML` in an `observer` component, the same XSS risk
36
+ applies as any other React render path — sanitize before it reaches the
37
+ observable, not by trusting MobX's reactivity to be a security boundary
38
+ (see `rules/security.mdc` in the `react` pack this one extends).
39
+
40
+ ## Reaction-driven side effects and SSRF/open-redirect risk
41
+
42
+ - A `reaction`/`autorun` that fires a `fetch`/navigation based on an
43
+ observable URL or hostname field must validate that value the same way
44
+ a request-time input would be validated — an attacker who can influence
45
+ the observable (e.g. via a query param synced into the store) can
46
+ otherwise trigger a request to an attacker-chosen host purely through
47
+ state changes, with no obvious call site to audit.
48
+
49
+ ## Disposal prevents stale-store data leaks across users/sessions
50
+
51
+ - A store not disposed on logout/route-change can keep the previous
52
+ user's data in memory and observable to any component that still holds
53
+ a reference (e.g. a lingering `reaction` on a background tab). Call
54
+ `dispose()` on session/user change for any store holding
55
+ user-scoped data, not just to free memory — to stop rendering or
56
+ syncing stale, now-unauthorized data.
@@ -0,0 +1,63 @@
1
+ ---
2
+ extends: common
3
+ paths: ["**/*.ts", "**/*.tsx"]
4
+ metadata:
5
+ origin: authored
6
+ ---
7
+
8
+ # MobX testing
9
+
10
+ Narrows the common testing rules to what is specific about testing MobX
11
+ stores, actions, and reactions — test layout, runner, and framework
12
+ concerns for React components themselves still come from the `react`
13
+ pack's own `rules/testing.mdc`.
14
+
15
+ ## Store tests are plain unit tests
16
+
17
+ - A store with no React dependency (no `observer`, no hook) is tested by
18
+ constructing it directly and calling its actions/reading its computeds
19
+ — no React Testing Library render is needed. Co-locate the test next to
20
+ the store (`xyz.store.test.ts` beside `xyz.store.ts`), matching the
21
+ project's existing test-file convention.
22
+ - Mock the store's injected dependencies (an API service passed into the
23
+ constructor), not MobX itself — MobX's reactivity is the thing under
24
+ test, not something to stub out.
25
+
26
+ ## Awaiting async actions and reactions
27
+
28
+ - After calling an async action in a test, `await` the action's own
29
+ returned promise (for a plain `async` method) or `await` a `when(...)`
30
+ predicate on the resulting state (`await when(() => !store.loading)`)
31
+ before asserting — do not assert immediately after the call and rely on
32
+ a fixed `setTimeout`/sleep, which is both slower and flaky.
33
+ - When asserting on a value only a `reaction`/`autorun` sets, wait for
34
+ that reaction to have run (`await when(() => condition)`, or `await
35
+ Promise.resolve()` for microtask-only sync reactions) rather than
36
+ asserting synchronously right after the triggering mutation.
37
+
38
+ ## `enforceActions` in tests
39
+
40
+ - Keep `enforceActions` at the project's real (usually `"observed"` or
41
+ `"always"`) setting in tests rather than relaxing it to `"never"` for
42
+ convenience — a test that mutates observable state outside an action
43
+ should fail the same way production code would, since that failure is
44
+ exactly the bug class these tests exist to catch.
45
+ - If a test genuinely needs to seed store state directly (bypassing
46
+ actions, e.g. to set up a precondition), wrap that seeding assignment
47
+ in `runInAction` rather than disabling `enforceActions` for the whole
48
+ suite.
49
+
50
+ ## Testing disposal and reaction cleanup
51
+
52
+ - Assert that `dispose()` actually stops a store's reactions: trigger a
53
+ mutation after `dispose()` and confirm the disposed reaction's effect
54
+ did not run again — a leaked, undisposed `autorun`/`reaction` is a real
55
+ bug that a naive "does dispose() throw" test will not catch.
56
+
57
+ ## Component tests still need `observer`-aware setup
58
+
59
+ - A test rendering an `observer` component must trigger the store
60
+ mutation the same way production code would (through the store's own
61
+ action) and then assert on the rendered output after that action
62
+ resolves — do not directly reassign an observable field the component
63
+ reads and expect the same ordering guarantees an action provides.
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: mobx-observable-testing
3
+ description: "Use when writing or fixing a test for a MobX store: asserting on an async action's post-await state, waiting for a reaction/autorun to fire, testing that dispose() actually stops a store's reactions, or deciding how enforceActions should behave in the test setup. Distinct from rendering-focused React component testing and generic Node/TypeScript test-runner setup. Not for writing the store or action code itself (use mobx-store-implementation) and not for React Testing Library render/interaction patterns with no MobX involved (use react-testing)."
4
+ triggers:
5
+ - "write a test for this MobX store's async fetch action"
6
+ - "test that this store's reaction actually fires"
7
+ - "my store test asserts before the async action finishes"
8
+ - "test that dispose() stops this store's autorun"
9
+ - "how should enforceActions behave in tests"
10
+ - "test this computed getter updates when the observable changes"
11
+ - "flaky MobX store test that sometimes reads stale state"
12
+ metadata:
13
+ origin: authored
14
+ category: test
15
+ version: "1.0.0"
16
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
17
+ license: "MIT"
18
+ ---
19
+
20
+ # MobX observable testing
21
+
22
+ Write or fix a test that exercises a MobX store's actions, computed
23
+ values, or reactions directly — not through a component render. See
24
+ `rules/testing.mdc` for the full set of conventions this skill applies,
25
+ and `rules/patterns.mdc` for the async-action shape being tested.
26
+
27
+ ## Scope
28
+
29
+ This skill is for testing MobX's own reactivity: does an action correctly
30
+ update state, does a computed recompute, does a reaction fire and get
31
+ disposed. It is not for writing the store/action code under test (use
32
+ `mobx-store-implementation`) and not for React Testing Library
33
+ render/interaction mechanics with no MobX-specific concern (use the
34
+ `react` pack's testing skill) — a test that only checks a button click
35
+ calls a prop handler has nothing MobX-specific in it and does not need
36
+ this skill.
37
+
38
+ ## Workflow
39
+
40
+ ### Step 1: Identify what kind of MobX behavior is under test
41
+
42
+ - A synchronous action's effect on observable state — assert immediately
43
+ after calling it.
44
+ - An async action's effect on state after an `await` — the assertion has
45
+ to wait for the action's own promise (or a predicate) to resolve, not
46
+ run synchronously.
47
+ - A `@computed` getter — assert it changes when its own observable
48
+ dependency changes, and does not change when an unrelated field does.
49
+ - A `reaction`/`autorun`/`when` — assert its effect actually runs after
50
+ the triggering mutation, and stops running after `dispose()`.
51
+
52
+ ### Step 2: Construct the store with mocked dependencies
53
+
54
+ Instantiate the store directly with mocked injected dependencies (an API
55
+ service stub), not a full component tree. Keep `enforceActions` at the
56
+ project's real setting (see `rules/testing.mdc`) — do not loosen it in
57
+ test setup just to make an assertion easier to write.
58
+
59
+ ### Step 3: Write the assertion with the right wait
60
+
61
+ - Sync action: call it, assert immediately.
62
+ - Async action: `await` the action's returned promise directly if it's a
63
+ plain `async` method; otherwise `await when(() => <the condition the
64
+ action should reach>)` before asserting.
65
+ - Reaction/autorun: trigger the mutation that should fire it, then
66
+ `await when(() => <the reaction's expected effect>)` (or `await
67
+ Promise.resolve()` for a synchronous microtask reaction) before
68
+ asserting the effect ran.
69
+ - Never assert immediately after a mutation that a reaction depends on
70
+ without first waiting for that reaction to run, and never paper over a
71
+ flaky wait with a fixed `setTimeout`/sleep — it is both slower and
72
+ still not deterministic under load.
73
+
74
+ ### Step 4: Test disposal
75
+
76
+ For a store owning `autorun`/`reaction`/`when`, add a test that calls
77
+ `dispose()`, then triggers the mutation that would normally fire the
78
+ reaction, and asserts the reaction's effect did NOT run again — a test
79
+ that only checks `dispose()` doesn't throw misses the actual leak.
80
+
81
+ ### Step 5: Verify
82
+
83
+ Run the project's test command and confirm the new/changed tests are
84
+ deterministic (run them a few times in a row locally if they involve
85
+ timing) and pass without a suppressed assertion.
86
+
87
+ ## Rules
88
+
89
+ - Follow `rules/testing.mdc` for store test layout, mocking, and
90
+ `enforceActions` behavior.
91
+ - ALWAYS wait for an async action's own completion (its promise, or a
92
+ `when(...)` predicate) before asserting on state it sets — never assert
93
+ synchronously right after calling it.
94
+ - ALWAYS keep `enforceActions` at the project's real setting; seed
95
+ precondition state through `runInAction`, not by disabling it.
96
+ - NEVER assert on a reaction's effect without first waiting for the
97
+ reaction to actually run.
98
+ - NEVER skip or delete a store test to make a suite pass; fix the source
99
+ if behavior regressed, or fix the test if its expectation was stale —
100
+ state which, and why.
101
+
102
+ ## Red Flags
103
+
104
+ | Rationalization | Why it is wrong |
105
+ |---|---|
106
+ | "I'll just add a `setTimeout(resolve, 100)` before asserting, it's fast enough in CI" | A fixed sleep is both slower than necessary and still not deterministic under load; wait on the action's own promise or a `when(...)` predicate instead |
107
+ | "I'll set `enforceActions: 'never'` in the test file so I can mutate state directly to set up my fixture" | That hides the same bug class the real app would hit; seed fixture state through `runInAction` instead |
108
+ | "The dispose() test just checks it doesn't throw, that's enough" | A store can dispose without ever having wired up its reactions correctly; assert the reaction's effect actually stops firing after dispose |
109
+ | "This store test is flaky, I'll skip it for now" | Skipping hides a real timing bug in the store or the test's own wait strategy; find the missing `await`/`when` instead |
110
+
111
+ ## Verification
112
+
113
+ Do not report a store test done until all of the following hold:
114
+
115
+ - The project's test command passes for the new/changed test.
116
+ - Every assertion on async-action or reaction state waits for that
117
+ action's promise or a `when(...)` predicate first — no bare
118
+ `setTimeout`/sleep.
119
+ - `enforceActions` was not loosened in the test file to make setup
120
+ easier.
121
+ - A store owning disposable reactions has a test proving `dispose()`
122
+ actually stops them, not just that it runs without throwing.
123
+ - Re-running the test locally 2-3 times in a row produces the same
124
+ result (no flake from a missing wait).
@@ -0,0 +1,73 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "I need a test for this MobX store's async method that fetches data from the API -- how do I make sure it actually waits for the result before I assert?",
5
+ "My store test asserts on this.items right after calling the async action and it's flaky",
6
+ "How do I test that this MobX store actually does something automatically when a value changes?",
7
+ "Write a test proving dispose() stops this store's reaction from running again",
8
+ "Should enforceActions be relaxed in my MobX store test setup?",
9
+ "How do I test that a derived value on this MobX store updates correctly when the thing it depends on changes?",
10
+ "This store test sometimes reads stale state before the reaction has run, fix the wait"
11
+ ],
12
+ "negative": [
13
+ "Add a new @action.bound method to this store's constructor that fetches items from the API and wraps the loading/error update in runInAction",
14
+ "Write a React Testing Library test that clicks this button and checks the handler prop was called",
15
+ "Add a @computed get totalPrice getter to this store class that sums the items array",
16
+ "Write a Vitest test for this plain utility function that formats a currency string",
17
+ "Fix this tsc error: Property 'total' does not exist on type 'OrderDraft'",
18
+ "Review this MobX store for accessibility-modifier and method-ordering issues"
19
+ ]
20
+ },
21
+ "scenarios": [
22
+ {
23
+ "id": "assert-after-async-action",
24
+ "prompt": "This test is flaky:\n\n```ts\nit(\"loads items\", () => {\n store.fetchItems();\n expect(store.items).toHaveLength(3);\n});\n```\n\n`fetchItems` is an async action. Why is this flaky and how do I fix it?",
25
+ "strictness": "high",
26
+ "expected_behavior": [
27
+ {
28
+ "grader": "judge",
29
+ "rubric": "A correct answer explains that the assertion runs before the async fetchItems action has finished updating state, and fixes it by awaiting the action's own returned promise (or a when(...) predicate on the resulting state) before asserting, showing the corrected test code.",
30
+ "pass_criteria": [
31
+ "States that the test asserts synchronously right after calling fetchItems, before the async action's await has resolved and updated store.items, which is why the assertion sometimes sees stale/empty state",
32
+ "Shows the corrected test awaiting the action's own promise (e.g. `await store.fetchItems();`) or a `when(...)` predicate on the resulting state before the expect call"
33
+ ],
34
+ "fail_criteria": [
35
+ "Recommends adding a fixed setTimeout/sleep delay before the assertion instead of awaiting the action's own promise or a when(...) predicate -- mentioning that only to warn against it is not a failure",
36
+ "Leaves the assertion running synchronously right after the fetchItems() call with no await or wait predicate added at all"
37
+ ]
38
+ }
39
+ ],
40
+ "calibration": {
41
+ "known_right": "The test calls `store.fetchItems()` without awaiting it, then asserts immediately -- but fetchItems is async, so the assertion runs before the underlying await (the API call) has resolved and the action has had a chance to set store.items. That's why it's flaky: sometimes the promise resolves fast enough by coincidence, sometimes it doesn't. Fix it by awaiting the action itself:\n\n```ts\nit(\"loads items\", async () => {\n await store.fetchItems();\n expect(store.items).toHaveLength(3);\n});\n```\n\nIf fetchItems doesn't return its promise (e.g. it's fire-and-forget from a UI action), wait on a predicate instead: `await when(() => !store.loading);` before asserting. Either way, the test needs to wait for the actual async work to finish rather than assuming it already has.",
42
+ "known_wrong": "Flaky async tests like this usually just need a bit more time to settle -- add a small delay before the assertion: `store.fetchItems(); await new Promise(r => setTimeout(r, 200)); expect(store.items).toHaveLength(3);`. 200ms should be more than enough for the fetch to resolve in a test environment, and it's a quick fix that doesn't require changing fetchItems itself.",
43
+ "vague": "The test probably isn't waiting long enough for the async action to complete before checking the result, so you need to make sure it waits properly.",
44
+ "subtle_wrong": "I made the test function async and added `await Promise.resolve();` after calling fetchItems, since that lets one microtask cycle run before the assertion: `it(\"loads items\", async () => { store.fetchItems(); await Promise.resolve(); expect(store.items).toHaveLength(3); });`. That should cover most cases since a single microtask flush usually catches up with pending promise resolutions in this test environment."
45
+ }
46
+ },
47
+ {
48
+ "id": "dispose-test-effect",
49
+ "prompt": "I have a store with an autorun disposed in dispose(). My current test is:\n\n```ts\nit(\"disposes cleanly\", () => {\n store.dispose();\n});\n```\n\nDoes this actually prove the autorun stops running? If not, what should the test do instead?",
50
+ "strictness": "high",
51
+ "expected_behavior": [
52
+ {
53
+ "grader": "judge",
54
+ "rubric": "A correct answer states that the current test only proves dispose() doesn't throw, not that the autorun actually stops firing, and fixes it by disposing the store, then triggering the mutation the autorun depends on, and asserting the autorun's effect did not run again.",
55
+ "pass_criteria": [
56
+ "States that the current test only proves dispose() runs without throwing, not that the autorun's effect actually stops firing after disposal",
57
+ "Shows or names a corrected test that calls dispose(), then mutates the observable the autorun depends on, then asserts the autorun's tracked effect (e.g. a spy/counter it increments) did not run again"
58
+ ],
59
+ "fail_criteria": [
60
+ "Claims the existing test (calling dispose() alone with no post-dispose mutation or assertion) is already sufficient to prove the autorun stops running",
61
+ "Recommends asserting only that store.disposed is set to true as proof the reaction stopped, without triggering the dependent mutation and checking the reaction's own effect did not re-run"
62
+ ]
63
+ }
64
+ ],
65
+ "calibration": {
66
+ "known_right": "No -- calling dispose() and asserting nothing else only proves dispose() doesn't throw. It says nothing about whether the autorun's own effect actually stopped running afterward. To prove that, trigger the mutation the autorun depends on after disposal and check its effect didn't fire again:\n\n```ts\nit(\"stops the autorun after dispose\", () => {\n const spy = vi.fn();\n const store = new ItemStore({ onCountChange: spy }); // autorun calls onCountChange internally\n store.disposeCallCount = 0;\n\n store.dispose();\n runInAction(() => {\n store.items.push(newItem()); // would normally trigger the autorun\n });\n\n expect(spy).not.toHaveBeenCalled();\n});\n```\n\nThe key part is the mutation *after* dispose() and the assertion that the tracked effect did not run again -- without both, the test can't actually distinguish a correctly-disposed reaction from one that was never wired up correctly in the first place.",
67
+ "known_wrong": "The current test is fine -- if dispose() didn't clean up the autorun's internal MobX subscription correctly, calling it would throw an error from MobX's reaction system. Since it completes without throwing, that's good evidence the disposer ran and the reaction was torn down properly. No need to add a post-dispose mutation check.",
68
+ "vague": "That test probably isn't strong enough -- you should check that the autorun really doesn't keep running after dispose, not just that dispose itself works.",
69
+ "subtle_wrong": "Good catch -- I strengthened it by asserting `store.disposed` is `true` right after calling dispose(): `store.dispose(); expect(store.disposed).toBe(true);`. That confirms the store's own disposed flag flipped, which is what the autorun's guard checks internally, so if disposed is true the autorun's guarded logic won't run on the next reaction anyway."
70
+ }
71
+ }
72
+ ]
73
+ }