arm101-cli 0.4.0__tar.gz → 0.5.0__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 (96) hide show
  1. arm101_cli-0.5.0/.devague/current +1 -0
  2. arm101_cli-0.5.0/.devague/current_plan +1 -0
  3. arm101_cli-0.5.0/.devague/frames/arm101-cli-grows-its-first-hardware-verbs-arm101-f.json +362 -0
  4. arm101_cli-0.5.0/.devague/plans/arm101-cli-grows-its-first-hardware-verbs-arm101-f.json +301 -0
  5. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.gitignore +3 -0
  6. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.markdownlint-cli2.yaml +12 -1
  7. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/CHANGELOG.md +22 -0
  8. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/PKG-INFO +25 -1
  9. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/README.md +18 -0
  10. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/__init__.py +6 -0
  11. arm101_cli-0.5.0/arm101/cli/_commands/calibrate.py +181 -0
  12. arm101_cli-0.5.0/arm101/cli/_commands/find_port.py +108 -0
  13. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/learn.py +12 -0
  14. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/overview.py +3 -0
  15. arm101_cli-0.5.0/arm101/cli/_commands/setup_motors.py +177 -0
  16. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/explain/catalog.py +74 -0
  17. arm101_cli-0.5.0/arm101/hardware/__init__.py +21 -0
  18. arm101_cli-0.5.0/arm101/hardware/bus.py +426 -0
  19. arm101_cli-0.5.0/arm101/hardware/ports.py +100 -0
  20. arm101_cli-0.5.0/arm101/hardware/profiles.py +208 -0
  21. arm101_cli-0.5.0/docs/hardware-validation.md +298 -0
  22. arm101_cli-0.5.0/docs/plans/2026-06-26-arm101-cli-grows-its-first-hardware-verbs-arm101-f.md +83 -0
  23. arm101_cli-0.5.0/docs/specs/2026-06-26-arm101-cli-grows-its-first-hardware-verbs-arm101-f.md +63 -0
  24. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/pyproject.toml +10 -1
  25. arm101_cli-0.5.0/tests/test_bus.py +196 -0
  26. arm101_cli-0.5.0/tests/test_calibrate.py +269 -0
  27. arm101_cli-0.5.0/tests/test_find_port.py +300 -0
  28. arm101_cli-0.5.0/tests/test_hardware_verbs.py +446 -0
  29. arm101_cli-0.5.0/tests/test_ports.py +246 -0
  30. arm101_cli-0.5.0/tests/test_profiles.py +201 -0
  31. arm101_cli-0.5.0/tests/test_setup_motors.py +264 -0
  32. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/uv.lock +36 -1
  33. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/agent-config/SKILL.md +0 -0
  34. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  35. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  36. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/ask-colleague/SKILL.md +0 -0
  37. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  38. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  39. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  40. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  41. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  42. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  43. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/cicd/SKILL.md +0 -0
  44. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  45. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  46. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  47. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  48. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  49. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/SKILL.md +0 -0
  50. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  51. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  52. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  53. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  54. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  55. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  56. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  57. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  58. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  59. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  60. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/recall/SKILL.md +0 -0
  61. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/recall/scripts/recall.sh +0 -0
  62. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/remember/SKILL.md +0 -0
  63. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/remember/scripts/remember.sh +0 -0
  64. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/run-tests/SKILL.md +0 -0
  65. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  66. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  67. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  68. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  69. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  70. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/think/SKILL.md +0 -0
  71. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/think/scripts/think.sh +0 -0
  72. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/version-bump/SKILL.md +0 -0
  73. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  74. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.claude/skills.local.yaml.example +0 -0
  75. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.flake8 +0 -0
  76. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.github/workflows/publish.yml +0 -0
  77. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/.github/workflows/tests.yml +0 -0
  78. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/AGENTS.colleague.md +0 -0
  79. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/CLAUDE.md +0 -0
  80. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/LICENSE +0 -0
  81. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/__init__.py +0 -0
  82. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/__main__.py +0 -0
  83. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/__init__.py +0 -0
  84. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/cli.py +0 -0
  85. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/doctor.py +0 -0
  86. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/explain.py +0 -0
  87. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_commands/whoami.py +0 -0
  88. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_errors.py +0 -0
  89. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/cli/_output.py +0 -0
  90. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/arm101/explain/__init__.py +0 -0
  91. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/culture.yaml +0 -0
  92. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/docs/skill-sources.md +0 -0
  93. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/sonar-project.properties +0 -0
  94. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/tests/__init__.py +0 -0
  95. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/tests/test_cli.py +0 -0
  96. {arm101_cli-0.4.0 → arm101_cli-0.5.0}/tests/test_cli_introspection.py +0 -0
@@ -0,0 +1 @@
1
+ arm101-cli-grows-its-first-hardware-verbs-arm101-f
@@ -0,0 +1 @@
1
+ arm101-cli-grows-its-first-hardware-verbs-arm101-f
@@ -0,0 +1,362 @@
1
+ {
2
+ "slug": "arm101-cli-grows-its-first-hardware-verbs-arm101-f",
3
+ "title": "arm101-cli grows its first hardware verbs: 'arm101 find-port' detects which USB serial port the SO-101 arm is on, and 'arm101 calibrate' records each joint's range of motion \u2014 both following the existing agent-first error/output/explain contracts.",
4
+ "schema_version": 1,
5
+ "status": "exported",
6
+ "created": "2026-06-26T21:26:56Z",
7
+ "updated": "2026-06-26T21:41:33Z",
8
+ "claims": [
9
+ {
10
+ "id": "c1",
11
+ "kind": "announcement",
12
+ "text": "arm101-cli grows its first hardware verbs: 'arm101 find-port' detects which USB serial port the SO-101 arm is on, and 'arm101 calibrate' records each joint's range of motion \u2014 both following the existing agent-first error/output/explain contracts.",
13
+ "origin": "user",
14
+ "status": "confirmed",
15
+ "honesty_conditions": [
16
+ {
17
+ "id": "h5",
18
+ "text": "Both 'arm101 find-port' and 'arm101 calibrate' exist as real registered verbs that pass the in-package doctor, the teken cli doctor --strict rubric gate, and the full pytest suite with no hardware attached.",
19
+ "status": "rejected"
20
+ },
21
+ {
22
+ "id": "h6",
23
+ "text": "All three verbs (find-port, calibrate, setup-motors) exist as registered verbs that pass the in-package doctor, teken cli doctor --strict, and the CI pytest suite (surface + contracts + profile round-trip + find-port enumeration) with no hardware attached.",
24
+ "status": "confirmed"
25
+ },
26
+ {
27
+ "id": "h7",
28
+ "text": "calibrate's and setup-motors' real motor-I/O paths have been exercised against a physical SO-101 follower arm at least once before the feature is considered done.",
29
+ "status": "confirmed"
30
+ }
31
+ ],
32
+ "hard_questions": [],
33
+ "links": []
34
+ },
35
+ {
36
+ "id": "c2",
37
+ "kind": "audience",
38
+ "text": "An operator (human or mesh agent) bringing up a physical SO-101 arm for the first time, who needs to know which /dev port the arm is on and to calibrate joint ranges before any motion.",
39
+ "origin": "llm",
40
+ "status": "confirmed",
41
+ "honesty_conditions": [
42
+ {
43
+ "id": "h14",
44
+ "text": "The verbs serve both a human at a terminal and a mesh agent: every prompt-driven flow (find-port --detect, setup-motors) has a documented non-interactive and/or --json path, so an agent is never forced through a blocking TTY prompt to obtain a result.",
45
+ "status": "confirmed"
46
+ }
47
+ ],
48
+ "hard_questions": [],
49
+ "links": []
50
+ },
51
+ {
52
+ "id": "c3",
53
+ "kind": "before_state",
54
+ "text": "arm101-cli has zero hardware verbs \u2014 it can introspect its own identity but cannot see, address, or calibrate the physical arm. Operators copy lerobot-find-port / lerobot-calibrate workflows by hand.",
55
+ "origin": "llm",
56
+ "status": "confirmed",
57
+ "honesty_conditions": [
58
+ {
59
+ "id": "h15",
60
+ "text": "On current main, 'arm101 --help' lists no find-port/calibrate/setup-motors verbs and no module under arm101/ opens a serial port \u2014 verifiable by grep before the change lands.",
61
+ "status": "confirmed"
62
+ }
63
+ ],
64
+ "hard_questions": [],
65
+ "links": []
66
+ },
67
+ {
68
+ "id": "c4",
69
+ "kind": "after_state",
70
+ "text": "Running 'arm101 find-port' identifies the arm's serial port; 'arm101 calibrate' walks the operator through recording min/mid/max for each of the 6 joints and persists a named calibration profile that later motion commands consume.",
71
+ "origin": "llm",
72
+ "status": "rejected",
73
+ "honesty_conditions": [
74
+ {
75
+ "id": "h3",
76
+ "text": "calibrate's persisted profile round-trips: writing then reading a profile yields identical per-joint min/mid/max, and the schema is documented where motion commands will later read it.",
77
+ "status": "proposed"
78
+ }
79
+ ],
80
+ "hard_questions": [],
81
+ "links": []
82
+ },
83
+ {
84
+ "id": "c5",
85
+ "kind": "why_it_matters",
86
+ "text": "Calibration is the precondition for any safe motion: without per-joint min/max, position commands can drive a joint into a hard stop. find-port removes the brittle hand-copied device path. These two verbs are the on-ramp from 'introspection chassis' to 'controls a real arm'.",
87
+ "origin": "llm",
88
+ "status": "rejected",
89
+ "honesty_conditions": [],
90
+ "hard_questions": [],
91
+ "links": []
92
+ },
93
+ {
94
+ "id": "c6",
95
+ "kind": "boundary",
96
+ "text": "Not in scope: actual teleoperation, training, or motion-execution verbs; the leader-arm variant's differently-geared motors; and setup-motors (writing motor IDs/baudrates to EEPROM) unless explicitly pulled in. This spec is find-port + calibrate for one follower arm.",
97
+ "origin": "llm",
98
+ "status": "rejected",
99
+ "honesty_conditions": [],
100
+ "hard_questions": [
101
+ {
102
+ "id": "q2",
103
+ "text": "Is setup-motors (writing per-motor IDs/baudrates to EEPROM) in scope, or a deliberate follow-up? It's the third LeRobot verb and a prerequisite to a fresh arm working at all.",
104
+ "resolved": false,
105
+ "blocking": false
106
+ }
107
+ ],
108
+ "links": []
109
+ },
110
+ {
111
+ "id": "c7",
112
+ "kind": "success_signal",
113
+ "text": "An operator with a connected SO-101 runs 'arm101 find-port' and gets the right /dev path, then 'arm101 calibrate my_arm' and a calibration profile file is written; both commands honor --json, exit-code policy, and never leak a traceback. Tests + rubric gate pass with no hardware attached.",
114
+ "origin": "llm",
115
+ "status": "rejected",
116
+ "honesty_conditions": [],
117
+ "hard_questions": [],
118
+ "links": []
119
+ },
120
+ {
121
+ "id": "c8",
122
+ "kind": "non_goal",
123
+ "text": "Does not vendor or wrap the full LeRobot stack; arm101-cli stays a standalone CLI, not a LeRobot plugin.",
124
+ "origin": "llm",
125
+ "status": "confirmed",
126
+ "honesty_conditions": [],
127
+ "hard_questions": [],
128
+ "links": []
129
+ },
130
+ {
131
+ "id": "c9",
132
+ "kind": "requirement",
133
+ "text": "The hardware-touching layer (serial enumeration + Feetech motor I/O) is isolated behind an adapter module so the introspection CLI still imports with zero third-party deps; hardware libs install via an optional extra (e.g. pip install '.[hardware]').",
134
+ "origin": "llm",
135
+ "status": "confirmed",
136
+ "honesty_conditions": [
137
+ {
138
+ "id": "h1",
139
+ "text": "After the feature lands, 'python -c \"import arm101.cli\"' still succeeds in an env with no third-party packages installed, and the hardware adapter import is lazy (only triggered when a hardware verb actually talks to the bus).",
140
+ "status": "confirmed"
141
+ }
142
+ ],
143
+ "hard_questions": [],
144
+ "links": []
145
+ },
146
+ {
147
+ "id": "c10",
148
+ "kind": "requirement",
149
+ "text": "Both verbs follow the existing contracts verbatim: register() under _commands/, CliError-only failures, emit_result/emit_error split, a --json mode, and lockstep updates to the explain catalog + overview _VERBS + learn _TEXT.",
150
+ "origin": "llm",
151
+ "status": "rejected",
152
+ "honesty_conditions": [
153
+ {
154
+ "id": "h2",
155
+ "text": "A new test asserts find-port and calibrate each: raise CliError (never sys.exit), keep stdout/stderr split in both text and --json modes, and that the explain catalog, overview _VERBS, and learn _TEXT all reference the new verbs (the lockstep-or-drift invariant).",
156
+ "status": "rejected"
157
+ },
158
+ {
159
+ "id": "h8",
160
+ "text": "A test asserts find-port, calibrate, and setup-motors each raise CliError (never sys.exit), keep the stdout/stderr split in both text and --json modes, and that the explain catalog, overview _VERBS, and learn _TEXT all reference all three verbs (the lockstep-or-drift invariant).",
161
+ "status": "proposed"
162
+ }
163
+ ],
164
+ "hard_questions": [],
165
+ "links": []
166
+ },
167
+ {
168
+ "id": "c11",
169
+ "kind": "decision",
170
+ "text": "find-port offers a non-interactive enumeration mode (list candidate serial ports, agent-friendly, default) AND an interactive disconnect-diff mode (--detect) that prompts the operator to unplug the arm and resolves the single changed port \u2014 mirroring lerobot-find-port.",
171
+ "origin": "llm",
172
+ "status": "confirmed",
173
+ "honesty_conditions": [
174
+ {
175
+ "id": "h4",
176
+ "text": "find-port's default (enumeration) mode is fully non-interactive and agent-safe (no blocking prompt), and the interactive --detect mode degrades to a clear CliError when run without a TTY rather than hanging.",
177
+ "status": "confirmed"
178
+ }
179
+ ],
180
+ "hard_questions": [],
181
+ "links": []
182
+ },
183
+ {
184
+ "id": "c12",
185
+ "kind": "assumption",
186
+ "text": "Serial-port enumeration can start as stdlib-only on Linux (glob /dev/ttyACM*, /dev/ttyUSB*, /dev/serial/by-id) so find-port needs no third-party dep; pyserial is only pulled in if/when cross-platform enumeration is required.",
187
+ "origin": "llm",
188
+ "status": "rejected",
189
+ "honesty_conditions": [],
190
+ "hard_questions": [
191
+ {
192
+ "id": "q3",
193
+ "text": "risk: Stdlib /dev globbing is Linux-only; macOS operators (the LeRobot docs show /dev/tty.usbmodem...) would get nothing until pyserial lands. Need to decide whether macOS support is required at launch.",
194
+ "resolved": false,
195
+ "blocking": false
196
+ }
197
+ ],
198
+ "links": []
199
+ },
200
+ {
201
+ "id": "c13",
202
+ "kind": "assumption",
203
+ "text": "calibrate persists a JSON profile keyed by a user-given arm id (e.g. 'arm101 calibrate my_follower') under a calibrations/ dir, storing per-joint {min, mid, max} for the 6 named joints (shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper).",
204
+ "origin": "llm",
205
+ "status": "rejected",
206
+ "honesty_conditions": [],
207
+ "hard_questions": [
208
+ {
209
+ "id": "q4",
210
+ "text": "Where should calibration profiles live and be keyed \u2014 a calibrations/ dir next to the package, an XDG/user config dir, or under culture.yaml? And is the arm id a required positional or defaulted?",
211
+ "resolved": false,
212
+ "blocking": false
213
+ }
214
+ ],
215
+ "links": []
216
+ },
217
+ {
218
+ "id": "c14",
219
+ "kind": "decision",
220
+ "text": "Real Feetech STS3215 motor I/O lives behind the isolated hardware adapter; with no hardware/SDK present, calibrate fails cleanly via CliError (exit 2, env error) \u2014 it does not stub fake readings. The CLI surface, profile schema, and adapter interface are fully built and tested without hardware.",
221
+ "origin": "llm",
222
+ "status": "rejected",
223
+ "honesty_conditions": [],
224
+ "hard_questions": [
225
+ {
226
+ "id": "q1",
227
+ "text": "Should calibrate fail-clean without hardware (define surface now, real I/O later), or must this spec include real Feetech STS3215 reads tested against a physical arm before it's 'done'?",
228
+ "resolved": false,
229
+ "blocking": false
230
+ }
231
+ ],
232
+ "links": []
233
+ },
234
+ {
235
+ "id": "c15",
236
+ "kind": "boundary",
237
+ "text": "In scope: find-port, calibrate, and setup-motors for ONE follower SO-101 arm, Linux at launch. Out of scope: the leader-arm variant (differently-geared motors), teleoperation, training, and motion-execution verbs.",
238
+ "origin": "user",
239
+ "status": "confirmed",
240
+ "honesty_conditions": [
241
+ {
242
+ "id": "h12",
243
+ "text": "The leader-arm variant, teleoperation, training, and motion-execution verbs are entirely absent from this spec's deliverable \u2014 no half-built stubs for them ship.",
244
+ "status": "confirmed"
245
+ }
246
+ ],
247
+ "hard_questions": [],
248
+ "links": []
249
+ },
250
+ {
251
+ "id": "c16",
252
+ "kind": "success_signal",
253
+ "text": "An operator with a connected SO-101 runs: find-port (returns the right /dev path), calibrate <id> (records real STS3215 min/mid/max to a profile), setup-motors (sets per-motor id/baudrate). Surface + contract + profile round-trip + find-port enumeration tests and the rubric gate pass in CI with NO hardware; real motor I/O is validated manually against a physical arm before 'done'.",
254
+ "origin": "user",
255
+ "status": "confirmed",
256
+ "honesty_conditions": [
257
+ {
258
+ "id": "h13",
259
+ "text": "CI is green on the surface/contract/round-trip/enumeration tests with no hardware attached, AND a manual run-log against a physical follower arm records find-port, calibrate, and setup-motors working end-to-end.",
260
+ "status": "confirmed"
261
+ }
262
+ ],
263
+ "hard_questions": [],
264
+ "links": []
265
+ },
266
+ {
267
+ "id": "c17",
268
+ "kind": "decision",
269
+ "text": "calibrate performs REAL Feetech STS3215 reads via the Feetech SDK, lazy-imported from the optional [hardware] install extra. With no bus/SDK present it raises CliError (exit 2, env error). The hardware adapter is tested in CI against a fake/in-memory bus; real reads are validated manually against a physical arm, and that manual validation is part of 'done'.",
270
+ "origin": "user",
271
+ "status": "confirmed",
272
+ "honesty_conditions": [],
273
+ "hard_questions": [],
274
+ "links": []
275
+ },
276
+ {
277
+ "id": "c18",
278
+ "kind": "decision",
279
+ "text": "Calibration profiles persist as JSON under an XDG/user config dir ($XDG_CONFIG_HOME or ~/.config/arm101/calibrations/<id>.json). The arm id is a REQUIRED positional argument, mirroring lerobot --robot.id. Profiles are reinstall-safe and not committed to the repo.",
280
+ "origin": "user",
281
+ "status": "confirmed",
282
+ "honesty_conditions": [],
283
+ "hard_questions": [],
284
+ "links": []
285
+ },
286
+ {
287
+ "id": "c19",
288
+ "kind": "requirement",
289
+ "text": "setup-motors walks the operator through one-motor-at-a-time EEPROM id/baudrate assignment (gripper=6 down to shoulder_pan=1), prompting to connect each motor alone before pressing Enter \u2014 mirroring lerobot-setup-motors. It is interactive and degrades to a clear CliError when run without a TTY.",
290
+ "origin": "user",
291
+ "status": "confirmed",
292
+ "honesty_conditions": [
293
+ {
294
+ "id": "h9",
295
+ "text": "setup-motors never proceeds to write EEPROM without an explicit operator Enter per motor, and a non-TTY invocation exits with CliError(2) rather than hanging or auto-writing.",
296
+ "status": "confirmed"
297
+ }
298
+ ],
299
+ "hard_questions": [],
300
+ "links": []
301
+ },
302
+ {
303
+ "id": "c20",
304
+ "kind": "decision",
305
+ "text": "Serial-port enumeration is Linux stdlib-only at launch (glob /dev/ttyACM*, /dev/ttyUSB*, /dev/serial/by-id). Optional [mac] and [win] install extras exist as placeholders; on macOS/Windows the hardware verbs return a clean 'unsupported for now' CliError rather than silently finding nothing.",
306
+ "origin": "user",
307
+ "status": "confirmed",
308
+ "honesty_conditions": [],
309
+ "hard_questions": [],
310
+ "links": []
311
+ },
312
+ {
313
+ "id": "c21",
314
+ "kind": "after_state",
315
+ "text": "find-port returns the arm's serial port; calibrate <id> walks the operator through recording min/mid/max for each of the 6 joints (shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper) and persists a named profile that later motion commands consume; setup-motors assigns each motor's id/baudrate in EEPROM.",
316
+ "origin": "llm",
317
+ "status": "confirmed",
318
+ "honesty_conditions": [
319
+ {
320
+ "id": "h10",
321
+ "text": "calibrate's persisted profile round-trips: writing then reading a profile yields identical per-joint min/mid/max, and the schema is documented where motion commands will later read it.",
322
+ "status": "confirmed"
323
+ }
324
+ ],
325
+ "hard_questions": [],
326
+ "links": []
327
+ },
328
+ {
329
+ "id": "c22",
330
+ "kind": "why_it_matters",
331
+ "text": "Calibration is the precondition for safe motion: without per-joint min/max, position commands can drive a joint into a hard stop. find-port removes the brittle hand-copied device path, and setup-motors makes a fresh-from-factory arm addressable at all. Together these three verbs are the on-ramp from 'introspection chassis' to 'controls a real arm'.",
332
+ "origin": "llm",
333
+ "status": "confirmed",
334
+ "honesty_conditions": [
335
+ {
336
+ "id": "h16",
337
+ "text": "The persisted profile's per-joint min/max is stored in the documented units/encoding that a future motion verb will read to clamp commands \u2014 the schema is the actual safety contract, not merely a record.",
338
+ "status": "confirmed"
339
+ }
340
+ ],
341
+ "hard_questions": [],
342
+ "links": []
343
+ },
344
+ {
345
+ "id": "c23",
346
+ "kind": "requirement",
347
+ "text": "All three verbs follow the existing contracts verbatim: register() under _commands/, CliError-only failures, the emit_result/emit_error stdout-stderr split, a --json mode, and lockstep updates to the explain catalog + overview _VERBS + learn _TEXT.",
348
+ "origin": "llm",
349
+ "status": "confirmed",
350
+ "honesty_conditions": [
351
+ {
352
+ "id": "h11",
353
+ "text": "A test asserts find-port, calibrate, and setup-motors each raise CliError (never sys.exit), keep the stdout/stderr split in both text and --json modes, and that the explain catalog, overview _VERBS, and learn _TEXT all reference all three verbs (the lockstep-or-drift invariant).",
354
+ "status": "confirmed"
355
+ }
356
+ ],
357
+ "hard_questions": [],
358
+ "links": []
359
+ }
360
+ ],
361
+ "open_vagueness": []
362
+ }