reverse-api-engineer 0.4.1__tar.gz → 0.4.2__tar.gz

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 (145) hide show
  1. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/CHANGELOG.md +5 -0
  2. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/PKG-INFO +1 -1
  3. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/pyproject.toml +1 -1
  4. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/base_engineer.py +53 -9
  5. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_base_engineer.py +74 -15
  6. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/.claude/settings.local.json +0 -0
  7. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/.claude-plugin/marketplace.json +0 -0
  8. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/.gitignore +0 -0
  9. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/.python-version +0 -0
  10. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/CLAUDE.md +0 -0
  11. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/CONTRIBUTING.md +0 -0
  12. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/INTERVIEW.md +0 -0
  13. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/LICENSE +0 -0
  14. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/PROMPT_STASH.md +0 -0
  15. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/README.md +0 -0
  16. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/RELEASING.md +0 -0
  17. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/assets/reverse-api-banner.svg +0 -0
  18. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/assets/reverse-api-engineer.gif +0 -0
  19. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/assets/reverse-api-logo.svg +0 -0
  20. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/.claude/settings.local.json +0 -0
  21. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/components.json +0 -0
  22. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/package-lock.json +0 -0
  23. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/package.json +0 -0
  24. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/postcss.config.js +0 -0
  25. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/public/_locales/en/messages.json +0 -0
  26. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/public/icons/icon-128.png +0 -0
  27. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/public/icons/icon-16.png +0 -0
  28. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/public/icons/icon-32.png +0 -0
  29. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/public/icons/icon-48.png +0 -0
  30. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/public/manifest.json +0 -0
  31. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/background/service-worker.ts +0 -0
  32. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/agent-action.tsx +0 -0
  33. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/chat-input.tsx +0 -0
  34. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/icons.tsx +0 -0
  35. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/markdown-renderer.tsx +0 -0
  36. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/mode-selector.tsx +0 -0
  37. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/plan.tsx +0 -0
  38. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/session-selector.tsx +0 -0
  39. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/terminal.tsx +0 -0
  40. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/components/ui/code-block.tsx +0 -0
  41. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/content/codegen-recorder.ts +0 -0
  42. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/index.css +0 -0
  43. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/shared/capture.ts +0 -0
  44. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/shared/native-host.ts +0 -0
  45. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/shared/storage.ts +0 -0
  46. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/shared/types.ts +0 -0
  47. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/sidepanel/index.html +0 -0
  48. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/sidepanel/main.tsx +0 -0
  49. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/sidepanel/side-panel.tsx +0 -0
  50. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/src/vite-env.d.ts +0 -0
  51. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/tailwind.config.js +0 -0
  52. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/tsconfig.json +0 -0
  53. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/chrome-extension/vite.config.ts +0 -0
  54. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/INDEX.md +0 -0
  55. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/QUICKSTART.md +0 -0
  56. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/README.md +0 -0
  57. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/SUMMARY.md +0 -0
  58. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/api_client.py +0 -0
  59. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/extract_job_fields.py +0 -0
  60. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/main.py +0 -0
  61. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/quick_example.py +0 -0
  62. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/apple/requirements.txt +0 -0
  63. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ashby/API_SUMMARY.txt +0 -0
  64. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ashby/QUICKSTART.md +0 -0
  65. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ashby/README.md +0 -0
  66. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ashby/api_client.py +0 -0
  67. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ashby/example_usage.py +0 -0
  68. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ashby/requirements.txt +0 -0
  69. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/autoscout24/README.md +0 -0
  70. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/autoscout24/SUMMARY.md +0 -0
  71. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/autoscout24/api_client.py +0 -0
  72. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ikea/README.md +0 -0
  73. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/ikea/api_client.py +0 -0
  74. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/mintlify/README.md +0 -0
  75. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/mintlify/api_client.py +0 -0
  76. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/uber/API_ANALYSIS_SUMMARY.md +0 -0
  77. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/uber/README.md +0 -0
  78. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/uber/api_client.py +0 -0
  79. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/uber/example_fetch_all_jobs.py +0 -0
  80. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/uber/quick_start.py +0 -0
  81. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/examples/uber/requirements.txt +0 -0
  82. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/llm-docs/OPENCODE_API_SUMMARY.md +0 -0
  83. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/llm-docs/claude-agent-sdk/QUICKSTART.md +0 -0
  84. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/llm-docs/claude-agent-sdk/TODO_LIST.md +0 -0
  85. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/llm-docs/claude-agent-sdk/TOOLS.md +0 -0
  86. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/llm-docs/opencode-api.json +0 -0
  87. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/.claude-plugin/plugin.json +0 -0
  88. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/CHANGELOG.md +0 -0
  89. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/LICENSE +0 -0
  90. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/README.md +0 -0
  91. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/agents/api-reverse-engineer.md +0 -0
  92. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/commands/agent.md +0 -0
  93. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/commands/capture.md +0 -0
  94. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/commands/engineer.md +0 -0
  95. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/commands/manual.md +0 -0
  96. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/CHANGELOG.md +0 -0
  97. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/LICENSE +0 -0
  98. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/SKILL.md +0 -0
  99. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/AUTH_PATTERNS.md +0 -0
  100. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/references/HAR_ANALYSIS.md +0 -0
  101. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_analyze.py +0 -0
  102. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_filter.py +0 -0
  103. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_utils.py +0 -0
  104. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/scripts/har_validate.py +0 -0
  105. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/plugins/reverse-api-engineer/skills/reverse-engineering-api/templates/api_client.py +0 -0
  106. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/scripts/clean_build.sh +0 -0
  107. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/__init__.py +0 -0
  108. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/action_recorder.py +0 -0
  109. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/auto_engineer.py +0 -0
  110. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/browser.py +0 -0
  111. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/cli.py +0 -0
  112. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/collector.py +0 -0
  113. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/collector_ui.py +0 -0
  114. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/config.py +0 -0
  115. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/copilot_engineer.py +0 -0
  116. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/engineer.py +0 -0
  117. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/messages.py +0 -0
  118. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/native_host.py +0 -0
  119. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/opencode_engineer.py +0 -0
  120. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/opencode_ui.py +0 -0
  121. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/playwright_codegen.py +0 -0
  122. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/pricing.py +0 -0
  123. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/session.py +0 -0
  124. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/sync.py +0 -0
  125. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/tui.py +0 -0
  126. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/src/reverse_api/utils.py +0 -0
  127. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/__init__.py +0 -0
  128. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/conftest.py +0 -0
  129. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_action_recorder.py +0 -0
  130. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_auto_engineer.py +0 -0
  131. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_collector.py +0 -0
  132. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_collector_ui.py +0 -0
  133. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_config.py +0 -0
  134. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_engineer.py +0 -0
  135. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_init.py +0 -0
  136. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_messages.py +0 -0
  137. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_native_host.py +0 -0
  138. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_opencode_engineer.py +0 -0
  139. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_opencode_ui.py +0 -0
  140. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_pricing.py +0 -0
  141. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_session.py +0 -0
  142. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_sync.py +0 -0
  143. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_tui.py +0 -0
  144. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/tests/test_utils.py +0 -0
  145. {reverse_api_engineer-0.4.1 → reverse_api_engineer-0.4.2}/uv.lock +0 -0
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.2] - 2026-03-12
9
+
10
+ ### Fixed
11
+ - **Engineer client resolution**: Prefer the active engineer client recorded in session history over config defaults, preventing iterative edits from switching to the wrong file when multiple language clients exist
12
+
8
13
  ## [0.4.1] - 2026-03-12
9
14
 
10
15
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reverse-api-engineer
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: A tool to capture browser traffic for API reverse engineering
5
5
  Project-URL: Homepage, https://github.com/kalil0321/reverse-api-engineer
6
6
  Project-URL: Repository, https://github.com/kalil0321/reverse-api-engineer
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reverse-api-engineer"
3
- version = "0.4.1"
3
+ version = "0.4.2"
4
4
  description = "A tool to capture browser traffic for API reverse engineering"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -7,9 +7,10 @@ from typing import Any
7
7
  import questionary
8
8
 
9
9
  from .messages import MessageStore
10
+ from .session import SessionManager
10
11
  from .sync import FileSyncWatcher, get_available_directory
11
12
  from .tui import THEME_PRIMARY, THEME_SECONDARY, ClaudeUI
12
- from .utils import generate_folder_name, get_docs_dir, get_scripts_dir
13
+ from .utils import generate_folder_name, get_docs_dir, get_history_path, get_scripts_dir
13
14
 
14
15
 
15
16
  class BaseEngineer(ABC):
@@ -244,26 +245,69 @@ class BaseEngineer(ABC):
244
245
  candidates[language] = client_path
245
246
  return candidates
246
247
 
247
- def _resolve_output_language(self, requested_language: str) -> str:
248
- """Keep iterative edits in the same language as the existing client."""
248
+ def _get_recorded_client_path(self, existing_clients: dict[str, Path] | None = None) -> Path | None:
249
+ """Return the last generated client path recorded in session history."""
249
250
  if self.output_mode == "docs" or self.is_fresh:
250
- return requested_language
251
+ return None
252
+
253
+ try:
254
+ session_manager = SessionManager(get_history_path())
255
+ run_data = session_manager.get_run(self.run_id)
256
+ except Exception:
257
+ return None
258
+
259
+ if not run_data:
260
+ return None
261
+
262
+ script_path = run_data.get("paths", {}).get("script_path")
263
+ if not script_path:
264
+ return None
265
+
266
+ resolved_path = Path(script_path)
267
+ if not resolved_path.exists():
268
+ return None
269
+
270
+ candidates = existing_clients or self._get_existing_client_candidates()
271
+ if resolved_path not in candidates.values():
272
+ return None
273
+
274
+ return resolved_path
275
+
276
+ def _get_preferred_existing_client(self) -> tuple[str, Path] | None:
277
+ """Return the existing client that iterative edits should continue from."""
278
+ if self.output_mode == "docs" or self.is_fresh:
279
+ return None
251
280
 
252
281
  existing_clients = self._get_existing_client_candidates()
253
282
  if not existing_clients:
254
- return requested_language
283
+ return None
255
284
 
256
- if requested_language in existing_clients:
257
- return requested_language
285
+ recorded_client_path = self._get_recorded_client_path(existing_clients)
286
+ if recorded_client_path:
287
+ for language, client_path in existing_clients.items():
288
+ if client_path == recorded_client_path:
289
+ return language, client_path
258
290
 
259
291
  return max(
260
292
  existing_clients.items(),
261
293
  key=lambda item: item[1].stat().st_mtime_ns,
262
- )[0]
294
+ )
295
+
296
+ def _resolve_output_language(self, requested_language: str) -> str:
297
+ """Keep iterative edits in the same language as the existing client."""
298
+ if self.output_mode == "docs" or self.is_fresh:
299
+ return requested_language
300
+
301
+ preferred_client = self._get_preferred_existing_client()
302
+ if preferred_client:
303
+ return preferred_client[0]
304
+
305
+ return requested_language
263
306
 
264
307
  def _get_existing_client_path(self) -> Path | None:
265
308
  """Return the current client path when iterating on an existing run."""
266
- return self._get_existing_client_candidates().get(self.output_language)
309
+ preferred_client = self._get_preferred_existing_client()
310
+ return preferred_client[1] if preferred_client else None
267
311
 
268
312
  def _get_language_name(self) -> str:
269
313
  """Return a human-readable language name."""
@@ -69,17 +69,74 @@ class TestBaseEngineerInit:
69
69
 
70
70
  with patch("reverse_api.base_engineer.get_scripts_dir", return_value=scripts_dir):
71
71
  with patch("reverse_api.base_engineer.MessageStore"):
72
- engineer = ConcreteEngineer(
73
- run_id="test123",
74
- har_path=har_path,
75
- prompt="test prompt",
76
- output_language="python",
77
- output_dir=str(tmp_path),
78
- )
72
+ with patch("reverse_api.base_engineer.SessionManager") as mock_session_manager:
73
+ mock_session_manager.return_value.get_run.return_value = None
74
+ engineer = ConcreteEngineer(
75
+ run_id="test123",
76
+ har_path=har_path,
77
+ prompt="test prompt",
78
+ output_language="python",
79
+ output_dir=str(tmp_path),
80
+ )
79
81
 
80
82
  assert engineer.output_language == "typescript"
81
83
  assert engineer.existing_client_path == client_path
82
84
 
85
+ def test_existing_client_language_uses_recorded_script_path(self, tmp_path):
86
+ """Recorded script path takes precedence over config and stale files."""
87
+ har_path = tmp_path / "test.har"
88
+ har_path.touch()
89
+ scripts_dir = tmp_path / "scripts"
90
+ scripts_dir.mkdir()
91
+ python_client = scripts_dir / "api_client.py"
92
+ python_client.write_text("print('python')\n")
93
+ typescript_client = scripts_dir / "api_client.ts"
94
+ typescript_client.write_text("export {};\n")
95
+
96
+ with patch("reverse_api.base_engineer.get_scripts_dir", return_value=scripts_dir):
97
+ with patch("reverse_api.base_engineer.MessageStore"):
98
+ with patch("reverse_api.base_engineer.SessionManager") as mock_session_manager:
99
+ mock_session_manager.return_value.get_run.return_value = {
100
+ "paths": {"script_path": str(typescript_client)}
101
+ }
102
+ engineer = ConcreteEngineer(
103
+ run_id="test123",
104
+ har_path=har_path,
105
+ prompt="test prompt",
106
+ output_language="python",
107
+ output_dir=str(tmp_path),
108
+ )
109
+
110
+ assert engineer.output_language == "typescript"
111
+ assert engineer.existing_client_path == typescript_client
112
+
113
+ def test_existing_client_language_falls_back_to_newest_file(self, tmp_path):
114
+ """Newest existing client wins when no recorded script path is available."""
115
+ har_path = tmp_path / "test.har"
116
+ har_path.touch()
117
+ scripts_dir = tmp_path / "scripts"
118
+ scripts_dir.mkdir()
119
+ python_client = scripts_dir / "api_client.py"
120
+ python_client.write_text("print('python')\n")
121
+ typescript_client = scripts_dir / "api_client.ts"
122
+ typescript_client.write_text("export {};\n")
123
+ typescript_client.touch()
124
+
125
+ with patch("reverse_api.base_engineer.get_scripts_dir", return_value=scripts_dir):
126
+ with patch("reverse_api.base_engineer.MessageStore"):
127
+ with patch("reverse_api.base_engineer.SessionManager") as mock_session_manager:
128
+ mock_session_manager.return_value.get_run.return_value = None
129
+ engineer = ConcreteEngineer(
130
+ run_id="test123",
131
+ har_path=har_path,
132
+ prompt="test prompt",
133
+ output_language="python",
134
+ output_dir=str(tmp_path),
135
+ )
136
+
137
+ assert engineer.output_language == "typescript"
138
+ assert engineer.existing_client_path == typescript_client
139
+
83
140
  def test_fresh_runs_can_switch_output_language(self, tmp_path):
84
141
  """Fresh runs ignore existing client language and honor the requested one."""
85
142
  har_path = tmp_path / "test.har"
@@ -255,14 +312,16 @@ class TestBaseEngineerBuildPrompt:
255
312
  with patch("reverse_api.base_engineer.get_scripts_dir", return_value=scripts_dir):
256
313
  with patch("reverse_api.base_engineer.get_docs_dir", return_value=tmp_path / "docs"):
257
314
  with patch("reverse_api.base_engineer.MessageStore") as mock_ms:
258
- mock_ms.return_value.messages_path = tmp_path / "messages" / "test.jsonl"
259
- eng = ConcreteEngineer(
260
- run_id="test123",
261
- har_path=har_path,
262
- prompt="test prompt",
263
- output_language="python",
264
- output_dir=str(tmp_path),
265
- )
315
+ with patch("reverse_api.base_engineer.SessionManager") as mock_session_manager:
316
+ mock_ms.return_value.messages_path = tmp_path / "messages" / "test.jsonl"
317
+ mock_session_manager.return_value.get_run.return_value = None
318
+ eng = ConcreteEngineer(
319
+ run_id="test123",
320
+ har_path=har_path,
321
+ prompt="test prompt",
322
+ output_language="python",
323
+ output_dir=str(tmp_path),
324
+ )
266
325
 
267
326
  prompt = eng._build_analysis_prompt()
268
327
  assert str(client_path) in prompt