microduck-cli 0.9.5__tar.gz → 0.9.6__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 (203) hide show
  1. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/CHANGELOG.md +15 -0
  2. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/PKG-INFO +2 -2
  3. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/README.md +1 -1
  4. microduck_cli-0.9.6/docs/verification/2026-09-08-thor-orin-tutorial-run.md +106 -0
  5. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/doctor.py +18 -8
  6. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/env/doctor.py +41 -9
  7. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/pyproject.toml +1 -1
  8. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_env_doctor.py +59 -0
  9. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/uv.lock +1 -1
  10. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/agent-config/SKILL.md +0 -0
  11. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  12. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/agent-config/scripts/show.sh +0 -0
  13. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/ask-colleague/SKILL.md +0 -0
  14. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  15. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  16. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  17. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  18. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  19. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  20. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/challenge/SKILL.md +0 -0
  21. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/cicd/SKILL.md +0 -0
  22. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  23. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  24. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  25. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  26. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  27. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/SKILL.md +0 -0
  28. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  29. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  30. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  31. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  32. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  33. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  34. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/deviate/SKILL.md +0 -0
  35. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  36. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  37. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/operate-microduck/SKILL.md +0 -0
  38. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  39. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  40. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/recall/SKILL.md +0 -0
  41. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/recall/scripts/recall.sh +0 -0
  42. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/remember/SKILL.md +0 -0
  43. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/remember/scripts/remember.sh +0 -0
  44. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/run-tests/SKILL.md +0 -0
  45. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/run-tests/scripts/test.sh +0 -0
  46. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/scope/SKILL.md +0 -0
  47. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/sonarclaude/SKILL.md +0 -0
  48. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  49. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  50. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  51. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/summarize-delivery/SKILL.md +0 -0
  52. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/think/SKILL.md +0 -0
  53. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/think/scripts/think.sh +0 -0
  54. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/validate-delivery/SKILL.md +0 -0
  55. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/version-bump/SKILL.md +0 -0
  56. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills/version-bump/scripts/bump.py +0 -0
  57. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.claude/skills.local.yaml.example +0 -0
  58. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/current +0 -0
  59. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/current_plan +0 -0
  60. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/deliveries/jetson-retest-and-ai-lab-tutorial.json +0 -0
  61. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/deliveries/microduck-cli-env-teach-operate-rules.json +0 -0
  62. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/deliveries/orin-sanity.json +0 -0
  63. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/deliveries/readme-as-proof.json +0 -0
  64. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/deliveries/thor-sanity.json +0 -0
  65. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/frames/jetson-retest-and-ai-lab-tutorial.json +0 -0
  66. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/frames/microduck-cli-env-teach-operate-rules.json +0 -0
  67. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/frames/orin-sanity.json +0 -0
  68. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/frames/readme-as-proof.json +0 -0
  69. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/frames/thor-sanity.json +0 -0
  70. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/plans/jetson-retest-and-ai-lab-tutorial.json +0 -0
  71. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/plans/microduck-cli-env-teach-operate-rules.json +0 -0
  72. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/plans/orin-sanity.json +0 -0
  73. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/plans/readme-as-proof.json +0 -0
  74. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.devague/plans/thor-sanity.json +0 -0
  75. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.eidetic/memory/microduck-cli__public.jsonl +0 -0
  76. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.flake8 +0 -0
  77. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.github/workflows/publish.yml +0 -0
  78. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.github/workflows/tests.yml +0 -0
  79. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.gitignore +0 -0
  80. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/.markdownlint-cli2.yaml +0 -0
  81. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/AGENTS.colleague.md +0 -0
  82. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/CLAUDE.md +0 -0
  83. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/LICENSE +0 -0
  84. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/culture.yaml +0 -0
  85. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/deliveries/2026-09-03-microduck-cli-env-teach-operate-rules.md +0 -0
  86. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/deliveries/2026-09-04-orin-sanity.md +0 -0
  87. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/deliveries/2026-09-04-readme-as-proof.md +0 -0
  88. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/deliveries/2026-09-07-jetson-retest-and-ai-lab-tutorial.md +0 -0
  89. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/operating-the-duck.md +0 -0
  90. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/plans/2026-09-03-microduck-cli-env-teach-operate-rules.md +0 -0
  91. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/plans/2026-09-04-orin-sanity.md +0 -0
  92. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/plans/2026-09-04-thor-sanity.md +0 -0
  93. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/plans/2026-09-07-jetson-retest-and-ai-lab-tutorial.md +0 -0
  94. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/skill-sources.md +0 -0
  95. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/specs/2026-09-03-microduck-cli-env-teach-operate-rules.md +0 -0
  96. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/specs/2026-09-04-orin-sanity.md +0 -0
  97. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/specs/2026-09-04-readme-as-proof.md +0 -0
  98. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/specs/2026-09-04-thor-sanity.md +0 -0
  99. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/specs/2026-09-07-jetson-retest-and-ai-lab-tutorial.md +0 -0
  100. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/tools/check_tutorial.py +0 -0
  101. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/upstream-pins.md +0 -0
  102. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/verification/2026-09-04-orin-sanity.md +0 -0
  103. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/verification/2026-09-04-sim-bringup.md +0 -0
  104. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/verification/2026-09-04-thor-sanity.md +0 -0
  105. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/docs/verification/2026-09-07-spark-retest.md +0 -0
  106. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/__init__.py +0 -0
  107. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/__main__.py +0 -0
  108. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/__init__.py +0 -0
  109. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/compose.py +0 -0
  110. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/default_rules.toml +0 -0
  111. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/defaults.py +0 -0
  112. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/engine.py +0 -0
  113. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/human_gate.py +0 -0
  114. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/idle.py +0 -0
  115. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/intents.py +0 -0
  116. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/liveness.py +0 -0
  117. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/model.py +0 -0
  118. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/release.py +0 -0
  119. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/replay.py +0 -0
  120. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/rule_engine.py +0 -0
  121. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/rules.py +0 -0
  122. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/sense.py +0 -0
  123. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/senselog.py +0 -0
  124. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/sink.py +0 -0
  125. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/behavior/skills.py +0 -0
  126. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/__init__.py +0 -0
  127. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/__init__.py +0 -0
  128. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/cli.py +0 -0
  129. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/duck.py +0 -0
  130. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/env.py +0 -0
  131. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/explain.py +0 -0
  132. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/learn.py +0 -0
  133. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/overview.py +0 -0
  134. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/policy.py +0 -0
  135. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/rules.py +0 -0
  136. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_commands/whoami.py +0 -0
  137. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_errors.py +0 -0
  138. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/cli/_output.py +0 -0
  139. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/duck/__init__.py +0 -0
  140. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/duck/addressing.py +0 -0
  141. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/duck/gate.py +0 -0
  142. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/duck/record.py +0 -0
  143. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/env/__init__.py +0 -0
  144. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/env/hosts.py +0 -0
  145. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/env/params.py +0 -0
  146. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/env/stack.py +0 -0
  147. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/explain/__init__.py +0 -0
  148. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/explain/catalog.py +0 -0
  149. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/explain/duck.py +0 -0
  150. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/explain/env.py +0 -0
  151. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/explain/policy.py +0 -0
  152. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/explain/rules.py +0 -0
  153. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/ipc/__init__.py +0 -0
  154. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/ipc/client.py +0 -0
  155. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/ipc/proto.py +0 -0
  156. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/train/__init__.py +0 -0
  157. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/train/artifacts.py +0 -0
  158. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/microduck_cli/train/lane.py +0 -0
  159. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/sonar-project.properties +0 -0
  160. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/__init__.py +0 -0
  161. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/fake_robotd.py +0 -0
  162. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/fixtures/duck_ipc_proto.json +0 -0
  163. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/live/__init__.py +0 -0
  164. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/live/test_live_cli.py +0 -0
  165. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_addressing.py +0 -0
  166. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_check_tutorial.py +0 -0
  167. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_cli.py +0 -0
  168. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_cli_introspection.py +0 -0
  169. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_client.py +0 -0
  170. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_default_rules.py +0 -0
  171. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_duck.py +0 -0
  172. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_engine.py +0 -0
  173. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_env.py +0 -0
  174. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_fake_robotd.py +0 -0
  175. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_gate.py +0 -0
  176. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_hosts.py +0 -0
  177. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_human_gate.py +0 -0
  178. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_idle.py +0 -0
  179. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_intents.py +0 -0
  180. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_lane.py +0 -0
  181. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_learn_skill.py +0 -0
  182. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_links.py +0 -0
  183. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_liveness.py +0 -0
  184. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_lockstep.py +0 -0
  185. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_model.py +0 -0
  186. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_no_config_writes.py +0 -0
  187. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_no_hardware_paths.py +0 -0
  188. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_no_secrets_in_output.py +0 -0
  189. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_params.py +0 -0
  190. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_policy.py +0 -0
  191. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_proto.py +0 -0
  192. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_record.py +0 -0
  193. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_release.py +0 -0
  194. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_replay.py +0 -0
  195. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_rule_engine.py +0 -0
  196. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_rules.py +0 -0
  197. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_rules_cli.py +0 -0
  198. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_sense.py +0 -0
  199. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_senselog.py +0 -0
  200. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_sink.py +0 -0
  201. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_skills.py +0 -0
  202. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_stack.py +0 -0
  203. {microduck_cli-0.9.5 → microduck_cli-0.9.6}/tests/test_zero_deps.py +0 -0
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/). This project
6
6
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.9.6] - 2026-09-08
9
+
10
+ ### Added
11
+
12
+ - `docs/verification/2026-09-08-thor-orin-tutorial-run.md` — the tutorial's operator run on Jetson AGX Thor (PyPI 0.9.4 wheel): the first smoke failed on a torch the clone had re-synced to `+cu129` after slipping back to the pin, the second passed in 26 s beside the production stack; the simulation's memory footprint on Thor (`duck-body` 933 MB, `robotd` 34 MB) and Orin (590 MB / 41 MB, +0.9 GB box-wide, headless); Orin's clone at `a30a9e4` resolves torch from the SBSA index with a plain `uv sync`.
13
+
14
+ ### Changed
15
+
16
+ - `README.md`: the Spark re-test row's `[FAIL]`-under-healthy caveat now points at this fix.
17
+
18
+ ### Fixed
19
+
20
+ - `env doctor` on a wheel install (`uv tool install microduck-cli`) reported both pin checks as "pinned commit unknown" because it read `docs/upstream-pins.md` from a path the wheel does not ship (defect d5 from the 2026-09-07 Spark re-test). The pinned commits are now carried inside the package as `env/doctor.py`'s `PACKAGED_PINS`; a checkout still reads the docs table first, a wheel falls back to the packaged copy, and `tests/test_env_doctor.py` asserts the two never drift.
21
+ - `env doctor` and `doctor` text output print `[WARN]` for a failing warning-severity check and reserve `[FAIL]` for errors, so a `healthy` verdict is never followed by a `[FAIL]` line (a Thor clone on the `microduck_rl#39` branch reads `[WARN] rl_pinned_commit`, as the tutorial says).
22
+
8
23
  ## [0.9.5] - 2026-09-07
9
24
 
10
25
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: microduck-cli
3
- Version: 0.9.5
3
+ Version: 0.9.6
4
4
  Summary: Agent-agnostic CLI for controlling the MicroDuck robot. Any agent, any human, one CLI. Sim-first: every verb is exercised against the real robotd daemon and the MuJoCo simulation.
5
5
  Project-URL: Homepage, https://github.com/agentculture/microduck-cli
6
6
  Project-URL: Issues, https://github.com/agentculture/microduck-cli/issues
@@ -150,7 +150,7 @@ before trusting a number here.
150
150
  | Box | Reached | Result | Caveat |
151
151
  |---|---|---|---|
152
152
  | **DGX Spark** (GB10, aarch64) | all six checks + train smoke | pass; live suite **12 passed, 0 failed** | walking `xfail` |
153
- | **DGX Spark**, re-test 2026-09-07 following the [Jetson AI Lab tutorial](docs/verification/2026-09-07-spark-retest.md) on the **PyPI 0.9.4 wheel**, viewer **windowed** | tutorial steps 1 and 5–11 executed on the wheel (steps 2–4, clone/build/venv, checked against pre-existing state, not re-run) + both live suites + smoke | pass; **12 passed, 1 xfailed** windowed | the wheel's `env doctor` prints two `[FAIL]` pin lines under a `healthy` verdict (pins file not shipped, filed); the smoke needs the GPU free — it failed OOM beside a resident vLLM engine and passed with it paused |
153
+ | **DGX Spark**, re-test 2026-09-07 following the [Jetson AI Lab tutorial](docs/verification/2026-09-07-spark-retest.md) on the **PyPI 0.9.4 wheel**, viewer **windowed** | tutorial steps 1 and 5–11 executed on the wheel (steps 2–4, clone/build/venv, checked against pre-existing state, not re-run) + both live suites + smoke | pass; **12 passed, 1 xfailed** windowed | the wheel's `env doctor` printed two `[FAIL]` pin lines under a `healthy` verdict (pins file not shipped — fixed in 0.9.6: the pins are packaged and warnings render `[WARN]`); the smoke needs the GPU free — it failed OOM beside a resident vLLM engine and passed with it paused |
154
154
  | **Jetson AGX Thor** (JetPack 7) | all six checks, three tiers, headless | pass; **12 passed, 1 xfailed** | ran on an *uncommitted* local torch override; the upstream fix is still open as [microduck_rl#39](https://github.com/pollen-robotics/microduck_rl/pull/39) (issue [#38](https://github.com/pollen-robotics/microduck_rl/issues/38)), so `env doctor`'s `rl_pinned_commit` fails there **by design** until it merges and this repo re-pins |
155
155
  | **Jetson AGX Orin** (L4T R39) | checks 1–4 | pass | the SBSA torch wheel carries no `sm_87` kernels — GPU training is **not available** on Orin at this pin |
156
156
 
@@ -133,7 +133,7 @@ before trusting a number here.
133
133
  | Box | Reached | Result | Caveat |
134
134
  |---|---|---|---|
135
135
  | **DGX Spark** (GB10, aarch64) | all six checks + train smoke | pass; live suite **12 passed, 0 failed** | walking `xfail` |
136
- | **DGX Spark**, re-test 2026-09-07 following the [Jetson AI Lab tutorial](docs/verification/2026-09-07-spark-retest.md) on the **PyPI 0.9.4 wheel**, viewer **windowed** | tutorial steps 1 and 5–11 executed on the wheel (steps 2–4, clone/build/venv, checked against pre-existing state, not re-run) + both live suites + smoke | pass; **12 passed, 1 xfailed** windowed | the wheel's `env doctor` prints two `[FAIL]` pin lines under a `healthy` verdict (pins file not shipped, filed); the smoke needs the GPU free — it failed OOM beside a resident vLLM engine and passed with it paused |
136
+ | **DGX Spark**, re-test 2026-09-07 following the [Jetson AI Lab tutorial](docs/verification/2026-09-07-spark-retest.md) on the **PyPI 0.9.4 wheel**, viewer **windowed** | tutorial steps 1 and 5–11 executed on the wheel (steps 2–4, clone/build/venv, checked against pre-existing state, not re-run) + both live suites + smoke | pass; **12 passed, 1 xfailed** windowed | the wheel's `env doctor` printed two `[FAIL]` pin lines under a `healthy` verdict (pins file not shipped — fixed in 0.9.6: the pins are packaged and warnings render `[WARN]`); the smoke needs the GPU free — it failed OOM beside a resident vLLM engine and passed with it paused |
137
137
  | **Jetson AGX Thor** (JetPack 7) | all six checks, three tiers, headless | pass; **12 passed, 1 xfailed** | ran on an *uncommitted* local torch override; the upstream fix is still open as [microduck_rl#39](https://github.com/pollen-robotics/microduck_rl/pull/39) (issue [#38](https://github.com/pollen-robotics/microduck_rl/issues/38)), so `env doctor`'s `rl_pinned_commit` fails there **by design** until it merges and this repo re-pins |
138
138
  | **Jetson AGX Orin** (L4T R39) | checks 1–4 | pass | the SBSA torch wheel carries no `sm_87` kernels — GPU training is **not available** on Orin at this pin |
139
139
 
@@ -0,0 +1,106 @@
1
+ # Jetson AGX Thor and AGX Orin, 2026-09-08: the tutorial's operator run
2
+
3
+ The Jetson AI Lab tutorial draft
4
+ ([NVIDIA-AI-IOT/jetson-ai-lab#444](https://github.com/NVIDIA-AI-IOT/jetson-ai-lab/pull/444))
5
+ was followed by the operator on Jetson AGX Thor on the **PyPI 0.9.4 wheel**
6
+ (`~/.local/bin/microduck`, `uv tool install`), and its review comments asked
7
+ for numbers this record supplies: the Thor smoke on the wheel, and the
8
+ simulation's memory footprint on both Jetson boards. Everything below was
9
+ run over SSH by the agent at the operator's request, except the first smoke
10
+ attempt, which the operator ran and saved as `1.json`.
11
+
12
+ | | Jetson AGX Thor | Jetson AGX Orin |
13
+ |---|---|---|
14
+ | host | "NVIDIA Thor", JetPack 7 / L4T R38.2.2, kernel `6.8.12-tegra`, CUDA 13.0, 122 GB unified | "Orin (nvgpu)" sm_87, L4T R39, kernel `6.8.12-1021-tegra`, 61 GB unified |
15
+ | CLI | `microduck-cli 0.9.4` from PyPI | `microduck-cli 0.9.4` from PyPI |
16
+ | `microduck` clone | `0cd676d` (the pin) | `0cd676d` (the pin) |
17
+ | `microduck_rl` clone | `a30a9e4` (`fix/jetson-thor-torch-source`, [microduck_rl#39](https://github.com/pollen-robotics/microduck_rl/pull/39)) | `a30a9e4` (same branch) |
18
+ | shared box | the production stack (`prod-*`, `model-gear-*` vLLM engines) up throughout | `model-gear-*` and `prod-*` containers up throughout; 8 GB available |
19
+
20
+ ## Thor: the smoke failed first, on a torch the clone had re-synced
21
+
22
+ The operator's first `policy smoke` (`1.json`, 01:31) failed in
23
+ `ManagerBasedRlEnv.__init__` with `torch.AcceleratorError: CUDA error: no
24
+ kernel image is available for execution on the device`, after torch's own
25
+ warning that the installed build supports `sm_80 sm_90 sm_100 sm_120` and
26
+ Thor is `sm_110`. The clone's reflog showed it had been checked out at
27
+ `a30a9e4` and then moved back to the pinned `29e887e`; the next `uv run
28
+ train` re-synced torch to the `2.9.1+cu129` wheel from PyTorch's index
29
+ (`Uninstalled 1 package … Installed 1 package` at the top of stderr), which
30
+ carries no Thor kernels. The three extra wheels the fix branch adds
31
+ (`nvpl-blas`, `nvpl-lapack`, `nvidia-cudss-cu13`) were still in the venv.
32
+
33
+ Fix: back to the tutorial's Thor tab.
34
+
35
+ ```text
36
+ $ git checkout a30a9e4 && UV_HTTP_TIMEOUT=600 uv sync # in ~/git/microduck_rl
37
+ $ .venv/bin/python -c "import torch"
38
+ ImportError: libnvpl_lapack_lp64_gomp.so.0: cannot open shared object file
39
+ $ .venv/bin/python -c "import mjlab_microduck.tasks, torch; print(torch.__version__, torch.cuda.get_arch_list(), torch.cuda.is_available())"
40
+ 2.9.1 ['sm_110', 'sm_121'] True
41
+ ```
42
+
43
+ The bare `import torch` failure is expected on this branch: `a30a9e4` does
44
+ not use `LD_LIBRARY_PATH`; `mjlab_microduck/_torch_libs.py` pre-loads the two
45
+ undeclared libraries when `mjlab_microduck.tasks` is imported, which every
46
+ train/play path does before torch. A hand probe must import the package
47
+ first. The 2026-09-04 Thor record used `LD_LIBRARY_PATH` because that module
48
+ did not exist yet.
49
+
50
+ ## Thor: the smoke on the wheel, second attempt
51
+
52
+ `free -g` immediately before: `Mem: 122 total, 86 used, 36 available` (the
53
+ tutorial's floor is 20 GB).
54
+
55
+ ```text
56
+ $ WANDB_MODE=offline timeout 30m microduck policy smoke Mjlab-Velocity-Flat-MicroDuck --json
57
+ Warp 1.12.0 initialized: CUDA Toolkit 12.9, Driver 13.0
58
+ "cuda:0" : "NVIDIA Thor" (123 GiB, sm_101, mempool enabled)
59
+ ...
60
+ Iteration time: 2.05s
61
+ Time elapsed: 00:00:09
62
+ {"argv": ["uv", "run", "train", "Mjlab-Velocity-Flat-MicroDuck", "--env.scene.num-envs", "64", "--agent.max_iterations", "5"], "ok": true, "returncode": 0, ...}
63
+ exit 0 elapsed 0:26.02 max RSS 2.8 GB
64
+ ```
65
+
66
+ **Pass.** 26 s wall-clock on a warm Warp kernel cache (the 2026-09-04 cold
67
+ run took 144 s). `docker ps` afterwards: every `prod-*` and `model-gear-*`
68
+ container still `Up`, the three vLLM engines `(healthy)`. `free -g` after:
69
+ `36 available`, unchanged.
70
+
71
+ ## Memory footprint of the simulation
72
+
73
+ RSS of the two processes `env up --sim` starts, read from `/proc/<pid>/status`
74
+ while the duck was standing (`duck init --apply`, `duck enable --apply`):
75
+
76
+ | process | Thor (windowed, up since the operator's run) | Orin (`--headless`, 3 s to healthy) |
77
+ |---|---|---|
78
+ | `duck-body` (`python -m mjlab_microduck.sim.body_server`) | 933 MB | 590 MB |
79
+ | `robotd --sim` | 34 MB | 41 MB |
80
+ | box `used` delta while up | not measured (already running) | +0.9 GB (`free -m`: 54355 → 55268 MB) |
81
+
82
+ So the simulation and the daemon together cost about one gigabyte, an order
83
+ of magnitude below the training smoke's 2.8 GB peak RSS and far below
84
+ Thor's 20 GB floor for the smoke. On Orin the stack ran and stood the duck
85
+ up with 8 GB available beside the production containers; `env down` stopped
86
+ both processes (`body: terminated`; the "something is still listening on
87
+ port 7801" warning cleared on the next check, nothing left running).
88
+
89
+ ## Orin: the fix branch resolves torch without `UV_INDEX`
90
+
91
+ The Orin clone is also at `a30a9e4`, and its `uv.lock` resolves `torch`
92
+ 2.9.1 from `pypi.jetson-ai-lab.io/sbsa/cu130` (the branch keys the SBSA
93
+ index on `'tegra' in platform_release`, which Orin's `6.8.12-1021-tegra`
94
+ kernel matches); `torch-2.9.1.dist-info` is what is installed. The tutorial's
95
+ Orin `UV_INDEX=…` line is only needed on the pinned `develop` commit. Nothing
96
+ changes for training: that torch has `sm_110`/`sm_121` kernels, not Orin's
97
+ `sm_87`, exactly as the 2026-09-04 Orin record found.
98
+
99
+ ## What this changed
100
+
101
+ - The 0.9.6 `env doctor` fix in this repo (`PACKAGED_PINS`, `[WARN]`): Thor
102
+ on the wheel hit the two `[FAIL] … unknown` lines the Spark re-test filed as
103
+ `d5`, and the tutorial reviewer asked for it to just work.
104
+ - The tutorial: Step 2 tells Jetson readers to check out `a30a9e4` up front
105
+ (the flip back to the pin is what broke the first smoke); the Thor and Orin
106
+ tabs carry these numbers; Step 4's Spark tab warns about the sync's size.
@@ -98,20 +98,30 @@ def _diagnose() -> dict[str, object]:
98
98
  return {"healthy": healthy, "checks": checks}
99
99
 
100
100
 
101
+ def _mark(check: dict) -> str:
102
+ """`ok` for a pass, `WARN` for a failing warning, `FAIL` for a failing error."""
103
+ if check["passed"]:
104
+ return "ok"
105
+ return "WARN" if check["severity"] == "warning" else "FAIL"
106
+
107
+
108
+ def _render_text(report: dict) -> str:
109
+ status = "healthy" if report["healthy"] else "unhealthy"
110
+ lines = [f"microduck-cli doctor: {status}", ""]
111
+ for check in report["checks"]:
112
+ lines.append(f"[{_mark(check)}] {check['id']}: {check['message']}")
113
+ if not check["passed"] and check["remediation"]:
114
+ lines.append(f" hint: {check['remediation']}")
115
+ return "\n".join(lines)
116
+
117
+
101
118
  def cmd_doctor(args: argparse.Namespace) -> int:
102
119
  report = _diagnose()
103
120
  json_mode = bool(getattr(args, "json", False))
104
121
  if json_mode:
105
122
  emit_result(report, json_mode=True)
106
123
  else:
107
- status = "healthy" if report["healthy"] else "unhealthy"
108
- lines = [f"microduck-cli doctor: {status}", ""]
109
- for check in report["checks"]:
110
- mark = "ok" if check["passed"] else "FAIL"
111
- lines.append(f"[{mark}] {check['id']}: {check['message']}")
112
- if not check["passed"] and check["remediation"]:
113
- lines.append(f" hint: {check['remediation']}")
114
- emit_result("\n".join(lines), json_mode=False)
124
+ emit_result(_render_text(report), json_mode=False)
115
125
  return 0 if report["healthy"] else 1
116
126
 
117
127
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  Diagnoses whether this box is ready to run the simulation stack (t13) and
4
4
  the training lane (t14): the upstream clones checked out at the pinned
5
- commits recorded in ``docs/upstream-pins.md``, a cargo toolchain new enough
5
+ commits recorded in ``docs/upstream-pins.md`` (carried inside the package as
6
+ ``PACKAGED_PINS`` too, so a wheel install can verify them), a cargo toolchain new enough
6
7
  to build ``robotd``/``robotctl``/``tof``/``sounds``, the built daemons
7
8
  themselves, the ``microduck_rl`` virtualenv with ``onnxruntime`` installed,
8
9
  a state directory short enough for unix sockets, a free port for
@@ -65,6 +66,18 @@ _MICRODUCK_RL_HF_README_URL = (
65
66
  "https://github.com/pollen-robotics/microduck_rl/blob/develop/scripts/hf/README.md"
66
67
  )
67
68
 
69
+ # --- the pinned upstream commits, carried inside the wheel -------------
70
+ # docs/upstream-pins.md is the authoritative ledger, but a wheel does not
71
+ # ship docs/, so a `uv tool install` used to report both pin checks as
72
+ # "unknown" (a [FAIL] line under a healthy verdict — the 2026-09-07 Spark
73
+ # re-test, defect d5). These are the same commits, hardcoded on purpose;
74
+ # tests/test_env_doctor.py asserts they equal the table so a re-pin that
75
+ # forgets one of the two fails CI rather than drifting.
76
+ PACKAGED_PINS: dict[str, str] = {
77
+ "pollen-robotics/microduck": "0cd676d6fbb6e90a762c84aa63abe7a02dbc9495",
78
+ "pollen-robotics/microduck_rl": "29e887ecfbf5d37144759e5a9f8a176dfb83d547",
79
+ }
80
+
68
81
  _MIN_CARGO_VERSION = (1, 89)
69
82
 
70
83
  # duck-body's default port, matching upstream's DUCK_SIM_PORT (env/stack.py's
@@ -181,6 +194,17 @@ def _parse_pins(pins_path: Path) -> dict[str, str]:
181
194
  return pins
182
195
 
183
196
 
197
+ def load_pins(pins_path: Path) -> dict[str, str]:
198
+ """The pinned commits: the docs table when it is readable, else the packaged copy.
199
+
200
+ A checkout reads ``docs/upstream-pins.md`` (so an in-flight re-pin is seen
201
+ before the constant is updated); a wheel, which does not ship ``docs/``,
202
+ falls back to :data:`PACKAGED_PINS` row by row. Either way the result
203
+ carries both repos whenever either source knows them.
204
+ """
205
+ return {**PACKAGED_PINS, **_parse_pins(pins_path)}
206
+
207
+
184
208
  def _find_onnxruntime(rl_clone: str) -> str | None:
185
209
  venv = Path(rl_clone) / ".venv"
186
210
  if not venv.is_dir():
@@ -296,7 +320,7 @@ def default_probe() -> EnvProbe:
296
320
  env = _probe_environ()
297
321
 
298
322
  pins_path = Path(__file__).resolve().parents[2] / "docs" / "upstream-pins.md"
299
- pins = _parse_pins(pins_path)
323
+ pins = load_pins(pins_path)
300
324
 
301
325
  microduck_clone, rl_clone = resolve_clone_paths(env)
302
326
  microduck_clone_commit = _git_head(microduck_clone) if microduck_clone else None
@@ -377,9 +401,9 @@ def _pinned_commit_check(
377
401
  check_id,
378
402
  False,
379
403
  "warning",
380
- f"pinned commit for {label} is unknown (docs/upstream-pins.md unreadable or the "
381
- "row is missing)",
382
- "check that docs/upstream-pins.md is present and its table is well-formed",
404
+ f"pinned commit for {label} is unknown (no row in docs/upstream-pins.md and none "
405
+ "packaged)",
406
+ "add the repo's row to docs/upstream-pins.md and PACKAGED_PINS in env/doctor.py",
383
407
  )
384
408
  if not clone_commit:
385
409
  return _check(
@@ -621,17 +645,25 @@ def diagnose(probe: EnvProbe) -> dict[str, object]:
621
645
  return {"healthy": healthy, "checks": checks}
622
646
 
623
647
 
648
+ def _mark(check: Mapping[str, object]) -> str:
649
+ if check["passed"]:
650
+ return "ok"
651
+ return "WARN" if check["severity"] == "warning" else "FAIL"
652
+
653
+
624
654
  def render_text(report: Mapping[str, object]) -> str:
625
655
  """Render `report` the way `microduck-cli doctor`'s text output looks.
626
656
 
627
- ``"[ok] id: message"`` / ``"[FAIL] id: message"``, with a ``" hint:
628
- remediation"`` line under any failing check that carries one.
657
+ ``"[ok] id: message"`` for a passing check, ``"[WARN] id: message"`` for a
658
+ failing ``warning``-severity one (surfaced, never blocking the verdict) and
659
+ ``"[FAIL] id: message"`` for a failing ``error``, with a ``" hint:
660
+ remediation"`` line under any failing check that carries one. The prefix
661
+ agrees with the verdict line: a healthy report never shows ``[FAIL]``.
629
662
  """
630
663
  status = "healthy" if report["healthy"] else "unhealthy"
631
664
  lines = [f"microduck-cli env doctor: {status}", ""]
632
665
  for check in report["checks"]: # type: ignore[union-attr]
633
- mark = "ok" if check["passed"] else "FAIL"
634
- lines.append(f"[{mark}] {check['id']}: {check['message']}")
666
+ lines.append(f"[{_mark(check)}] {check['id']}: {check['message']}")
635
667
  if not check["passed"] and check["remediation"]:
636
668
  lines.append(f" hint: {check['remediation']}")
637
669
  return "\n".join(lines)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "microduck-cli"
3
- version = "0.9.5"
3
+ version = "0.9.6"
4
4
  description = "Agent-agnostic CLI for controlling the MicroDuck robot. Any agent, any human, one CLI. Sim-first: every verb is exercised against the real robotd daemon and the MuJoCo simulation."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -3,11 +3,15 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ from pathlib import Path
6
7
 
7
8
  from microduck_cli.env.doctor import (
9
+ PACKAGED_PINS,
8
10
  EnvProbe,
11
+ _parse_pins,
9
12
  default_probe,
10
13
  diagnose,
14
+ load_pins,
11
15
  render_text,
12
16
  )
13
17
  from microduck_cli.env.hosts import HostInfo
@@ -335,3 +339,58 @@ def test_default_probe_never_raises_and_returns_env_probe():
335
339
  # Must be diagnosable without raising regardless of what's installed here.
336
340
  report = diagnose(probe)
337
341
  assert isinstance(report["healthy"], bool)
342
+
343
+
344
+ # --- packaged pins (d5: a wheel ships no docs/) ---------------------------
345
+
346
+ _PINS_DOC = Path(__file__).resolve().parents[1] / "docs" / "upstream-pins.md"
347
+
348
+
349
+ def test_packaged_pins_match_the_docs_table():
350
+ # A re-pin that updates one source and not the other must fail here.
351
+ assert _parse_pins(_PINS_DOC) == PACKAGED_PINS
352
+
353
+
354
+ def test_load_pins_falls_back_to_packaged_when_docs_absent(tmp_path):
355
+ assert load_pins(tmp_path / "missing" / "upstream-pins.md") == PACKAGED_PINS
356
+
357
+
358
+ def test_load_pins_prefers_a_readable_docs_table(tmp_path):
359
+ doc = tmp_path / "upstream-pins.md"
360
+ doc.write_text(
361
+ "| repo | ref | commit |\n|---|---|---|\n"
362
+ "| `pollen-robotics/microduck` | branch `x` | `abcdef1234567` |\n",
363
+ encoding="utf-8",
364
+ )
365
+ pins = load_pins(doc)
366
+ assert pins["pollen-robotics/microduck"] == "abcdef1234567"
367
+ assert pins["pollen-robotics/microduck_rl"] == PACKAGED_PINS["pollen-robotics/microduck_rl"]
368
+
369
+
370
+ def test_default_probe_knows_both_pins():
371
+ probe = default_probe()
372
+ assert probe.microduck_pinned_commit == PACKAGED_PINS["pollen-robotics/microduck"]
373
+ assert probe.rl_pinned_commit == PACKAGED_PINS["pollen-robotics/microduck_rl"]
374
+
375
+
376
+ # --- [WARN] vs [FAIL] -----------------------------------------------------
377
+
378
+
379
+ def test_render_text_marks_warnings_warn_not_fail():
380
+ probe = EnvProbe(
381
+ **{
382
+ **COMPLETE_PROBE.__dict__,
383
+ "rl_clone_commit": "a30a9e4ffc161c90d19181f2815d1b50064182bb",
384
+ }
385
+ )
386
+ report = diagnose(probe)
387
+ text = render_text(report)
388
+ assert text.startswith("microduck-cli env doctor: healthy")
389
+ assert "[WARN] rl_pinned_commit:" in text
390
+ assert "[FAIL]" not in text
391
+
392
+
393
+ def test_render_text_marks_errors_fail():
394
+ text = render_text(diagnose(EMPTY_PROBE))
395
+ assert text.startswith("microduck-cli env doctor: unhealthy")
396
+ assert "[FAIL] microduck_clone_present:" in text
@@ -227,7 +227,7 @@ wheels = [
227
227
 
228
228
  [[package]]
229
229
  name = "microduck-cli"
230
- version = "0.9.5"
230
+ version = "0.9.6"
231
231
  source = { editable = "." }
232
232
 
233
233
  [package.dev-dependencies]
File without changes
File without changes
File without changes
File without changes