@horizon36596/zenith-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +18 -0
  3. package/README.md +207 -0
  4. package/dist/autoLoad.d.ts +54 -0
  5. package/dist/autoLoad.d.ts.map +1 -0
  6. package/dist/autoLoad.js +139 -0
  7. package/dist/autoLoad.js.map +1 -0
  8. package/dist/calibrateFit.d.ts +38 -0
  9. package/dist/calibrateFit.d.ts.map +1 -0
  10. package/dist/calibrateFit.js +69 -0
  11. package/dist/calibrateFit.js.map +1 -0
  12. package/dist/commandLibrary.d.ts +19 -0
  13. package/dist/commandLibrary.d.ts.map +1 -0
  14. package/dist/commandLibrary.js +27 -0
  15. package/dist/commandLibrary.js.map +1 -0
  16. package/dist/commands/calibrate.d.ts +18 -0
  17. package/dist/commands/calibrate.d.ts.map +1 -0
  18. package/dist/commands/calibrate.js +116 -0
  19. package/dist/commands/calibrate.js.map +1 -0
  20. package/dist/commands/codegen.d.ts +12 -0
  21. package/dist/commands/codegen.d.ts.map +1 -0
  22. package/dist/commands/codegen.js +63 -0
  23. package/dist/commands/codegen.js.map +1 -0
  24. package/dist/commands/deploy.d.ts +63 -0
  25. package/dist/commands/deploy.d.ts.map +1 -0
  26. package/dist/commands/deploy.js +181 -0
  27. package/dist/commands/deploy.js.map +1 -0
  28. package/dist/commands/diff.d.ts +11 -0
  29. package/dist/commands/diff.d.ts.map +1 -0
  30. package/dist/commands/diff.js +68 -0
  31. package/dist/commands/diff.js.map +1 -0
  32. package/dist/commands/estimate.d.ts +9 -0
  33. package/dist/commands/estimate.d.ts.map +1 -0
  34. package/dist/commands/estimate.js +81 -0
  35. package/dist/commands/estimate.js.map +1 -0
  36. package/dist/commands/init.d.ts +10 -0
  37. package/dist/commands/init.d.ts.map +1 -0
  38. package/dist/commands/init.js +67 -0
  39. package/dist/commands/init.js.map +1 -0
  40. package/dist/commands/new.d.ts +10 -0
  41. package/dist/commands/new.d.ts.map +1 -0
  42. package/dist/commands/new.js +28 -0
  43. package/dist/commands/new.js.map +1 -0
  44. package/dist/commands/propose.d.ts +15 -0
  45. package/dist/commands/propose.d.ts.map +1 -0
  46. package/dist/commands/propose.js +67 -0
  47. package/dist/commands/propose.js.map +1 -0
  48. package/dist/commands/render.d.ts +16 -0
  49. package/dist/commands/render.d.ts.map +1 -0
  50. package/dist/commands/render.js +90 -0
  51. package/dist/commands/render.js.map +1 -0
  52. package/dist/commands/sim.d.ts +31 -0
  53. package/dist/commands/sim.d.ts.map +1 -0
  54. package/dist/commands/sim.js +213 -0
  55. package/dist/commands/sim.js.map +1 -0
  56. package/dist/commands/validate.d.ts +19 -0
  57. package/dist/commands/validate.d.ts.map +1 -0
  58. package/dist/commands/validate.js +97 -0
  59. package/dist/commands/validate.js.map +1 -0
  60. package/dist/coreGate.d.ts +19 -0
  61. package/dist/coreGate.d.ts.map +1 -0
  62. package/dist/coreGate.js +23 -0
  63. package/dist/coreGate.js.map +1 -0
  64. package/dist/fieldImage.d.ts +19 -0
  65. package/dist/fieldImage.d.ts.map +1 -0
  66. package/dist/fieldImage.js +63 -0
  67. package/dist/fieldImage.js.map +1 -0
  68. package/dist/findings.d.ts +20 -0
  69. package/dist/findings.d.ts.map +1 -0
  70. package/dist/findings.js +57 -0
  71. package/dist/findings.js.map +1 -0
  72. package/dist/index.d.ts +32 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +194 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/javaCodegen.d.ts +44 -0
  77. package/dist/javaCodegen.d.ts.map +1 -0
  78. package/dist/javaCodegen.js +537 -0
  79. package/dist/javaCodegen.js.map +1 -0
  80. package/dist/launcher.d.ts +39 -0
  81. package/dist/launcher.d.ts.map +1 -0
  82. package/dist/launcher.js +153 -0
  83. package/dist/launcher.js.map +1 -0
  84. package/dist/lib.d.ts +13 -0
  85. package/dist/lib.d.ts.map +1 -0
  86. package/dist/lib.js +13 -0
  87. package/dist/lib.js.map +1 -0
  88. package/dist/project.d.ts +33 -0
  89. package/dist/project.d.ts.map +1 -0
  90. package/dist/project.js +104 -0
  91. package/dist/project.js.map +1 -0
  92. package/dist/skeletons.d.ts +16 -0
  93. package/dist/skeletons.d.ts.map +1 -0
  94. package/dist/skeletons.js +120 -0
  95. package/dist/skeletons.js.map +1 -0
  96. package/dist/traceReport.d.ts +75 -0
  97. package/dist/traceReport.d.ts.map +1 -0
  98. package/dist/traceReport.js +91 -0
  99. package/dist/traceReport.js.map +1 -0
  100. package/dist/version.d.ts +3 -0
  101. package/dist/version.d.ts.map +1 -0
  102. package/dist/version.js +4 -0
  103. package/dist/version.js.map +1 -0
  104. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Horizon (FTC 36596)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,18 @@
1
+ Zenith
2
+ Copyright (c) 2026 Horizon (FTC 36596)
3
+
4
+ Zenith is licensed under the MIT License; see LICENSE.
5
+
6
+ Third-party assets
7
+
8
+ - BIOBUZZ field images (apps/web/public/fields/biobuzz/*.webp) by Team Juice 16236, from the r/FTC
9
+ post "BIOBUZZ custom field images (MeepMeep compatible)". Used with the credit the author asks for.
10
+
11
+ Bundled fonts
12
+
13
+ The following fonts are bundled in apps/web/public/fonts/. Each is licensed under the SIL Open Font
14
+ License, Version 1.1. The full licence text ships next to each font file.
15
+
16
+ - Jost, licensed under the SIL Open Font License 1.1 (jost-OFL.txt).
17
+ - IBM Plex Sans, licensed under the SIL Open Font License 1.1 (ibm-plex-sans-OFL.txt).
18
+ - JetBrains Mono, licensed under the SIL Open Font License 1.1 (jetbrains-mono-OFL.txt).
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # @horizon36596/zenith-cli
2
+
3
+ The `zenith` command for [Zenith](https://libraries.horizon36596.org/zenith/), the FTC autonomous
4
+ planner by Horizon (FTC 36596). It validates, estimates, renders, generates code for, deploys and
5
+ simulates the autos in a robot repository, with `--json` on every verb and no prompts, so a CI job
6
+ or an agent can drive it.
7
+
8
+ ```
9
+ npm install --save-dev @horizon36596/zenith-cli
10
+ npx zenith --version
11
+ npx zenith init
12
+ npx zenith validate autos/*.auto.json
13
+ ```
14
+
15
+ Requires Node 22 or newer. Documentation: https://libraries.horizon36596.org/zenith/. Licence: MIT.
16
+
17
+ ## Package reference
18
+
19
+ The `zenith` command line tool: every verb an editor, a CI job, or an agent needs to work with a
20
+ Zenith project without opening the web app. The docs site's CLI reference
21
+ (https://libraries.horizon36596.org/zenith/cli-reference/) is the user-facing version of this file.
22
+
23
+ ### Install and run
24
+
25
+ Inside this monorepo:
26
+
27
+ ```
28
+ pnpm install
29
+ pnpm --filter @horizon36596/zenith-cli build
30
+ pnpm zenith -- <verb> [args]
31
+ ```
32
+
33
+ `pnpm zenith` forwards the `--` separator pnpm inserts before `<verb>`; `main()` drops that one
34
+ leading `--` before parsing, so flags after the verb (`--project`, `--json`, ...) work normally.
35
+
36
+ Or, once built, `zenith` is the package's bin and can be run directly (`node packages/cli/dist/index.js`,
37
+ or just `zenith` if the workspace's `node_modules/.bin` is on `PATH`).
38
+
39
+ ### Project resolution
40
+
41
+ Every verb that needs a project looks for `zenith.json` in `--project <dir>` if given, otherwise by
42
+ walking up from the current directory. An auto argument may be a path to a `.auto.json` file or its
43
+ bare name inside `autosDir`.
44
+
45
+ ### Conventions every verb follows
46
+
47
+ - `--json` on every verb prints machine-readable output instead of the human-readable text, and never
48
+ mixes the two.
49
+ - No verb ever prompts. If stdin would be needed for a decision, the verb fails instead.
50
+ - Exit codes are the same convention everywhere: **0** ok, **1** the auto has findings errors (a
51
+ validation or feasibility problem in the auto itself), **2** usage, IO, or a verb that depends on a
52
+ `@horizon36596/zenith-core` export not landed yet.
53
+ - Every verb that calls a `@horizon36596/zenith-core` M1 export (`estimate`, `ledger`, `render`, `diff`) is real
54
+ as of this build; `src/coreGate.ts`'s `NotImplementedError` handling (`core M1 not landed yet:
55
+ ...` to stderr, or `{"error": "..."}` under `--json`, exit code 2) is a defensive fallback rather
56
+ than the common case, kept so a future gap still fails with a clear message instead of a stack
57
+ trace.
58
+ - Every verb that needs season-specific rules (`validate`, `estimate`, `render`, `sim`, `propose`)
59
+ resolves them through `@horizon36596/zenith-seasons` from the project's `field.json`. When the field names no
60
+ season plugin, or one this build does not carry, the season-gated checks (`CAPACITY`,
61
+ `EMPTY_SHOT`, `MOUTH_LEADING`, `SWEEP_SPEED`, `START_ILLEGAL`, `LEGAL_APPROACH`, `TURRET_RANGE`)
62
+ and the ledger are simply absent rather than wrong; the verb still runs and prints a `warning:`
63
+ line to stderr (and a `seasonWarnings` array under `--json`) saying so.
64
+
65
+ ### Verbs
66
+
67
+ #### `zenith validate <autos...> [--json] [--project <dir>]`
68
+
69
+ Checks one or more `.auto.json` files against the schema and the feasibility rules. Prints a findings
70
+ table (or JSON). Exit 1 if any file has an error-level finding.
71
+
72
+ #### `zenith init [--command-library ivy|solverslib] [--json] [--force]`
73
+
74
+ Writes a starter `zenith.json` and an `autos/` directory that validates, for a new project.
75
+ `--command-library` writes `deploy.commandLibrary`; the value is read in any case and written
76
+ lowercase. There is no default: without it the `deploy` section names no library, `init` prints a
77
+ note, and `zenith codegen` refuses, as does `zenith deploy` once there is a `codegen` section, until
78
+ `"ivy"` or `"solverslib"` is added.
79
+
80
+ #### `zenith new <name> [--alliance RED|BLUE] [--json] [--force] [--project <dir>]`
81
+
82
+ Writes a new `.auto.json` from a canonical skeleton.
83
+
84
+ #### `zenith estimate <auto> [--json] [--explain] [--project <dir>]`
85
+
86
+ Prints a per-step table (id, kind, nominal time, low-high band, strafe percentage, an unknown-time
87
+ flag) and the total against the field's auto period. `--explain` also prints the assumptions behind
88
+ the numbers (`estimate.explain`). Calls `@horizon36596/zenith-core`'s `estimate`.
89
+
90
+ #### `zenith render <auto> (--svg <out> | --png <out>) [--alliance RED|BLUE] [--base <otherAuto>] [--json] [--project <dir>]`
91
+
92
+ Draws the field, the path, findings and the ledger to SVG or PNG. PNG rendering goes through
93
+ `@resvg/resvg-js` (a CLI-only dependency; `@horizon36596/zenith-core` stays pure and only produces SVG text).
94
+ `--base` loads and validates a second auto for comparison; `@horizon36596/zenith-core`'s `render` does not yet take
95
+ an overlay, so today `--base` is validated but not drawn, and the CLI says so on stderr rather than
96
+ silently ignoring it.
97
+
98
+ #### `zenith codegen <auto> [--json] [--project <dir>]`
99
+
100
+ Writes the generated, read-only Java class described on the docs site's Robot runtime page
101
+ ("Generated Java") to `zenith.json`'s `codegen.dir/<package path>/<PascalName>Generated.java`. The header
102
+ records the source file's sha256 (first 8 hex characters) so a stale generated file is easy to spot.
103
+ Output is deterministic: the same auto always produces byte-identical Java. The class is written for
104
+ the runtime `deploy.commandLibrary` names, `"ivy"` or `"solverslib"`. The key is required and has no
105
+ default: without it, or without a `deploy` block, the verb exits 2 with the same message as
106
+ `zenith deploy`.
107
+
108
+ A path step from `"current"` is written as `DeferredPath.of(...)`, the call both runtimes' `AutoBuilder`
109
+ makes, so it is built from the live pose when the step starts. Numbers that come from the path's
110
+ length (a marker placed anywhere but the start, a heading shared out by arc length) stay the planned
111
+ path's, and the generated method says so in a comment. `examples/starter/generated/` in the
112
+ repository has the output for both libraries, and
113
+ [Choosing a command library](https://libraries.horizon36596.org/zenith/command-libraries/) explains the
114
+ differences.
115
+
116
+ #### `zenith deploy [--dry-run] [--json] [--project <dir>]`
117
+
118
+ Copies `autosDir/*.auto.json`, `waypoints.json`, `robot.json` and the field file into
119
+ `zenith.json.deploy.dir`. When `zenith.json` has a `codegen` section, it also writes or refreshes a
120
+ three-line `@Autonomous` stub per auto under `codegen.dir/<package>/generated/<PascalName>Auto.java`.
121
+ Each stub extends `AutoFromFile` from the runtime `deploy.commandLibrary` names:
122
+ `org.horizon36596.zenith.ivy` for `"ivy"` or `org.horizon36596.zenith.solverslib` for `"solverslib"`.
123
+ The stubs are the only Java the verb writes, so the key is required, with no default, exactly when
124
+ there is a `codegen` section; then, without it, the verb copies nothing, exits 2, and prints:
125
+
126
+ ```
127
+ zenith: zenith.json does not say which command library your robot code uses, and Zenith does not pick one. Add "commandLibrary": "solverslib" or "commandLibrary": "ivy" to its "deploy" section. See https://libraries.horizon36596.org/zenith/command-libraries/.
128
+ ```
129
+
130
+ With no `codegen` section it only copies, and needs no library. The first line of the output names the library
131
+ (`command library: Ivy (org.horizon36596.zenith.ivy)`), and `--json` output carries it as
132
+ `commandLibrary` (`null` when there is no `codegen` section and none is named). Stubs for autos that no longer exist are removed. An auto with a findings error is skipped (reported, not
133
+ copied) but does not stop the other autos from deploying; overall exit code is 1 if anything was
134
+ skipped. `--dry-run` lists every action (`copy:`, `write:`, `keep:`, `remove:`) without touching disk.
135
+
136
+ #### `zenith diff <a> <b> [--json]`
137
+
138
+ Prints the structural diff between two autos: added, removed, changed and moved steps, as a table or
139
+ markdown-flavoured text (or as JSON under `--json`). Calls `@horizon36596/zenith-core`'s `diff`.
140
+
141
+ #### `zenith sim <auto> [--open] [--timeout <seconds>] [--json] [--project <dir>]`
142
+
143
+ Runs `zenith.json`'s `sim.command` (with `{auto}` substituted) through the shell, streaming its
144
+ output; a non-zero exit or a missing trace file fails with exit 2 and the command's output tail on
145
+ stderr. Reads the trace at `sim.trace` and prints estimate vs actual per step (nominal, actual, delta,
146
+ and the largest cross-track distance between recorded poses and the planned path) plus a whole-run
147
+ summary (structure contacts, launches, tips, pieces held at the end). `--open` also renders the plan
148
+ to `autosDir/.renders/<name>.sim.svg` and prints its path, without spawning an image viewer.
149
+
150
+ #### `zenith calibrate [--traces <dir>] [--write] [--date <date>] [--json] [--project <dir>]`
151
+
152
+ Pairs every path step's recorded duration (from every `*.trace.json` in `--traces`, or
153
+ `traces/`, or `sim.trace`'s directory) against the plan's geometry, and fits `accelInPerS2`, `settleS`,
154
+ and a per-heading-mode scale by a grid search over the trapezoidal time model
155
+ (the docs site's Simulation page). Reports the residual band left after both
156
+ corrections. `--write` updates `robot.json`'s `kinematics.accelInPerS2` and `kinematics.settleS` with
157
+ provenance `CALIBRATED FROM SIM <date> (<n> steps)`; the per-mode scale has no field in
158
+ `@horizon36596/zenith-schema` yet, so it is reported but not persisted. The CLI reads the clock for the provenance
159
+ date (or takes `--date`); `@horizon36596/zenith-core`'s fit itself never does.
160
+
161
+ #### `zenith propose <auto> [--dry-run] [--base <branch>] [--json] [--project <dir>]`
162
+
163
+ The CLI half of proposing an auto change as a pull request (the docs site's GitHub page). Only `--dry-run` is implemented
164
+ here: it validates the auto (a findings error blocks with exit 1), renders an SVG to
165
+ `autosDir/.renders/<name>.svg`, and prints the PR body — `@horizon36596/zenith-core`'s `prBody`, given the local
166
+ render path directly as `renderUrl` since a `--dry-run` run has made no commit for a real GitHub URL
167
+ to point at — to stdout. Without `--dry-run` it prints "GitHub mode: use the web app or pass
168
+ --dry-run" and exits 2, because opening an actual pull request is `@horizon36596/zenith-github`'s job, which this
169
+ package does not depend on.
170
+
171
+ `zenith diff` and `zenith propose --dry-run` both format through `@horizon36596/zenith-core`'s own
172
+ `diffToMarkdown`/`prBody` (this package carries no separate copy of either), so a diff printed by the
173
+ CLI and a PR body opened by `@horizon36596/zenith-github` read the same way for the same inputs.
174
+
175
+ ### Driving it with `--json`
176
+
177
+ Every verb above is safe for an agent or a script to call directly: pass `--json`, read stdout as one
178
+ JSON document, and use the exit code to decide whether to keep going.
179
+
180
+ - Check the exit code first. `0` means the JSON is the successful result. `1` means the auto has a
181
+ findings error; the JSON is a findings list, not the thing you asked for. `2` means something
182
+ prevented the verb from running at all (bad usage, a missing project, an IO error, or a `@horizon36596/zenith-core`
183
+ export not landed yet); the JSON (where the verb prints one on `2`) is `{"error": "..."}`.
184
+ - Never parse the human-readable (non-`--json`) output. It is formatted for a terminal and can change
185
+ wording between versions without notice; the JSON shape is the contract.
186
+ - `zenith estimate <auto> --json` and `zenith sim <auto> --json` are the two verbs most useful for an
187
+ agent checking its own work after editing an auto: estimate answers "does this still fit the auto
188
+ period", and sim (once a trace exists) answers "did the robot actually do what the plan said".
189
+ - `zenith propose <auto> --dry-run --json` is the one to call before asking a human to open a real pull
190
+ request: it fails loudly (exit 1) if the auto does not validate, and otherwise hands back the exact
191
+ PR body text under `body` and the render path under `render`.
192
+ - A verb that depends on a `@horizon36596/zenith-core` M1 export not yet landed in this checkout (`estimate`,
193
+ `ledger`, `render`, `diff`) exits 2 with `{"error": "core M1 not landed yet: ..."}` rather than a
194
+ stack trace, so a script can detect "not ready yet" and retry later instead of treating it as its own
195
+ bug.
196
+
197
+ ### Tests
198
+
199
+ ```
200
+ pnpm --filter @horizon36596/zenith-cli test
201
+ ```
202
+
203
+ Each verb has a test file next to its command module under `src/commands/`, plus tests for the pure
204
+ helpers (`src/javaCodegen.ts`, `src/calibrateFit.ts`, `src/traceReport.ts`, `src/format/`). Tests for
205
+ verbs that call into a `@horizon36596/zenith-core` M1 export check both branches - the real result once that export
206
+ has landed, and the `core M1 not landed yet` gate message before it has - so the suite passes and stays
207
+ meaningful throughout M1, not just at the end of it.
@@ -0,0 +1,54 @@
1
+ import { type Finding, type Plan } from "@horizon36596/zenith-core";
2
+ import { type Auto, type Field, type Robot } from "@horizon36596/zenith-schema";
3
+ import { type Project } from "./project.js";
4
+ /** One auto file, parsed, resolved, planned and checked, ready for a verb to use. */
5
+ export interface LoadedAuto {
6
+ path: string;
7
+ auto: Auto;
8
+ robot: Robot;
9
+ field: Field;
10
+ plan: Plan;
11
+ /** Every finding `check()` raised, schema problems included. */
12
+ findings: Finding[];
13
+ /**
14
+ * Full sentences from `@horizon36596/zenith-seasons` when `field.json` names a season plugin this build does
15
+ * not carry (or none at all): the routine still plans and estimates, but every season-dependent
16
+ * check and ledger row is absent, and a caller should say so rather than stay silent about it.
17
+ */
18
+ seasonWarnings: string[];
19
+ }
20
+ export type LoadAutoResult = {
21
+ ok: true;
22
+ loaded: LoadedAuto;
23
+ } | {
24
+ ok: false;
25
+ path: string;
26
+ findings: Finding[];
27
+ };
28
+ /**
29
+ * `auto.name` is substituted into a shell command (`zenith sim`) and into filesystem paths
30
+ * (`zenith sim --open`, `zenith propose`) elsewhere in this package. Constraining it here, at the
31
+ * one place every verb loads an auto, means every one of those call sites can use the value without
32
+ * re-checking it: no shell metacharacter, path separator or ".." segment can ever reach them
33
+ * through `auto.name`.
34
+ */
35
+ export declare const AUTO_NAME_PATTERN: RegExp;
36
+ export declare function assertValidAutoName(name: string): void;
37
+ /**
38
+ * Turns a verb's `<auto>` argument into a file path: a path that exists as given, or a bare name
39
+ * resolved against the project's `autosDir`. Falls back to the given path so a bad argument still
40
+ * fails with a plain "cannot read" message rather than a confusing one about the autos directory.
41
+ */
42
+ export declare function resolveAutoPath(cwd: string, project: Project, autoArg: string): string;
43
+ /**
44
+ * A schema issue's `path` (e.g. `["steps", 4, "segments", 0, "control"]`) names a JSON location,
45
+ * not a step id, so a naive `path.join(".")` used as `Finding.stepId` sends `selectStep` nowhere
46
+ * an agent or the editor can act on (finding 26). This walks the raw (unvalidated) JSON back along
47
+ * the issue's path, remembering the deepest `steps`/`then`/`else` array index it passes through,
48
+ * and returns that step's own `id` when it has one, or its positional id (`step<n>`) otherwise —
49
+ * the same convention `@horizon36596/zenith-core`'s `effectiveId` uses for a top-level, unnamed step.
50
+ */
51
+ export declare function nearestStepId(rawJson: unknown, path: readonly (string | number)[]): string;
52
+ /** Parses, resolves, plans and checks one auto file. Never throws; schema failures come back as findings. */
53
+ export declare function loadAutoAndPlan(path: string, project: Project): LoadAutoResult;
54
+ //# sourceMappingURL=autoLoad.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoLoad.d.ts","sourceRoot":"","sources":["../src/autoLoad.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAEpI,OAAO,EAAe,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAC7F,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAE1D,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,gEAAgE;IAChE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB;;;;OAIG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AASjH;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAsB,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtD;AA0BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAatF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,CAyB1F;AAED,6GAA6G;AAC7G,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,cAAc,CAwC9E"}
@@ -0,0 +1,139 @@
1
+ import { existsSync } from "node:fs";
2
+ import { isAbsolute, join, resolve as resolvePath } from "node:path";
3
+ import { check, estimate, loadAuto, loadField, loadRobot, plan, resolve } from "@horizon36596/zenith-core";
4
+ import { resolveSeason } from "@horizon36596/zenith-seasons";
5
+ import { SchemaError } from "@horizon36596/zenith-schema";
6
+ import { readJsonFile } from "./project.js";
7
+ const schemaFinding = (stepId, message) => ({
8
+ severity: "error",
9
+ stepId,
10
+ code: "SCHEMA",
11
+ message,
12
+ });
13
+ /**
14
+ * `auto.name` is substituted into a shell command (`zenith sim`) and into filesystem paths
15
+ * (`zenith sim --open`, `zenith propose`) elsewhere in this package. Constraining it here, at the
16
+ * one place every verb loads an auto, means every one of those call sites can use the value without
17
+ * re-checking it: no shell metacharacter, path separator or ".." segment can ever reach them
18
+ * through `auto.name`.
19
+ */
20
+ export const AUTO_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
21
+ export function assertValidAutoName(name) {
22
+ if (!AUTO_NAME_PATTERN.test(name)) {
23
+ throw new Error(`Invalid auto file:\n name: must match ${AUTO_NAME_PATTERN.source} (letters, digits, ".", "_", "-" only); got ${JSON.stringify(name)}.`);
24
+ }
25
+ }
26
+ /**
27
+ * An auto may name its own robot or field file; when it does, that file wins over the link file's,
28
+ * and a broken override is a finding rather than a crash. Mirrors `commands/validate.ts`'s
29
+ * `override`, kept separate so this module has no dependency on that command.
30
+ */
31
+ function override(project, path, load, fallback, findings) {
32
+ if (path === undefined)
33
+ return fallback;
34
+ const full = isAbsolute(path) ? path : join(project.root, path);
35
+ try {
36
+ return load(readJsonFile(full));
37
+ }
38
+ catch (error) {
39
+ findings.push(schemaFinding("(file)", `Cannot use ${path}: ${error.message.split("\n")[0] ?? ""}`));
40
+ return fallback;
41
+ }
42
+ }
43
+ /**
44
+ * Turns a verb's `<auto>` argument into a file path: a path that exists as given, or a bare name
45
+ * resolved against the project's `autosDir`. Falls back to the given path so a bad argument still
46
+ * fails with a plain "cannot read" message rather than a confusing one about the autos directory.
47
+ */
48
+ export function resolveAutoPath(cwd, project, autoArg) {
49
+ const direct = isAbsolute(autoArg) ? autoArg : resolvePath(cwd, autoArg);
50
+ if (existsSync(direct))
51
+ return direct;
52
+ // A path (as opposed to a bare name) may be meant relative to the project root rather than the
53
+ // working directory, e.g. `--project examples/starter` paired with `autos/x.auto.json` when the
54
+ // shell's cwd is the repository root, not the project.
55
+ if (!isAbsolute(autoArg)) {
56
+ const inProject = join(project.root, autoArg);
57
+ if (inProject !== direct && existsSync(inProject))
58
+ return inProject;
59
+ }
60
+ const byName = join(project.root, project.link.autosDir, `${autoArg}.auto.json`);
61
+ if (existsSync(byName))
62
+ return byName;
63
+ return direct;
64
+ }
65
+ /**
66
+ * A schema issue's `path` (e.g. `["steps", 4, "segments", 0, "control"]`) names a JSON location,
67
+ * not a step id, so a naive `path.join(".")` used as `Finding.stepId` sends `selectStep` nowhere
68
+ * an agent or the editor can act on (finding 26). This walks the raw (unvalidated) JSON back along
69
+ * the issue's path, remembering the deepest `steps`/`then`/`else` array index it passes through,
70
+ * and returns that step's own `id` when it has one, or its positional id (`step<n>`) otherwise —
71
+ * the same convention `@horizon36596/zenith-core`'s `effectiveId` uses for a top-level, unnamed step.
72
+ */
73
+ export function nearestStepId(rawJson, path) {
74
+ let node = rawJson;
75
+ let lastStep;
76
+ let lastIndex = -1;
77
+ const stepListKeys = new Set(["steps", "then", "else"]);
78
+ for (let i = 0; i < path.length; i += 1) {
79
+ const key = path[i];
80
+ if (node === null || typeof node !== "object")
81
+ break;
82
+ const next = Array.isArray(node)
83
+ ? node[key]
84
+ : node[key];
85
+ if (typeof key === "string" && stepListKeys.has(key) && typeof path[i + 1] === "number") {
86
+ const idx = path[i + 1];
87
+ if (Array.isArray(next) && idx >= 0 && idx < next.length) {
88
+ lastStep = next[idx];
89
+ lastIndex = idx;
90
+ }
91
+ }
92
+ node = next;
93
+ }
94
+ if (lastStep !== undefined && lastStep !== null && typeof lastStep === "object") {
95
+ const id = lastStep.id;
96
+ if (typeof id === "string" && id.length > 0)
97
+ return id;
98
+ }
99
+ return lastIndex >= 0 ? `step${String(lastIndex + 1)}` : "(file)";
100
+ }
101
+ /** Parses, resolves, plans and checks one auto file. Never throws; schema failures come back as findings. */
102
+ export function loadAutoAndPlan(path, project) {
103
+ let json;
104
+ try {
105
+ json = readJsonFile(path);
106
+ }
107
+ catch (error) {
108
+ return { ok: false, path, findings: [schemaFinding("(file)", error.message)] };
109
+ }
110
+ try {
111
+ const auto = loadAuto(json);
112
+ assertValidAutoName(auto.name);
113
+ const findings = [];
114
+ const robot = override(project, auto.robot, loadRobot, project.robot, findings);
115
+ const field = override(project, auto.field, loadField, project.field, findings);
116
+ const planned = plan(resolve(auto, project.waypoints), robot, field);
117
+ const season = resolveSeason(field);
118
+ findings.push(...check(planned, estimate(planned, robot), robot, field, season.rules));
119
+ return {
120
+ ok: true,
121
+ loaded: { path, auto, robot, field, plan: planned, findings, seasonWarnings: [...season.warnings] },
122
+ };
123
+ }
124
+ catch (error) {
125
+ if (error instanceof SchemaError) {
126
+ return {
127
+ ok: false,
128
+ path,
129
+ findings: error.issues.map((issue) => schemaFinding(issue.path.length === 0 ? "(file)" : nearestStepId(json, issue.path), `${issue.path.join(".")}: ${issue.message}`)),
130
+ };
131
+ }
132
+ return {
133
+ ok: false,
134
+ path,
135
+ findings: [schemaFinding("(file)", error instanceof Error ? error.message : String(error))],
136
+ };
137
+ }
138
+ }
139
+ //# sourceMappingURL=autoLoad.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoLoad.js","sourceRoot":"","sources":["../src/autoLoad.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `zenith calibrate`'s fit (site/docs/simulation.md): pairs a trace's
3
+ * recorded step durations against the plan's geometry and fits the free parameters of the time
4
+ * model - `accel`, `settleS`, and a per
5
+ * heading-mode scale. Pure: no clock, no randomness, so the same samples always fit the same
6
+ * numbers and a test can assert on them exactly.
7
+ *
8
+ * The fit is deliberately simple rather than the full two-pass velocity profile `core.estimate`
9
+ * will run once M1 lands: a symmetric trapezoidal accelerate/cruise/decelerate model against a
10
+ * fixed cruise speed (the robot's own measured `maxForwardVelInPerS`), searched on a coarse grid
11
+ * over `accel` and `settleS` (the spec allows "simple grid or closed form"). The per-mode scale
12
+ * then absorbs whatever the trapezoid model does not capture for a `constant` or `linear` heading
13
+ * leg, which behaves more like a strafe. The residual band is what is left after both corrections,
14
+ * reported as the fraction the estimate should still expect to be wrong by.
15
+ */
16
+ export interface CalibrationSample {
17
+ stepId: string;
18
+ /** The path step's heading mode, or "none" for a step this fit does not otherwise use. */
19
+ headingMode: string;
20
+ lengthIn: number;
21
+ actualS: number;
22
+ }
23
+ export type ModeScale = Record<string, number>;
24
+ export interface CalibrationResult {
25
+ accelInPerS2: number;
26
+ settleS: number;
27
+ scaleByMode: ModeScale;
28
+ /** The residual band as fractions of the actual duration, e.g. -0.12 to 0.15. Zero when there are
29
+ * no samples to measure a residual from. */
30
+ residualLowFraction: number;
31
+ residualHighFraction: number;
32
+ sampleCount: number;
33
+ }
34
+ /** A symmetric trapezoid: accelerate to `cruiseInPerS` (or to a lower peak on a short leg), cruise,
35
+ * decelerate at the same rate, then add the settle time. */
36
+ export declare function predictTravelS(lengthIn: number, cruiseInPerS: number, accelInPerS2: number, settleS: number): number;
37
+ export declare function fitCalibration(samples: readonly CalibrationSample[], maxForwardVelInPerS: number): CalibrationResult;
38
+ //# sourceMappingURL=calibrateFit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calibrateFit.d.ts","sourceRoot":"","sources":["../src/calibrateFit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,SAAS,CAAC;IACvB;gDAC4C;IAC5C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD;4DAC4D;AAC5D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CASpH;AAYD,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,EAAE,mBAAmB,EAAE,MAAM,GAAG,iBAAiB,CA+CpH"}
@@ -0,0 +1,69 @@
1
+ const ACCEL_GRID = { minInPerS2: 10, maxInPerS2: 220, stepInPerS2: 5 };
2
+ const SETTLE_GRID = { minS: 0, maxS: 1, stepS: 0.05 };
3
+ /** A symmetric trapezoid: accelerate to `cruiseInPerS` (or to a lower peak on a short leg), cruise,
4
+ * decelerate at the same rate, then add the settle time. */
5
+ export function predictTravelS(lengthIn, cruiseInPerS, accelInPerS2, settleS) {
6
+ if (lengthIn <= 0 || cruiseInPerS <= 0 || accelInPerS2 <= 0)
7
+ return settleS;
8
+ const accelDistanceIn = (cruiseInPerS * cruiseInPerS) / (2 * accelInPerS2);
9
+ if (2 * accelDistanceIn <= lengthIn) {
10
+ const cruiseS = (lengthIn - 2 * accelDistanceIn) / cruiseInPerS;
11
+ return 2 * (cruiseInPerS / accelInPerS2) + cruiseS + settleS;
12
+ }
13
+ const peakInPerS = Math.sqrt(accelInPerS2 * lengthIn);
14
+ return (2 * peakInPerS) / accelInPerS2 + settleS;
15
+ }
16
+ function sumSquaredError(samples, cruiseInPerS, accelInPerS2, settleS) {
17
+ let error = 0;
18
+ for (const sample of samples) {
19
+ const predicted = predictTravelS(sample.lengthIn, cruiseInPerS, accelInPerS2, settleS);
20
+ const residual = predicted - sample.actualS;
21
+ error += residual * residual;
22
+ }
23
+ return error;
24
+ }
25
+ export function fitCalibration(samples, maxForwardVelInPerS) {
26
+ const usable = samples.filter((sample) => sample.lengthIn > 0 && sample.actualS > 0);
27
+ if (usable.length === 0) {
28
+ return { accelInPerS2: 0, settleS: 0, scaleByMode: {}, residualLowFraction: 0, residualHighFraction: 0, sampleCount: 0 };
29
+ }
30
+ let bestAccel = ACCEL_GRID.minInPerS2;
31
+ let bestSettle = SETTLE_GRID.minS;
32
+ let bestError = Number.POSITIVE_INFINITY;
33
+ for (let accel = ACCEL_GRID.minInPerS2; accel <= ACCEL_GRID.maxInPerS2; accel += ACCEL_GRID.stepInPerS2) {
34
+ for (let settle = SETTLE_GRID.minS; settle <= SETTLE_GRID.maxS; settle += SETTLE_GRID.stepS) {
35
+ const error = sumSquaredError(usable, maxForwardVelInPerS, accel, settle);
36
+ if (error < bestError) {
37
+ bestError = error;
38
+ bestAccel = accel;
39
+ bestSettle = settle;
40
+ }
41
+ }
42
+ }
43
+ const ratiosByMode = new Map();
44
+ for (const sample of usable) {
45
+ const predicted = predictTravelS(sample.lengthIn, maxForwardVelInPerS, bestAccel, bestSettle);
46
+ const ratio = predicted === 0 ? 1 : sample.actualS / predicted;
47
+ const list = ratiosByMode.get(sample.headingMode) ?? [];
48
+ list.push(ratio);
49
+ ratiosByMode.set(sample.headingMode, list);
50
+ }
51
+ const scaleByMode = {};
52
+ for (const [mode, ratios] of ratiosByMode) {
53
+ scaleByMode[mode] = ratios.reduce((sum, ratio) => sum + ratio, 0) / ratios.length;
54
+ }
55
+ const residualFractions = usable.map((sample) => {
56
+ const scale = scaleByMode[sample.headingMode] ?? 1;
57
+ const predicted = predictTravelS(sample.lengthIn, maxForwardVelInPerS, bestAccel, bestSettle) * scale;
58
+ return (predicted - sample.actualS) / sample.actualS;
59
+ });
60
+ return {
61
+ accelInPerS2: bestAccel,
62
+ settleS: bestSettle,
63
+ scaleByMode,
64
+ residualLowFraction: Math.min(0, ...residualFractions),
65
+ residualHighFraction: Math.max(0, ...residualFractions),
66
+ sampleCount: usable.length,
67
+ };
68
+ }
69
+ //# sourceMappingURL=calibrateFit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calibrateFit.js","sourceRoot":"","sources":["../src/calibrateFit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Link } from "@horizon36596/zenith-schema";
2
+ /**
3
+ * The command library the robot runtime builds on, from `zenith.json` `deploy.commandLibrary`
4
+ * (site/docs/command-libraries.md). Neither library is a default: every command that writes Java
5
+ * refuses until the project names one.
6
+ */
7
+ export type CommandLibrary = "solverslib" | "ivy";
8
+ /** Both libraries, in the order Zenith lists them. Neither is a default. */
9
+ export declare const LIBRARIES: readonly CommandLibrary[];
10
+ /** The Java package each library's runtime classes live in. */
11
+ export declare const RUNTIME_PACKAGE: Readonly<Record<CommandLibrary, string>>;
12
+ /** The name a person reads, for CLI output and the desktop deploy preview. */
13
+ export declare const LIBRARY_LABEL: Readonly<Record<CommandLibrary, string>>;
14
+ /**
15
+ * @returns the project's command library
16
+ * @throws ProjectError when `zenith.json` does not name one; Zenith never picks one for a team
17
+ */
18
+ export declare function commandLibraryOf(link: Link): CommandLibrary;
19
+ //# sourceMappingURL=commandLibrary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commandLibrary.d.ts","sourceRoot":"","sources":["../src/commandLibrary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAGxD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,KAAK,CAAC;AAElD,4EAA4E;AAC5E,eAAO,MAAM,SAAS,EAAE,SAAS,cAAc,EAA0B,CAAC;AAE1E,+DAA+D;AAC/D,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAGpE,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAGlE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,CAU3D"}
@@ -0,0 +1,27 @@
1
+ import { ProjectError } from "./project.js";
2
+ /** Both libraries, in the order Zenith lists them. Neither is a default. */
3
+ export const LIBRARIES = ["solverslib", "ivy"];
4
+ /** The Java package each library's runtime classes live in. */
5
+ export const RUNTIME_PACKAGE = {
6
+ solverslib: "org.horizon36596.zenith.solverslib",
7
+ ivy: "org.horizon36596.zenith.ivy",
8
+ };
9
+ /** The name a person reads, for CLI output and the desktop deploy preview. */
10
+ export const LIBRARY_LABEL = {
11
+ solverslib: "SolversLib",
12
+ ivy: "Ivy",
13
+ };
14
+ /**
15
+ * @returns the project's command library
16
+ * @throws ProjectError when `zenith.json` does not name one; Zenith never picks one for a team
17
+ */
18
+ export function commandLibraryOf(link) {
19
+ const library = link.deploy?.commandLibrary;
20
+ if (library === undefined) {
21
+ throw new ProjectError('zenith.json does not say which command library your robot code uses, and Zenith does not pick one. ' +
22
+ 'Add "commandLibrary": "solverslib" or "commandLibrary": "ivy" to its "deploy" section. ' +
23
+ "See https://libraries.horizon36596.org/zenith/command-libraries/.");
24
+ }
25
+ return library;
26
+ }
27
+ //# sourceMappingURL=commandLibrary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commandLibrary.js","sourceRoot":"","sources":["../src/commandLibrary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAS5C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,SAAS,GAA8B,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;AAE1E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAA6C;IACvE,UAAU,EAAE,oCAAoC;IAChD,GAAG,EAAE,6BAA6B;CACnC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,aAAa,GAA6C;IACrE,UAAU,EAAE,YAAY;IACxB,GAAG,EAAE,KAAK;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC;IAC5C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,YAAY,CACpB,qGAAqG;YACnG,yFAAyF;YACzF,mEAAmE,CACtE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}