tan-cli 0.7.0__py3-none-any.whl

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 (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,377 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """`tan inspect` -- show resolved project/debug context values.
3
+
4
+ Port of `crates/tan-cli/src/commands/inspect.rs` + the `tan_core::debug`
5
+ model it reads (`crates/tan-core/src/debug/{context,inspect}.rs`). Builds the
6
+ same six-row "resolved debug context" -- `workspaceRoot`, `sdkRoot`,
7
+ `boardYamlPath`, `boardYamlExists`, `westCwd`, `pythonBinary` -- that `tan
8
+ trace`/`tan support-bundle` (#257) also need. [`resolve_debug_project_context`]
9
+ and [`collect_resolved_values`] below are this port's ONE copy of that model:
10
+ `trace_cmd.py` and `support_bundle_cmd.py` import both directly rather than
11
+ re-deriving them, mirroring how the Rust `tan_core::debug` module is the one
12
+ place all three commands read it from.
13
+
14
+ **Established by RUNNING the oracle (`target/debug/tan.exe`), not by reading
15
+ `crates/`** -- issues #258/#260/#261 all record source-reading alone producing
16
+ a wrong answer here twice already. Every shape below (the six rows, their
17
+ `source`/`detail` strings, the JSON key order, the mixed-separator `outputPath`
18
+ shape `trace`/`support-bundle` share) was measured against a freshly-built
19
+ oracle from THIS worktree's `crates/` (`cargo build -p alp-tan-cli --bin tan`),
20
+ not a possibly-stale `dev`-branch checkout's binary -- the two disagree on
21
+ `Project.boardYaml`'s existence-filtering (tan-cli#236, landed on this
22
+ worktree's branch, not yet on `dev`), which is exactly the kind of mismatch
23
+ RUNNING catches and reading `crates/` alone would not.
24
+
25
+ **Which SDK ladder.** `inspect`/`trace` are two of the thirteen commands
26
+ `tan.core.sdk_discovery.resolve_sdk_root_ladder`'s own docstring names -- measured against
27
+ the oracle -- as resolving the LATERAL/narrow ladder (the same one
28
+ `doctor_cmd.doctor` uses), not the wide `init`/`generate`/`examples`
29
+ one. This file calls `resolve_sdk_root_ladder` directly for that reason,
30
+ rather than `build_output.resolve_project_context`'s narrower
31
+ `resolve_sdk_tiered` (no positional-walk tail) that `size`/`image`/
32
+ `debug-config` use for their own, separately-documented reason.
33
+
34
+ **Always-populated fields.** No `--west-cwd`/`--python-path` flag exists on
35
+ this CLI surface at all (the oracle's own `GlobalArgs` carries neither), so
36
+ `westCwd` always equals the resolved `workspaceRoot` and `pythonBinary` is
37
+ always the per-platform default (`python` on Windows, `python3` elsewhere) --
38
+ verified against the oracle: every resolved-values row for these two keys
39
+ reports source `setting`/`default` respectively, in every argument shape
40
+ tried, never `unresolved`/`setting`-from-an-override. The Rust
41
+ `collect_resolved_values`'s "nothing resolved" branches for `workspaceRoot`/
42
+ `boardYamlPath`/`westCwd` are therefore dead code in THIS port's construction
43
+ (the workspace root and the joined board.yaml path are always non-null once a
44
+ command reaches this point) and are not reproduced here.
45
+
46
+ `tan inspect` itself has no failure exit in the oracle -- verified against
47
+ every argument shape tried (missing SDK, missing board.yaml, an empty
48
+ `--path`, `--path` matching nothing): always exit 0, with an `issues` entry
49
+ carrying the bad news instead.
50
+ """
51
+
52
+ from __future__ import annotations
53
+
54
+ import json
55
+ import os
56
+ from dataclasses import dataclass
57
+ from pathlib import Path
58
+ from typing import Any
59
+
60
+ import typer
61
+
62
+ from tan.commands.sdk_cmd import NO_SDK_NEXT_STEPS
63
+ from tan.core.sdk_discovery import _abs_posix, resolve_sdk_root_ladder, sdk_resolution_issues
64
+ from tan.core.timestamp import generated_at_iso
65
+ from tan.envelope import Envelope, Issue, Project, SdkInfo, emit
66
+ from tan.exit_codes import ExitCode
67
+ from tan.output_format import FORMAT_HELP, OutputFormat, resolve_format
68
+
69
+ #: `data.schemaVersion` for this command's payload.
70
+ DATA_SCHEMA_VERSION = "1"
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class ResolvedDebugContext:
75
+ """The resolved project/debug context `inspect`/`trace`/`support-bundle`
76
+ all build from the same four CLI inputs (`--project`/`--board-yaml`/
77
+ `--sdk-root`, plus the always-resolved host defaults). Mirrors the shape
78
+ `tan_core::debug::DebugWorkspaceContext` carries, minus the two fields
79
+ that only mean something inside an IDE extension host
80
+ (`project_selected`/`debugger_extensions`) -- the standalone CLI has no
81
+ reader for either (see `doctor_cmd`'s own note on the same gap)."""
82
+
83
+ #: Posix, absolute -- always resolved (cwd, or `--project` joined onto it).
84
+ workspace_root: str
85
+ #: Posix, absolute, or `None` when no alp-sdk checkout resolved.
86
+ sdk_root: str | None
87
+ #: The `SdkSourceTier` string `resolve_sdk_root_ladder` answered with
88
+ #: (`"sdkRootFlag"`/`"projectPin"`/`"globalDefault"`/`"discovery"`/`"none"`).
89
+ sdk_tier: str
90
+ #: Posix, absolute -- the resolved location, joined onto `workspace_root`
91
+ #: unconditionally (mirrors `project.rs::resolve_board_yaml_path`): this is
92
+ #: WHERE a board.yaml would live, whether or not one is actually there.
93
+ board_yaml_path: str
94
+ #: Whether a real file sits at `board_yaml_path` (probed once, here).
95
+ board_yaml_exists: bool
96
+ #: Always equals `workspace_root` -- see the module docstring.
97
+ west_cwd: str
98
+ #: Always the per-platform default -- see the module docstring.
99
+ python_binary: str
100
+ #: The envelope `project` block (`board_yaml` present only when the file
101
+ #: really exists -- `Project.resolved` applies that filter).
102
+ project: Project
103
+ #: The envelope `sdk` block, or `None` when nothing resolved.
104
+ sdk: SdkInfo | None
105
+ #: tan-cli#478: the project `~/.alp/sdk-default` was written FOR, when the
106
+ #: SDK resolved through `globalDefault` and that project is not this one.
107
+ #: `resolve_sdk_root_ladder` has always answered with it; this context
108
+ #: dropped it, so `inspect`/`trace`/`support-bundle` reported
109
+ #: `sourceTier: "globalDefault"` pointing at ANOTHER project's checkout
110
+ #: with `ok: true` and `issues: []` -- the exact silence tan-cli#464 closed
111
+ #: for `doctor`/`generate`/`build`/`presets`/`examples` and no one else.
112
+ #: `None` when the pointer is this project's own, or was never consulted.
113
+ foreign_global_default_for: str | None = None
114
+ #: The unreadable/unresolvable `.alp/sdk-path` pin, same carry-through.
115
+ #: Paired with the field above because `sdk_resolution_issues` emits the
116
+ #: two together, in that order, and a caller holding one but not the other
117
+ #: can only re-open half the hole.
118
+ broken_project_pin: str | None = None
119
+
120
+
121
+ def resolve_debug_project_context(
122
+ project_arg: str | None, board_yaml_arg: str | None, sdk_root_arg: str | None
123
+ ) -> ResolvedDebugContext:
124
+ """Resolve `--project`/`--board-yaml`/`--sdk-root` into the shared debug
125
+ context. Mirrors `doctor_cmd.doctor`'s own workspace-root/board-yaml/SDK
126
+ preprocessing (same ladder, same `--board-yaml` anchoring rule) rather than
127
+ `build_output.resolve_project_context`'s -- see the module docstring for why
128
+ these two commands take the narrow ladder specifically.
129
+ """
130
+ cwd = Path.cwd()
131
+ workspace_root_path = (
132
+ cwd if project_arg is None else Path(os.path.join(str(cwd), project_arg))
133
+ )
134
+ workspace_root = _abs_posix(str(workspace_root_path))
135
+
136
+ configured = board_yaml_arg or "board.yaml"
137
+ if os.path.isabs(configured):
138
+ board_yaml_path = _abs_posix(configured)
139
+ else:
140
+ board_yaml_path = _abs_posix(os.path.join(str(workspace_root_path), configured))
141
+ board_yaml_exists = os.path.isfile(board_yaml_path)
142
+
143
+ sdk_resolution = resolve_sdk_root_ladder(sdk_root_arg, workspace_root_path)
144
+ resolved_sdk = sdk_resolution.path
145
+ sdk_tier = sdk_resolution.tier
146
+ sdk_root = _abs_posix(str(resolved_sdk)) if resolved_sdk is not None else None
147
+ sdk = SdkInfo.from_resolution(sdk_root, sdk_resolution) if sdk_root is not None else None
148
+
149
+ python_binary = "python" if os.name == "nt" else "python3"
150
+
151
+ return ResolvedDebugContext(
152
+ workspace_root=workspace_root,
153
+ sdk_root=sdk_root,
154
+ sdk_tier=sdk_tier,
155
+ board_yaml_path=board_yaml_path,
156
+ board_yaml_exists=board_yaml_exists,
157
+ west_cwd=workspace_root,
158
+ python_binary=python_binary,
159
+ project=Project.resolved(workspace_root, board_yaml_path),
160
+ sdk=sdk,
161
+ # tan-cli#478: carried, not recomputed. The ladder already answered
162
+ # with both; dropping them here is what left three commands silent
163
+ # about resolving another project's SDK.
164
+ foreign_global_default_for=sdk_resolution.foreign_global_default_for,
165
+ broken_project_pin=sdk_resolution.broken_project_pin,
166
+ )
167
+
168
+
169
+ def collect_resolved_values(context: ResolvedDebugContext) -> list[dict[str, Any]]:
170
+ """The six resolved-value rows, in the oracle's fixed order. Port of
171
+ `tan_core::debug::inspect::collect_resolved_values`, narrowed to the
172
+ branches this port's [`ResolvedDebugContext`] can actually produce -- see
173
+ the module docstring's "Always-populated fields" note."""
174
+ return [
175
+ {
176
+ "key": "workspaceRoot",
177
+ "value": context.workspace_root,
178
+ "source": "workspace",
179
+ "detail": "Resolved project directory (cwd, or --project <dir>).",
180
+ },
181
+ {
182
+ "key": "sdkRoot",
183
+ "value": context.sdk_root,
184
+ "source": "workspace" if context.sdk_root is not None else "unresolved",
185
+ "detail": (
186
+ "Resolved alp-sdk root used for scripts and schemas."
187
+ if context.sdk_root is not None
188
+ # tan-cli#381: named `tan sdk switch <path>`, which refuses in
189
+ # this build (`sdk_cmd._run_not_ported`) -- the #305 dead end,
190
+ # re-introduced by a port written after that fix landed. The
191
+ # unresolved arm is the only one a reader acts on, so it takes
192
+ # the shared NO_SDK_NEXT_STEPS instead of its own wording.
193
+ else f"Unresolved -- {NO_SDK_NEXT_STEPS}."
194
+ ),
195
+ },
196
+ {
197
+ "key": "boardYamlPath",
198
+ "value": context.board_yaml_path,
199
+ "source": "setting",
200
+ "detail": "Resolved board.yaml path (default location, or --board-yaml <path>).",
201
+ },
202
+ {
203
+ "key": "boardYamlExists",
204
+ "value": context.board_yaml_exists,
205
+ "source": "runtime",
206
+ "detail": (
207
+ "board.yaml exists at the resolved path."
208
+ if context.board_yaml_exists
209
+ else "board.yaml is missing at the resolved path."
210
+ ),
211
+ },
212
+ {
213
+ "key": "westCwd",
214
+ "value": context.west_cwd,
215
+ "source": "setting",
216
+ "detail": "Working directory used for west commands.",
217
+ },
218
+ {
219
+ "key": "pythonBinary",
220
+ "value": context.python_binary,
221
+ "source": "default",
222
+ "detail": "Interpreter used for loader and validation scripts.",
223
+ },
224
+ ]
225
+
226
+
227
+ def filter_resolved_values(
228
+ values: list[dict[str, Any]], focus: str | None
229
+ ) -> list[dict[str, Any]]:
230
+ """`focus is None` passes everything through; otherwise keep a value whose
231
+ `key` equals `focus` or is nested under it (a `focus.` dotted or `focus[`
232
+ indexed prefix) -- mirrors `inspect.rs::filter_resolved_values`."""
233
+ if focus is None:
234
+ return values
235
+ dot = f"{focus}."
236
+ bracket = f"{focus}["
237
+ return [
238
+ v
239
+ for v in values
240
+ if v["key"] == focus or v["key"].startswith(dot) or v["key"].startswith(bracket)
241
+ ]
242
+
243
+
244
+ def _format_value_text(value: Any) -> str:
245
+ """Strings JSON-quoted, everything else its compact JSON form -- mirrors
246
+ Rust's `format_value` (`Value::String` -> `serde_json::to_string`, else
247
+ `Value::to_string()`).
248
+
249
+ `ensure_ascii=False`, for the same reason `diff_cmd._format_value` carries
250
+ it: `serde_json::to_string` emits raw UTF-8 and never a `\\uXXXX` escape, so
251
+ the default `ensure_ascii=True` printed `/home/<user>/jos\\u00e9-proj` where
252
+ the oracle prints `/home/<user>/josé-proj` -- a path that no longer equals
253
+ disk and can no longer be pasted back into a shell. Every OTHER scalar
254
+ (`true`/`false`/`null`/a bare number) renders identically either way, so
255
+ this is the only character class the two languages ever disagreed on.
256
+ """
257
+ return json.dumps(value, ensure_ascii=False)
258
+
259
+
260
+ def _inspect_text_lines(
261
+ values: list[dict[str, Any]], focus: str | None, show_origin: bool, quiet: bool
262
+ ) -> list[str]:
263
+ lines = [f"inspect: resolved values={len(values)}"]
264
+ if focus is not None:
265
+ lines.append(f"path={focus}")
266
+ if not quiet:
267
+ for v in values:
268
+ rendered = _format_value_text(v["value"])
269
+ if show_origin:
270
+ lines.append(
271
+ f"{v['key']}={rendered} source={v['source']} "
272
+ f"detail={json.dumps(v['detail'], ensure_ascii=False)}"
273
+ )
274
+ else:
275
+ lines.append(f"{v['key']}={rendered}")
276
+ return lines
277
+
278
+
279
+ def inspect(
280
+ ctx: typer.Context,
281
+ path: str = typer.Option(
282
+ None, "--path", metavar="PATH", help="Limit output to resolved values under this key path."
283
+ ),
284
+ project: str = typer.Option(
285
+ None, "--project", metavar="PATH", help="Project root (defaults to current directory)."
286
+ ),
287
+ board_yaml: str = typer.Option(
288
+ None,
289
+ "--board-yaml",
290
+ metavar="PATH",
291
+ help="Explicit board.yaml path (overrides project resolution).",
292
+ ),
293
+ show_origin: bool = typer.Option(
294
+ False, "--show-origin", help="Include source + detail metadata for each value."
295
+ ),
296
+ sdk_root: str = typer.Option(
297
+ None, "--sdk-root", metavar="PATH", help="alp-sdk checkout root."
298
+ ),
299
+ output_format: OutputFormat = typer.Option(None, "--format", help=FORMAT_HELP),
300
+ quiet: bool = typer.Option(False, "--quiet", help="Suppress non-essential output."),
301
+ verbose: bool = typer.Option(False, "--verbose", hidden=True),
302
+ no_color: bool = typer.Option(False, "--no-color", hidden=True),
303
+ non_interactive: bool = typer.Option(False, "--non-interactive", hidden=True),
304
+ ci: bool = typer.Option(False, "--ci", hidden=True),
305
+ target: str = typer.Option(None, "--target", hidden=True),
306
+ all_targets: bool = typer.Option(False, "--all", hidden=True),
307
+ ) -> None:
308
+ """Inspect resolved project/debug context values.
309
+
310
+ `--verbose`/`--no-color`/`--non-interactive`/`--ci`/`--target`/`--all` are
311
+ accepted and ignored: clap makes every one of them `global = true` in the
312
+ oracle, so `tan inspect --ci` (etc.) must not be a Click usage error even
313
+ though `inspect.rs` never reads any of them.
314
+ """
315
+ del verbose, no_color, non_interactive, ci, target, all_targets
316
+ resolved_format = resolve_format(output_format, ctx.obj, choices=OutputFormat)
317
+ json_mode = resolved_format == "json"
318
+
319
+ generated_at = generated_at_iso(millis=True)
320
+ context = resolve_debug_project_context(project, board_yaml, sdk_root)
321
+
322
+ # tan-cli#478: FIRST, and unconditionally. `inspect`'s whole job is to say
323
+ # which SDK a build would use, so resolving another project's checkout
324
+ # through `globalDefault` is the single most load-bearing thing it can
325
+ # report -- and it reported the root while saying nothing about whose it
326
+ # was. Same helper, same order, as `size`/`image`/`flash`.
327
+ issues: list[Issue] = sdk_resolution_issues(
328
+ context.broken_project_pin, context.sdk_tier, context.foreign_global_default_for
329
+ )
330
+ if not context.board_yaml_exists:
331
+ issues.append(
332
+ Issue(
333
+ "inspect.board-yaml-missing",
334
+ "warning",
335
+ "board.yaml path could not be resolved or the file does not exist.",
336
+ )
337
+ )
338
+
339
+ focus = path
340
+ values = filter_resolved_values(collect_resolved_values(context), focus)
341
+ if focus is not None and not values:
342
+ issues.append(
343
+ Issue(
344
+ "inspect.path-not-found",
345
+ "warning",
346
+ f"No resolved values match --path '{focus}'.",
347
+ )
348
+ )
349
+
350
+ data = {
351
+ "schemaVersion": DATA_SCHEMA_VERSION,
352
+ "generatedAt": generated_at,
353
+ "focusPath": focus,
354
+ "showOrigin": show_origin,
355
+ "resolvedValues": values,
356
+ }
357
+
358
+ if not json_mode:
359
+ # tan-cli#478 review finding 6: `issues` has carried the
360
+ # foreign-default/broken-pin pair since the top of this function, but
361
+ # only the JSON branch below ever read it -- the DEFAULT text path
362
+ # stayed silent on the exact fact this command exists to disclose
363
+ # ("which SDK a build would use"). Printed first and unconditionally
364
+ # (unlike the `--quiet`-suppressed resolved-value lines).
365
+ for issue in issues:
366
+ if issue.code.startswith("sdk."):
367
+ typer.echo(issue.message, err=True)
368
+ for line in _inspect_text_lines(values, focus, show_origin, quiet):
369
+ typer.echo(line, err=True)
370
+
371
+ if json_mode:
372
+ emit(
373
+ Envelope(
374
+ "inspect", context.project, data, issues, ExitCode.SUCCESS, sdk=context.sdk
375
+ )
376
+ )
377
+ raise typer.Exit(int(ExitCode.SUCCESS))