@netlify/axis 0.2.0

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 (230) hide show
  1. package/README.md +977 -0
  2. package/dist/adapters/base/acp-adapter.d.ts +44 -0
  3. package/dist/adapters/base/acp-adapter.d.ts.map +1 -0
  4. package/dist/adapters/base/acp-adapter.js +559 -0
  5. package/dist/adapters/base/acp-adapter.js.map +1 -0
  6. package/dist/adapters/base/agent-adapter.d.ts +132 -0
  7. package/dist/adapters/base/agent-adapter.d.ts.map +1 -0
  8. package/dist/adapters/base/agent-adapter.js +212 -0
  9. package/dist/adapters/base/agent-adapter.js.map +1 -0
  10. package/dist/adapters/claude-code.d.ts +3 -0
  11. package/dist/adapters/claude-code.d.ts.map +1 -0
  12. package/dist/adapters/claude-code.js +138 -0
  13. package/dist/adapters/claude-code.js.map +1 -0
  14. package/dist/adapters/claude-sdk.d.ts +11 -0
  15. package/dist/adapters/claude-sdk.d.ts.map +1 -0
  16. package/dist/adapters/claude-sdk.js +46 -0
  17. package/dist/adapters/claude-sdk.js.map +1 -0
  18. package/dist/adapters/codex.d.ts +3 -0
  19. package/dist/adapters/codex.d.ts.map +1 -0
  20. package/dist/adapters/codex.js +183 -0
  21. package/dist/adapters/codex.js.map +1 -0
  22. package/dist/adapters/gemini-acp.d.ts +11 -0
  23. package/dist/adapters/gemini-acp.d.ts.map +1 -0
  24. package/dist/adapters/gemini-acp.js +60 -0
  25. package/dist/adapters/gemini-acp.js.map +1 -0
  26. package/dist/adapters/gemini.d.ts +3 -0
  27. package/dist/adapters/gemini.d.ts.map +1 -0
  28. package/dist/adapters/gemini.js +222 -0
  29. package/dist/adapters/gemini.js.map +1 -0
  30. package/dist/adapters/goose.d.ts +3 -0
  31. package/dist/adapters/goose.d.ts.map +1 -0
  32. package/dist/adapters/goose.js +9 -0
  33. package/dist/adapters/goose.js.map +1 -0
  34. package/dist/adapters/registry.d.ts +7 -0
  35. package/dist/adapters/registry.d.ts.map +1 -0
  36. package/dist/adapters/registry.js +37 -0
  37. package/dist/adapters/registry.js.map +1 -0
  38. package/dist/adapters/utils/mcp.d.ts +23 -0
  39. package/dist/adapters/utils/mcp.d.ts.map +1 -0
  40. package/dist/adapters/utils/mcp.js +114 -0
  41. package/dist/adapters/utils/mcp.js.map +1 -0
  42. package/dist/adapters/utils/resolve.d.ts +20 -0
  43. package/dist/adapters/utils/resolve.d.ts.map +1 -0
  44. package/dist/adapters/utils/resolve.js +48 -0
  45. package/dist/adapters/utils/resolve.js.map +1 -0
  46. package/dist/adapters/utils/skills.d.ts +17 -0
  47. package/dist/adapters/utils/skills.d.ts.map +1 -0
  48. package/dist/adapters/utils/skills.js +52 -0
  49. package/dist/adapters/utils/skills.js.map +1 -0
  50. package/dist/adapters/utils/token-estimator.d.ts +21 -0
  51. package/dist/adapters/utils/token-estimator.d.ts.map +1 -0
  52. package/dist/adapters/utils/token-estimator.js +37 -0
  53. package/dist/adapters/utils/token-estimator.js.map +1 -0
  54. package/dist/baselines/diff.d.ts +9 -0
  55. package/dist/baselines/diff.d.ts.map +1 -0
  56. package/dist/baselines/diff.js +83 -0
  57. package/dist/baselines/diff.js.map +1 -0
  58. package/dist/baselines/index.d.ts +3 -0
  59. package/dist/baselines/index.d.ts.map +1 -0
  60. package/dist/baselines/index.js +3 -0
  61. package/dist/baselines/index.js.map +1 -0
  62. package/dist/baselines/store.d.ts +19 -0
  63. package/dist/baselines/store.d.ts.map +1 -0
  64. package/dist/baselines/store.js +104 -0
  65. package/dist/baselines/store.js.map +1 -0
  66. package/dist/cli.d.ts +3 -0
  67. package/dist/cli.d.ts.map +1 -0
  68. package/dist/cli.js +487 -0
  69. package/dist/cli.js.map +1 -0
  70. package/dist/config/loader.d.ts +8 -0
  71. package/dist/config/loader.d.ts.map +1 -0
  72. package/dist/config/loader.js +99 -0
  73. package/dist/config/loader.js.map +1 -0
  74. package/dist/config/validator.d.ts +11 -0
  75. package/dist/config/validator.d.ts.map +1 -0
  76. package/dist/config/validator.js +203 -0
  77. package/dist/config/validator.js.map +1 -0
  78. package/dist/docs-site/_astro/cli.DDWZtG0-.css +1 -0
  79. package/dist/docs-site/cli/index.html +18 -0
  80. package/dist/docs-site/configuration/index.html +121 -0
  81. package/dist/docs-site/content-assets.mjs +1 -0
  82. package/dist/docs-site/content-modules.mjs +1 -0
  83. package/dist/docs-site/data-store.json +9 -0
  84. package/dist/docs-site/index.html +69 -0
  85. package/dist/docs-site/quickstart/index.html +59 -0
  86. package/dist/docs-site/running/index.html +87 -0
  87. package/dist/docs-site/scoring/index.html +135 -0
  88. package/dist/index.d.ts +19 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +15 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/report-ui/index.html +291 -0
  93. package/dist/report-ui/mock-data.json +298 -0
  94. package/dist/reports/html.d.ts +7 -0
  95. package/dist/reports/html.d.ts.map +1 -0
  96. package/dist/reports/html.js +27 -0
  97. package/dist/reports/html.js.map +1 -0
  98. package/dist/reports/reader.d.ts +21 -0
  99. package/dist/reports/reader.d.ts.map +1 -0
  100. package/dist/reports/reader.js +110 -0
  101. package/dist/reports/reader.js.map +1 -0
  102. package/dist/reports/writer.d.ts +14 -0
  103. package/dist/reports/writer.d.ts.map +1 -0
  104. package/dist/reports/writer.js +106 -0
  105. package/dist/reports/writer.js.map +1 -0
  106. package/dist/runner/lifecycle.d.ts +10 -0
  107. package/dist/runner/lifecycle.d.ts.map +1 -0
  108. package/dist/runner/lifecycle.js +58 -0
  109. package/dist/runner/lifecycle.js.map +1 -0
  110. package/dist/runner/runner.d.ts +34 -0
  111. package/dist/runner/runner.d.ts.map +1 -0
  112. package/dist/runner/runner.js +330 -0
  113. package/dist/runner/runner.js.map +1 -0
  114. package/dist/scoring/category-score.d.ts +52 -0
  115. package/dist/scoring/category-score.d.ts.map +1 -0
  116. package/dist/scoring/category-score.js +157 -0
  117. package/dist/scoring/category-score.js.map +1 -0
  118. package/dist/scoring/composite.d.ts +5 -0
  119. package/dist/scoring/composite.d.ts.map +1 -0
  120. package/dist/scoring/composite.js +24 -0
  121. package/dist/scoring/composite.js.map +1 -0
  122. package/dist/scoring/deep-eval.d.ts +25 -0
  123. package/dist/scoring/deep-eval.d.ts.map +1 -0
  124. package/dist/scoring/deep-eval.js +382 -0
  125. package/dist/scoring/deep-eval.js.map +1 -0
  126. package/dist/scoring/goal-achievement.d.ts +5 -0
  127. package/dist/scoring/goal-achievement.d.ts.map +1 -0
  128. package/dist/scoring/goal-achievement.js +241 -0
  129. package/dist/scoring/goal-achievement.js.map +1 -0
  130. package/dist/scoring/index.d.ts +22 -0
  131. package/dist/scoring/index.d.ts.map +1 -0
  132. package/dist/scoring/index.js +115 -0
  133. package/dist/scoring/index.js.map +1 -0
  134. package/dist/scoring/parse-json.d.ts +6 -0
  135. package/dist/scoring/parse-json.d.ts.map +1 -0
  136. package/dist/scoring/parse-json.js +18 -0
  137. package/dist/scoring/parse-json.js.map +1 -0
  138. package/dist/scoring/sparse-index.d.ts +15 -0
  139. package/dist/scoring/sparse-index.d.ts.map +1 -0
  140. package/dist/scoring/sparse-index.js +338 -0
  141. package/dist/scoring/sparse-index.js.map +1 -0
  142. package/dist/scoring/triage.d.ts +15 -0
  143. package/dist/scoring/triage.d.ts.map +1 -0
  144. package/dist/scoring/triage.js +204 -0
  145. package/dist/scoring/triage.js.map +1 -0
  146. package/dist/skills/resolver.d.ts +19 -0
  147. package/dist/skills/resolver.d.ts.map +1 -0
  148. package/dist/skills/resolver.js +95 -0
  149. package/dist/skills/resolver.js.map +1 -0
  150. package/dist/transcript/categorize.d.ts +24 -0
  151. package/dist/transcript/categorize.d.ts.map +1 -0
  152. package/dist/transcript/categorize.js +233 -0
  153. package/dist/transcript/categorize.js.map +1 -0
  154. package/dist/transcript/classify.d.ts +7 -0
  155. package/dist/transcript/classify.d.ts.map +1 -0
  156. package/dist/transcript/classify.js +32 -0
  157. package/dist/transcript/classify.js.map +1 -0
  158. package/dist/transcript/extract.d.ts +24 -0
  159. package/dist/transcript/extract.d.ts.map +1 -0
  160. package/dist/transcript/extract.js +266 -0
  161. package/dist/transcript/extract.js.map +1 -0
  162. package/dist/transcript/index.d.ts +3 -0
  163. package/dist/transcript/index.d.ts.map +1 -0
  164. package/dist/transcript/index.js +2 -0
  165. package/dist/transcript/index.js.map +1 -0
  166. package/dist/transcript/normalize.d.ts +15 -0
  167. package/dist/transcript/normalize.d.ts.map +1 -0
  168. package/dist/transcript/normalize.js +160 -0
  169. package/dist/transcript/normalize.js.map +1 -0
  170. package/dist/transcript/types.d.ts +92 -0
  171. package/dist/transcript/types.d.ts.map +1 -0
  172. package/dist/transcript/types.js +2 -0
  173. package/dist/transcript/types.js.map +1 -0
  174. package/dist/transcript/urls.d.ts +10 -0
  175. package/dist/transcript/urls.d.ts.map +1 -0
  176. package/dist/transcript/urls.js +31 -0
  177. package/dist/transcript/urls.js.map +1 -0
  178. package/dist/types/agent.d.ts +80 -0
  179. package/dist/types/agent.d.ts.map +1 -0
  180. package/dist/types/agent.js +2 -0
  181. package/dist/types/agent.js.map +1 -0
  182. package/dist/types/baseline.d.ts +65 -0
  183. package/dist/types/baseline.d.ts.map +1 -0
  184. package/dist/types/baseline.js +2 -0
  185. package/dist/types/baseline.js.map +1 -0
  186. package/dist/types/config.d.ts +76 -0
  187. package/dist/types/config.d.ts.map +1 -0
  188. package/dist/types/config.js +2 -0
  189. package/dist/types/config.js.map +1 -0
  190. package/dist/types/index.d.ts +8 -0
  191. package/dist/types/index.d.ts.map +1 -0
  192. package/dist/types/index.js +8 -0
  193. package/dist/types/index.js.map +1 -0
  194. package/dist/types/output.d.ts +70 -0
  195. package/dist/types/output.d.ts.map +1 -0
  196. package/dist/types/output.js +15 -0
  197. package/dist/types/output.js.map +1 -0
  198. package/dist/types/report.d.ts +37 -0
  199. package/dist/types/report.d.ts.map +1 -0
  200. package/dist/types/report.js +2 -0
  201. package/dist/types/report.js.map +1 -0
  202. package/dist/types/scenario.d.ts +23 -0
  203. package/dist/types/scenario.d.ts.map +1 -0
  204. package/dist/types/scenario.js +2 -0
  205. package/dist/types/scenario.js.map +1 -0
  206. package/dist/types/scoring.d.ts +176 -0
  207. package/dist/types/scoring.d.ts.map +1 -0
  208. package/dist/types/scoring.js +2 -0
  209. package/dist/types/scoring.js.map +1 -0
  210. package/dist/ui/AnimatedTokens.d.ts +29 -0
  211. package/dist/ui/AnimatedTokens.d.ts.map +1 -0
  212. package/dist/ui/AnimatedTokens.js +53 -0
  213. package/dist/ui/AnimatedTokens.js.map +1 -0
  214. package/dist/ui/App.d.ts +6 -0
  215. package/dist/ui/App.d.ts.map +1 -0
  216. package/dist/ui/App.js +16 -0
  217. package/dist/ui/App.js.map +1 -0
  218. package/dist/ui/LiveDuration.d.ts +20 -0
  219. package/dist/ui/LiveDuration.d.ts.map +1 -0
  220. package/dist/ui/LiveDuration.js +31 -0
  221. package/dist/ui/LiveDuration.js.map +1 -0
  222. package/dist/ui/LiveStatus.d.ts +7 -0
  223. package/dist/ui/LiveStatus.d.ts.map +1 -0
  224. package/dist/ui/LiveStatus.js +52 -0
  225. package/dist/ui/LiveStatus.js.map +1 -0
  226. package/dist/ui/format.d.ts +29 -0
  227. package/dist/ui/format.d.ts.map +1 -0
  228. package/dist/ui/format.js +514 -0
  229. package/dist/ui/format.js.map +1 -0
  230. package/package.json +65 -0
@@ -0,0 +1,203 @@
1
+ export function validateConfig(data, filePath) {
2
+ if (typeof data !== "object" || data === null) {
3
+ throw new Error(`Invalid config at ${filePath}: must be a JSON object`);
4
+ }
5
+ const obj = data;
6
+ if (typeof obj.scenarios !== "string") {
7
+ throw new Error(`Invalid config at ${filePath}: "scenarios" must be a string path`);
8
+ }
9
+ if (!Array.isArray(obj.agents)) {
10
+ throw new Error(`Invalid config at ${filePath}: "agents" must be an array`);
11
+ }
12
+ for (let i = 0; i < obj.agents.length; i++) {
13
+ const entry = obj.agents[i];
14
+ if (typeof entry === "string")
15
+ continue;
16
+ if (typeof entry !== "object" || entry === null) {
17
+ throw new Error(`Invalid config at ${filePath}: agents[${i}] must be a string or object`);
18
+ }
19
+ const agentObj = entry;
20
+ if (typeof agentObj.adapter !== "string") {
21
+ throw new Error(`Invalid config at ${filePath}: agents[${i}] must have an "adapter" string`);
22
+ }
23
+ if (agentObj.scenarios !== undefined && !Array.isArray(agentObj.scenarios)) {
24
+ throw new Error(`Invalid config at ${filePath}: agents[${i}].scenarios must be an array`);
25
+ }
26
+ if (agentObj.skills !== undefined) {
27
+ validateSkillsSources(agentObj.skills, filePath, `agents[${i}].skills`);
28
+ }
29
+ }
30
+ if (obj.env !== undefined) {
31
+ if (!Array.isArray(obj.env) || !obj.env.every((v) => typeof v === "string")) {
32
+ throw new Error(`Invalid config at ${filePath}: "env" must be an array of strings`);
33
+ }
34
+ }
35
+ const defaults = obj.defaults;
36
+ if (defaults?.concurrency !== undefined) {
37
+ const c = defaults.concurrency;
38
+ if (typeof c !== "number" || !Number.isInteger(c) || c < 1) {
39
+ throw new Error(`Invalid config at ${filePath}: "defaults.concurrency" must be a positive integer`);
40
+ }
41
+ }
42
+ if (obj.mcp_servers !== undefined) {
43
+ validateMcpServers(obj.mcp_servers, filePath);
44
+ }
45
+ if (obj.skills !== undefined) {
46
+ validateSkillsSources(obj.skills, filePath, "skills");
47
+ }
48
+ }
49
+ export function validateScenario(data, filePath) {
50
+ if (typeof data !== "object" || data === null) {
51
+ throw new Error(`Invalid scenario at ${filePath}: must be a JSON object`);
52
+ }
53
+ const obj = data;
54
+ if (typeof obj.name !== "string") {
55
+ throw new Error(`Invalid scenario at ${filePath}: missing required field "name"`);
56
+ }
57
+ if (typeof obj.prompt !== "string") {
58
+ throw new Error(`Invalid scenario at ${filePath}: missing required field "prompt"`);
59
+ }
60
+ if (typeof obj.rubric === "string") {
61
+ // String rubric — freeform evaluation description
62
+ }
63
+ else if (Array.isArray(obj.rubric)) {
64
+ for (let i = 0; i < obj.rubric.length; i++) {
65
+ const entry = obj.rubric[i];
66
+ if (typeof entry.check !== "string") {
67
+ throw new Error(`Invalid scenario at ${filePath}: rubric[${i}] missing "check" string`);
68
+ }
69
+ if (entry.weight !== undefined && typeof entry.weight !== "number") {
70
+ throw new Error(`Invalid scenario at ${filePath}: rubric[${i}].weight must be a number`);
71
+ }
72
+ }
73
+ // Resolve weights so downstream code always has them
74
+ obj.rubric = resolveRubricWeights(obj.rubric);
75
+ }
76
+ else {
77
+ throw new Error(`Invalid scenario at ${filePath}: "rubric" must be a string or array`);
78
+ }
79
+ if (obj.agents !== undefined) {
80
+ if (!Array.isArray(obj.agents) || obj.agents.length === 0) {
81
+ throw new Error(`Invalid scenario at ${filePath}: "agents" must be a non-empty array of strings`);
82
+ }
83
+ for (let i = 0; i < obj.agents.length; i++) {
84
+ if (typeof obj.agents[i] !== "string") {
85
+ throw new Error(`Invalid scenario at ${filePath}: agents[${i}] must be a string`);
86
+ }
87
+ }
88
+ }
89
+ if (obj.skills !== undefined) {
90
+ validateSkillsSources(obj.skills, filePath, "skills");
91
+ }
92
+ if (obj.setup !== undefined) {
93
+ validateLifecycleActions(obj.setup, filePath, "setup");
94
+ }
95
+ if (obj.teardown !== undefined) {
96
+ validateLifecycleActions(obj.teardown, filePath, "teardown");
97
+ }
98
+ }
99
+ function validateMcpServers(data, filePath) {
100
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
101
+ throw new Error(`Invalid config at ${filePath}: "mcp_servers" must be an object`);
102
+ }
103
+ const servers = data;
104
+ for (const [name, server] of Object.entries(servers)) {
105
+ if (typeof server !== "object" || server === null) {
106
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name} must be an object`);
107
+ }
108
+ const s = server;
109
+ if (s.type !== "stdio" && s.type !== "http") {
110
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.type must be "stdio" or "http"`);
111
+ }
112
+ if (s.type === "stdio") {
113
+ if (typeof s.command !== "string") {
114
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name} (stdio) requires a "command" string`);
115
+ }
116
+ if (s.args !== undefined) {
117
+ if (!Array.isArray(s.args) || !s.args.every((a) => typeof a === "string")) {
118
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.args must be an array of strings`);
119
+ }
120
+ }
121
+ if (s.env !== undefined) {
122
+ if (typeof s.env !== "object" || s.env === null || Array.isArray(s.env)) {
123
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.env must be an object`);
124
+ }
125
+ for (const [k, v] of Object.entries(s.env)) {
126
+ if (typeof v !== "string") {
127
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.env.${k} must be a string`);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ if (s.type === "http") {
133
+ if (typeof s.url !== "string") {
134
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name} (http) requires a "url" string`);
135
+ }
136
+ if (s.headers !== undefined) {
137
+ if (typeof s.headers !== "object" || s.headers === null || Array.isArray(s.headers)) {
138
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.headers must be an object`);
139
+ }
140
+ for (const [k, v] of Object.entries(s.headers)) {
141
+ if (typeof v !== "string") {
142
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.headers.${k} must be a string`);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ if (s.axisCategory !== undefined && s.axisCategory !== "environment" && s.axisCategory !== "service") {
148
+ throw new Error(`Invalid config at ${filePath}: mcp_servers.${name}.axisCategory must be "environment" or "service"`);
149
+ }
150
+ }
151
+ }
152
+ function validateSkillsSources(data, filePath, field) {
153
+ if (!Array.isArray(data)) {
154
+ throw new Error(`Invalid config at ${filePath}: "${field}" must be an array`);
155
+ }
156
+ for (let i = 0; i < data.length; i++) {
157
+ const entry = data[i];
158
+ if (typeof entry === "string")
159
+ continue;
160
+ if (typeof entry !== "object" || entry === null) {
161
+ throw new Error(`Invalid config at ${filePath}: ${field}[${i}] must be a string or object`);
162
+ }
163
+ const obj = entry;
164
+ if (typeof obj.source !== "string") {
165
+ throw new Error(`Invalid config at ${filePath}: ${field}[${i}] must have a "source" string`);
166
+ }
167
+ if (obj.axisCategory !== undefined && obj.axisCategory !== "environment" && obj.axisCategory !== "service") {
168
+ throw new Error(`Invalid config at ${filePath}: ${field}[${i}].axisCategory must be "environment" or "service"`);
169
+ }
170
+ }
171
+ }
172
+ /**
173
+ * Resolve optional weights on rubric entries. Entries with explicit weights
174
+ * keep them; entries without a weight split the remaining budget equally.
175
+ * If no entries have weights, each gets `1 / n`.
176
+ */
177
+ export function resolveRubricWeights(rubric) {
178
+ if (rubric.length === 0)
179
+ return rubric;
180
+ const specified = rubric.filter((r) => r.weight !== undefined);
181
+ const unspecified = rubric.filter((r) => r.weight === undefined);
182
+ if (unspecified.length === 0)
183
+ return rubric;
184
+ const usedWeight = specified.reduce((sum, r) => sum + r.weight, 0);
185
+ const remaining = Math.max(0, 1.0 - usedWeight);
186
+ const share = unspecified.length > 0 ? remaining / unspecified.length : 0;
187
+ return rubric.map((r) => (r.weight !== undefined ? r : { ...r, weight: share }));
188
+ }
189
+ function validateLifecycleActions(data, filePath, field) {
190
+ if (!Array.isArray(data)) {
191
+ throw new Error(`Invalid scenario at ${filePath}: "${field}" must be an array`);
192
+ }
193
+ for (let i = 0; i < data.length; i++) {
194
+ const entry = data[i];
195
+ if (entry.action !== "run_script") {
196
+ throw new Error(`Invalid scenario at ${filePath}: ${field}[${i}].action must be "run_script"`);
197
+ }
198
+ if (typeof entry.command !== "string") {
199
+ throw new Error(`Invalid scenario at ${filePath}: ${field}[${i}] missing "command" string`);
200
+ }
201
+ }
202
+ }
203
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/config/validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ :root{--bg: #fafbf9;--bg-card: #ffffff;--text: #1a1a2e;--text-secondary: #4a5568;--text-muted: #637083;--border: #e5e7eb;--border-bright: #d1d5db;--light-gray: #f3f4f6;--accent: #016867;--accent-hover: #015554;--accent-bright: #2dd4bf;--accent-light: rgba(1, 104, 103, .07);--color-env: #059669;--color-svc: #2563eb;--color-agent: #6b7280;--warning: #d97706;--warning-light: rgba(217, 119, 6, .06);--danger: #dc2626;--danger-light: rgba(220, 38, 38, .06);--success: #059669;--success-light: rgba(5, 150, 105, .06);--sidebar-bg: #0f2b2b;--sidebar-text: #8faca8;--sidebar-text-muted: #7a9b97;--sidebar-hover: rgba(255, 255, 255, .06);--sidebar-active-bg: rgba(45, 212, 191, .12);--sidebar-active-text: #2dd4bf;--radius: 8px;--radius-lg: 12px;--shadow: 0 1px 3px rgba(0, 0, 0, .06);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .08);--glow: 0 0 40px rgba(1, 104, 103, .06);--font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;--font-mono: "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;--transition: .15s ease;--sidebar-width: 240px}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:15px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:var(--font);color:var(--text);background:var(--bg);background-image:radial-gradient(ellipse at top,rgba(1,104,103,.04) 0%,transparent 60%);background-attachment:fixed;line-height:1.6;min-height:100vh}:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}.site-header{position:sticky;top:0;z-index:50;background:#fafbf9d9;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border);padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between}.site-logo{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--text)}.site-logo-mark{font-size:2.0625rem;font-weight:800;letter-spacing:.12em;color:var(--accent)}.logo-ax{letter-spacing:-.06em}.logo-i{display:inline-block;font-style:italic;transform:skew(-20deg);margin-left:2px;margin-right:-2px}.site-header-links{display:flex;gap:16px;align-items:center}.site-header-links a{font-size:1rem;color:var(--text-secondary);text-decoration:none;font-weight:500;transition:color var(--transition)}.site-header-links a:hover{color:var(--accent)}.mobile-menu-btn{display:none;background:none;border:none;cursor:pointer;color:var(--text);font-size:1.25rem;padding:4px}.site-shell{display:flex;min-height:calc(100vh - 56px)}.sidebar{width:var(--sidebar-width);flex-shrink:0;background:radial-gradient(ellipse at 20% 15%,rgba(45,212,191,.18) 0%,transparent 50%),radial-gradient(ellipse at 80% 85%,rgba(30,80,160,.2) 0%,transparent 50%),radial-gradient(ellipse at 50% 50%,rgba(14,74,74,.4) 0%,transparent 70%),linear-gradient(180deg,#0d3d3d,#0a2424);padding:24px 0;position:sticky;top:56px;height:calc(100vh - 56px);overflow-y:auto}.sidebar-nav{display:flex;flex-direction:column;gap:2px;padding:0 12px}.sidebar-link{display:block;padding:8px 16px;font-size:.875rem;font-weight:500;color:var(--sidebar-text);text-decoration:none;border-radius:var(--radius);transition:background var(--transition),color var(--transition)}.sidebar-link:hover{background:var(--sidebar-hover);color:#c8dbd9}.sidebar-link.active{background:var(--sidebar-active-bg);color:var(--sidebar-active-text);font-weight:600}.sidebar-section-label{padding:20px 16px 6px;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--sidebar-text-muted)}.main-content{flex:1;min-width:0;padding:40px 48px 80px;max-width:860px}h1{font-size:2.25rem;font-weight:800;letter-spacing:-.03em;line-height:1.15;margin-bottom:12px;background:linear-gradient(135deg,var(--text) 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}h2{font-size:1.5rem;font-weight:700;letter-spacing:-.02em;color:var(--text);margin-top:48px;margin-bottom:16px;padding-bottom:12px;border-bottom:none;background-image:linear-gradient(90deg,rgba(1,104,103,.35),transparent 80%);background-size:100% 1px;background-position:bottom left;background-repeat:no-repeat}h3{font-size:1.125rem;font-weight:600;color:var(--text);margin-top:32px;margin-bottom:12px}h4{font-size:.9375rem;font-weight:600;color:var(--text-secondary);margin-top:24px;margin-bottom:8px}p{margin-bottom:16px;color:var(--text-secondary)}.lead{font-size:1.125rem;color:var(--text-secondary);line-height:1.7;margin-bottom:32px}a{color:var(--accent);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px;transition:color var(--transition)}a:hover{color:var(--accent-hover)}.sidebar-link,.site-logo,.site-header-links a{text-decoration:none}strong{font-weight:600;color:var(--text)}ul,ol{margin-bottom:16px;padding-left:24px}li{margin-bottom:6px;color:var(--text-secondary)}li>ul,li>ol{margin-top:6px;margin-bottom:0}code{font-family:var(--font-mono);font-size:.8125em;background:var(--accent-light);padding:2px 6px;border-radius:4px;color:var(--accent)}pre{background:#0f2b2b;color:#d4e8e6;padding:20px 24px;border-radius:var(--radius-lg);overflow-x:auto;margin-bottom:24px;font-size:.8125rem;line-height:1.7;border:1px solid rgba(255,255,255,.08);border-left:3px solid rgba(45,212,191,.5);box-shadow:var(--glow)}pre code{background:none;padding:0;border-radius:0;color:inherit;font-size:inherit}.card-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:32px}.card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px;box-shadow:var(--shadow);transition:border-color var(--transition),box-shadow var(--transition)}.card:hover{border-color:var(--border-bright);box-shadow:0 4px 24px #01686714,var(--glow)}.card-title{font-size:.9375rem;font-weight:600;margin-bottom:6px;color:var(--text)}.card-desc{font-size:.8125rem;color:var(--text-secondary);line-height:1.5}.card-accent{border-top:3px solid var(--accent)}.card-env{border-top:3px solid var(--color-env)}.card-svc{border-top:3px solid var(--color-svc)}.card-agent{border-top:3px solid var(--color-agent)}.card-weight{display:inline-block;font-size:.6875rem;font-weight:700;font-family:var(--font-mono);background:var(--accent-light);color:var(--accent);padding:2px 8px;border-radius:10px;margin-bottom:8px}table{width:100%;border-collapse:collapse;margin-bottom:24px;font-size:.875rem}thead th{text-align:left;padding:10px 12px;font-size:.75rem;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);font-weight:600;border-bottom:2px solid var(--border)}tbody td{padding:10px 12px;border-bottom:1px solid var(--light-gray);vertical-align:top;color:var(--text-secondary)}tbody tr:hover{background:var(--light-gray)}td code{font-size:.75rem}.callout{padding:16px 20px;border-radius:var(--radius);margin-bottom:24px;font-size:.875rem;line-height:1.6}.callout p:last-child{margin-bottom:0}.callout-title{font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;margin-bottom:4px}.callout-info{background:var(--accent-light);border-left:3px solid var(--accent)}.callout-info .callout-title{color:var(--accent)}.callout-warn{background:var(--warning-light);border-left:3px solid var(--warning)}.callout-warn .callout-title{color:var(--warning)}.callout-success{background:var(--success-light);border-left:3px solid var(--success)}.callout-success .callout-title{color:var(--success)}.pipeline{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:32px;padding:24px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow)}.pipeline-step{display:flex;align-items:center;gap:4px;padding:6px 14px;background:var(--light-gray);border:1px solid var(--border);border-radius:20px;font-size:.8125rem;font-weight:500;color:var(--text-secondary);white-space:nowrap}.pipeline-step.llm{background:var(--accent-light);border-color:var(--accent);color:var(--accent);font-weight:600}.pipeline-arrow{color:var(--text-muted);font-size:.875rem;flex-shrink:0}.hero{margin-bottom:48px}.hero-badge{display:inline-block;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--accent);background:var(--accent-light);border:1px solid rgba(1,104,103,.2);padding:4px 12px;border-radius:12px;margin-bottom:16px}.cli-block{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow);transition:border-color var(--transition),box-shadow var(--transition)}.cli-block:hover{border-color:var(--border-bright);box-shadow:var(--shadow),var(--glow)}.cli-command{font-family:var(--font-mono);font-size:.9375rem;font-weight:600;color:var(--accent);margin-bottom:8px}.cli-desc{font-size:.875rem;color:var(--text-secondary);margin-bottom:12px}.cli-flags{display:flex;flex-direction:column;gap:6px}.cli-flag{display:flex;gap:12px;font-size:.8125rem}.cli-flag-name{font-family:var(--font-mono);font-weight:500;color:var(--text);flex-shrink:0;min-width:200px}.cli-flag-desc{color:var(--text-secondary)}.site-footer{margin-top:64px;padding:24px 0;border-top:1px solid var(--border);font-size:.8125rem;color:var(--text-muted)}.dimension-wheel{display:grid;grid-template-columns:1fr 200px 1fr;grid-template-rows:1fr 1fr;gap:20px 28px;align-items:center;margin-bottom:32px;padding:24px 0}.dimension-chart{grid-column:2;grid-row:1 / 3;width:200px;height:200px}.dimension-chart circle{transition:stroke-width .2s ease,opacity .2s ease;cursor:pointer}.dimension-chart text{transition:opacity .2s ease}.dimension-label{display:block;padding:10px 14px;border-radius:var(--radius);text-decoration:none;transition:background var(--transition),box-shadow var(--transition)}.dimension-label:hover{background:var(--light-gray);box-shadow:var(--shadow)}.dimension-pct{display:block;font-size:.75rem;font-weight:700;font-family:var(--font-mono);margin-bottom:2px}.dimension-name{display:block;font-size:.9375rem;font-weight:600;color:var(--text);margin-bottom:2px}.dimension-desc{display:block;font-size:.8125rem;color:var(--text-secondary);line-height:1.4}.dimension-label-goal{grid-column:3;grid-row:1;justify-self:start}.dimension-label-env{grid-column:3;grid-row:2;justify-self:start}.dimension-label-svc{grid-column:1;grid-row:2;justify-self:end;text-align:right}.dimension-label-agent{grid-column:1;grid-row:1;justify-self:end;text-align:right}@media(max-width:768px){.mobile-menu-btn{display:block}.sidebar{position:fixed;top:56px;left:0;bottom:0;z-index:40;transform:translate(-100%);transition:transform .2s ease;box-shadow:var(--shadow-lg)}.sidebar.open{transform:translate(0)}.sidebar-backdrop{display:none;position:fixed;inset:56px 0 0;z-index:39;background:#0f2b2b80}.sidebar-backdrop.open{display:block}.main-content{padding:24px 16px 64px}.card-grid{grid-template-columns:1fr}.dimension-wheel{grid-template-columns:1fr;grid-template-rows:auto;justify-items:center}.dimension-chart{grid-column:1;grid-row:1}.dimension-label-goal{grid-column:1;grid-row:2;justify-self:stretch;text-align:left}.dimension-label-env{grid-column:1;grid-row:3;justify-self:stretch;text-align:left}.dimension-label-svc{grid-column:1;grid-row:4;justify-self:stretch;text-align:left}.dimension-label-agent{grid-column:1;grid-row:5;justify-self:stretch;text-align:left}.pipeline{flex-direction:column;align-items:stretch}.pipeline-arrow{text-align:center;transform:rotate(90deg)}.cli-flag{flex-direction:column;gap:2px}.cli-flag-name{min-width:0}}
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>CLI - AXIS Docs</title><meta name="description" content="Documentation for AXIS, the Agent eXperience Index Score synthetic testing framework for AI agents."><link rel="stylesheet" href="/_astro/cli.DDWZtG0-.css"></head> <body> <header class="site-header"> <a href="/" class="site-logo" aria-label="AXIS home"> <span class="site-logo-mark"><span class="logo-ax">AX</span><span class="logo-i">I</span>S</span> </a> <div class="site-header-links"> <a href="https://github.com/netlify/axis">GitHub</a> <a href="https://www.npmjs.com/package/@netlify/axis">npm</a> </div> <button class="mobile-menu-btn" id="menu-btn" aria-label="Toggle navigation">&#9776;</button> </header> <div class="site-shell"> <div class="sidebar-backdrop" id="sidebar-backdrop"></div> <aside class="sidebar" id="sidebar"> <nav class="sidebar-nav"> <div class="sidebar-section-label">Getting Started</div> <a href="/" class="sidebar-link">What is AXIS</a> <a href="/quickstart" class="sidebar-link">Quick Start</a> <div class="sidebar-section-label">How It Works</div> <a href="/scoring" class="sidebar-link">Scoring Framework</a> <a href="/running" class="sidebar-link">Running Tests</a> <div class="sidebar-section-label">Reference</div> <a href="/cli" class="sidebar-link active">CLI</a> <a href="/configuration" class="sidebar-link">Configuration</a> </nav> </aside> <main class="main-content"> <h1>CLI Reference</h1> <p class="lead">
2
+ Complete reference for the <code>axis</code> command-line interface. All commands can be run
3
+ directly or via <code>npx @netlify/axis</code>.
4
+ </p> <h2><code>axis run</code></h2> <div class="cli-block"> <div class="cli-command">axis run [options]</div> <div class="cli-desc">Execute scenarios against configured agents.</div> <div class="cli-flags"> <div class="cli-flag"> <span class="cli-flag-name"><code>-c, --config &lt;path&gt;</code></span> <span class="cli-flag-desc">Config file (default: <code>axis.config.json</code>).</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>-s, --scenario &lt;key&gt;</code></span> <span class="cli-flag-desc">Run a specific scenario by key.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>-a, --agent &lt;name&gt;</code></span> <span class="cli-flag-desc">Run with a specific agent only.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--json</code></span> <span class="cli-flag-desc">JSON output to stdout (no live terminal display).</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>-v, --verbose</code></span> <span class="cli-flag-desc">Detailed per-step logging.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>-o, --output-dir &lt;dir&gt;</code></span> <span class="cli-flag-desc">Also write report files to this directory.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--concurrency &lt;n&gt;</code></span> <span class="cli-flag-desc">Max parallel jobs.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--debug</code></span> <span class="cli-flag-desc">Capture raw agent stdout for debugging.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--no-score</code></span> <span class="cli-flag-desc">Skip scoring (raw results only).</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--refresh-skills</code></span> <span class="cli-flag-desc">Force re-clone remote skills.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--compare-baseline [name]</code></span> <span class="cli-flag-desc">Diff against a baseline after scoring.</span> </div> </div> </div> <h2><code>axis reports</code></h2> <div class="cli-block"> <div class="cli-command">axis reports [reportId] [scenarioKey] [options]</div> <div class="cli-desc">View past AXIS reports.</div> <div class="cli-flags"> <div class="cli-flag"> <span class="cli-flag-name"><code>[reportId]</code></span> <span class="cli-flag-desc">Report ID or <code>latest</code>. Omit to list all reports.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>[scenarioKey]</code></span> <span class="cli-flag-desc">Drill into a specific scenario detail.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>-a, --agent &lt;name...&gt;</code></span> <span class="cli-flag-desc">Filter by agent(s), repeatable.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--json</code></span> <span class="cli-flag-desc">Output as JSON.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--html</code></span> <span class="cli-flag-desc">Open report in browser.</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>-n, --limit &lt;count&gt;</code></span> <span class="cli-flag-desc">Max reports to list (default: 10).</span> </div> </div> </div> <h2><code>axis baseline</code></h2> <div class="cli-block"> <div class="cli-command">axis baseline set [name]</div> <div class="cli-desc">Create or update a baseline snapshot from a report.</div> <div class="cli-flags"> <div class="cli-flag"> <span class="cli-flag-name"><code>--from &lt;reportId&gt;</code></span> <span class="cli-flag-desc">Use a specific report (default: latest).</span> </div> </div> </div> <div class="cli-block"> <div class="cli-command">axis baseline list</div> <div class="cli-desc">List all saved baselines.</div> </div> <div class="cli-block"> <div class="cli-command">axis baseline show [name]</div> <div class="cli-desc">Display baseline contents.</div> <div class="cli-flags"> <div class="cli-flag"> <span class="cli-flag-name"><code>--json</code></span> <span class="cli-flag-desc">Output as JSON.</span> </div> </div> </div> <div class="cli-block"> <div class="cli-command">axis baseline diff [name]</div> <div class="cli-desc">Compare a report against a baseline. Exits with code 1 if regressions are detected.</div> <div class="cli-flags"> <div class="cli-flag"> <span class="cli-flag-name"><code>--report &lt;reportId&gt;</code></span> <span class="cli-flag-desc">Specific report to compare (default: latest).</span> </div> <div class="cli-flag"> <span class="cli-flag-name"><code>--json</code></span> <span class="cli-flag-desc">Output as JSON.</span> </div> </div> </div> <div class="cli-block"> <div class="cli-command">axis baseline delete [name]</div> <div class="cli-desc">Delete a saved baseline.</div> </div> <footer class="site-footer">
5
+ AXIS is maintained by Netlify.
6
+ </footer> </main> </div> <script>
7
+ const btn = document.getElementById("menu-btn");
8
+ const sidebar = document.getElementById("sidebar");
9
+ const backdrop = document.getElementById("sidebar-backdrop");
10
+
11
+ function toggle() {
12
+ sidebar.classList.toggle("open");
13
+ backdrop.classList.toggle("open");
14
+ }
15
+
16
+ btn.addEventListener("click", toggle);
17
+ backdrop.addEventListener("click", toggle);
18
+ </script> </body> </html>
@@ -0,0 +1,121 @@
1
+ <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Configuration - AXIS Docs</title><meta name="description" content="Documentation for AXIS, the Agent eXperience Index Score synthetic testing framework for AI agents."><link rel="stylesheet" href="/_astro/cli.DDWZtG0-.css"></head> <body> <header class="site-header"> <a href="/" class="site-logo" aria-label="AXIS home"> <span class="site-logo-mark"><span class="logo-ax">AX</span><span class="logo-i">I</span>S</span> </a> <div class="site-header-links"> <a href="https://github.com/netlify/axis">GitHub</a> <a href="https://www.npmjs.com/package/@netlify/axis">npm</a> </div> <button class="mobile-menu-btn" id="menu-btn" aria-label="Toggle navigation">&#9776;</button> </header> <div class="site-shell"> <div class="sidebar-backdrop" id="sidebar-backdrop"></div> <aside class="sidebar" id="sidebar"> <nav class="sidebar-nav"> <div class="sidebar-section-label">Getting Started</div> <a href="/" class="sidebar-link">What is AXIS</a> <a href="/quickstart" class="sidebar-link">Quick Start</a> <div class="sidebar-section-label">How It Works</div> <a href="/scoring" class="sidebar-link">Scoring Framework</a> <a href="/running" class="sidebar-link">Running Tests</a> <div class="sidebar-section-label">Reference</div> <a href="/cli" class="sidebar-link">CLI</a> <a href="/configuration" class="sidebar-link active">Configuration</a> </nav> </aside> <main class="main-content"> <h1>Configuration Reference</h1> <p class="lead">
2
+ Complete reference for <code>axis.config.json</code> and scenario files.
3
+ </p> <h2>axis.config.json</h2> <p>
4
+ AXIS is configured via an <code>axis.config.json</code> file in your project root.
5
+ </p> <pre><code>{
6
+ &quot;scenarios&quot;: &quot;./scenarios&quot;,
7
+ &quot;agents&quot;: [
8
+ &quot;claude-code&quot;,
9
+ {
10
+ &quot;adapter&quot;: &quot;gemini&quot;,
11
+ &quot;model&quot;: &quot;gemini-2.5-pro&quot;,
12
+ &quot;scenarios&quot;: [&quot;cms/*&quot;],
13
+ &quot;flags&quot;: { &quot;yolo&quot;: true }
14
+ }
15
+ ],
16
+ &quot;defaults&quot;: {
17
+ &quot;concurrency&quot;: 4,
18
+ &quot;scoring_weights&quot;: {
19
+ &quot;goal_achievement&quot;: 0.4,
20
+ &quot;environment&quot;: 0.2,
21
+ &quot;service&quot;: 0.2,
22
+ &quot;agent&quot;: 0.2
23
+ }
24
+ },
25
+ &quot;env&quot;: [&quot;ANTHROPIC_API_KEY&quot;, &quot;GEMINI_API_KEY&quot;],
26
+ &quot;mcp_servers&quot;: {
27
+ &quot;filesystem&quot;: {
28
+ &quot;type&quot;: &quot;stdio&quot;,
29
+ &quot;command&quot;: &quot;npx&quot;,
30
+ &quot;args&quot;: [&quot;-y&quot;, &quot;@modelcontextprotocol/server-filesystem&quot;, &quot;/tmp&quot;]
31
+ }
32
+ },
33
+ &quot;skills&quot;: [&quot;./skills/deploy&quot;],
34
+ &quot;adapters&quot;: {
35
+ &quot;my-agent&quot;: &quot;./adapters/my-agent.ts&quot;
36
+ }
37
+ }</code></pre> <h3>Top-Level Fields</h3> <table> <thead> <tr> <th>Field</th> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>scenarios</code></td> <td><code>string</code></td> <td>Path to the scenarios directory (required).</td> </tr> <tr> <td><code>agents</code></td> <td><code>(string | AgentConfig)[]</code></td> <td>Adapter names or full agent configurations (required).</td> </tr> <tr> <td><code>defaults</code></td> <td><code>object</code></td> <td>Default concurrency and scoring weights.</td> </tr> <tr> <td><code>env</code></td> <td><code>string[]</code></td> <td>Environment variables to pass through to agent processes.</td> </tr> <tr> <td><code>mcp_servers</code></td> <td><code>object</code></td> <td>MCP servers available to all agents.</td> </tr> <tr> <td><code>skills</code></td> <td><code>string[]</code></td> <td>Skills available to all agents (local paths, GitHub shorthand, or URLs).</td> </tr> <tr> <td><code>adapters</code></td> <td><code>object</code></td> <td>Custom adapter module paths, keyed by adapter name.</td> </tr> </tbody> </table> <h3>Agent Configuration</h3> <p>
38
+ Agents can be specified as a simple string (adapter name) or a full configuration object.
39
+ </p> <table> <thead> <tr> <th>Field</th> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>adapter</code></td> <td><code>string</code></td> <td>Adapter type (required): <code>claude-code</code>, <code>codex</code>, <code>gemini</code>, etc.</td> </tr> <tr> <td><code>model</code></td> <td><code>string</code></td> <td>Model override passed to the agent CLI.</td> </tr> <tr> <td><code>scenarios</code></td> <td><code>string[]</code></td> <td>Subset of scenarios to run (supports glob patterns like <code>cms/*</code>).</td> </tr> <tr> <td><code>skills</code></td> <td><code>string[]</code></td> <td>Agent-specific skills (merged with top-level skills).</td> </tr> <tr> <td><code>flags</code></td> <td><code>object</code></td> <td>CLI flags passed to the agent (for example, <code>{&quot;full-auto&quot;: true}</code>).</td> </tr> <tr> <td><code>command</code></td> <td><code>string</code></td> <td>Custom CLI command (for custom adapters).</td> </tr> </tbody> </table><code> <h3>Scoring Weights</h3> <p>
40
+ Override the default dimension weights under <code>defaults.scoring_weights</code>. Values
41
+ must sum to 1.0.
42
+ </p> <pre><code>{
43
+ &quot;defaults&quot;: {
44
+ &quot;scoring_weights&quot;: {
45
+ &quot;goal_achievement&quot;: 0.4,
46
+ &quot;environment&quot;: 0.2,
47
+ &quot;service&quot;: 0.2,
48
+ &quot;agent&quot;: 0.2
49
+ }
50
+ }
51
+ }</code></pre> <h3>MCP Servers</h3> <p>
52
+ Configure Model Context Protocol servers that are automatically wired into each agent
53
+ environment. AXIS supports both stdio (local process) and HTTP (remote endpoint) servers.
54
+ </p> <pre><code>{
55
+ &quot;mcp_servers&quot;: {
56
+ &quot;filesystem&quot;: {
57
+ &quot;type&quot;: &quot;stdio&quot;,
58
+ &quot;command&quot;: &quot;npx&quot;,
59
+ &quot;args&quot;: [&quot;-y&quot;, &quot;@modelcontextprotocol/server-filesystem&quot;, &quot;/tmp&quot;],
60
+ &quot;env&quot;: { &quot;LOG_LEVEL&quot;: &quot;info&quot; }
61
+ },
62
+ &quot;remote-api&quot;: {
63
+ &quot;type&quot;: &quot;http&quot;,
64
+ &quot;url&quot;: &quot;https://mcp.example.com/tools&quot;,
65
+ &quot;headers&quot;: { &quot;Authorization&quot;: &quot;Bearer ${TOKEN}&quot; }
66
+ }
67
+ }
68
+ }</code></pre> <p>
69
+ Each adapter writes MCP configuration in its native format before spawning the agent.
70
+ </p> <ul> <li><strong>Claude Code:</strong> <code>.mcp.json</code> in workspace root.</li> <li><strong>Codex:</strong> <code>config.toml</code> in <code>CODEX_HOME</code>.</li> <li><strong>Gemini:</strong> <code>settings.json</code> in <code>GEMINI_CLI_HOME</code>.</li> </ul> <h3>Skills</h3> <p>
71
+ Skills extend agent capabilities with reusable instruction sets. Specify them at the top level
72
+ (shared), per agent, or per scenario.
73
+ </p> <pre><code>{
74
+ &quot;skills&quot;: [
75
+ &quot;./skills/deploy&quot;,
76
+ &quot;netlify/axis-skill-deploy&quot;,
77
+ &quot;https://github.com/owner/repo&quot;
78
+ ]
79
+ }</code></pre> <ul> <li><strong>Local paths:</strong> Relative to the config file.</li> <li><strong>GitHub shorthand:</strong> <code>owner/repo</code> format.</li> <li><strong>Full URLs:</strong> GitHub repository URLs.</li> </ul> <p>
80
+ Remote skills are cached in <code>.axis/skills-cache/</code>. Use <code>--refresh-skills</code>
81
+ to force re-clone.
82
+ </p> <h3>Environment Variables</h3> <p>
83
+ The <code>env</code> field lists additional environment variables to pass through to agent
84
+ processes. The following are always passed through by default:
85
+ </p> <ul> <li><code>ANTHROPIC_API_KEY</code>, <code>CODEX_API_KEY</code>, <code>GEMINI_API_KEY</code></li> <li>System essentials: <code>PATH</code>, <code>USER</code>, <code>SHELL</code>, <code>LANG</code>, <code>TERM</code>, <code>TMPDIR</code></li> </ul> <h2>Scenarios</h2> <p>
86
+ Scenarios are JSON files in the configured scenarios directory. The filename (without <code>.json</code>)
87
+ becomes the scenario key. Nested directories create namespaced keys.
88
+ </p> <ul> <li><code>scenarios/hello-world.json</code> maps to key <code>hello-world</code>.</li> <li><code>scenarios/cms/create-post.json</code> maps to key <code>cms/create-post</code>.</li> </ul> <h3>Scenario Schema</h3> <pre><code>{
89
+ &quot;name&quot;: &quot;Debug and fix a broken script&quot;,
90
+ &quot;prompt&quot;: &quot;There is a JavaScript file at /tmp/app/add.js that has a bug. Find it, fix it, and verify.&quot;,
91
+ &quot;rubric&quot;: [
92
+ { &quot;check&quot;: &quot;Agent identified the bug&quot;, &quot;weight&quot;: 0.3 },
93
+ { &quot;check&quot;: &quot;Agent fixed the bug&quot;, &quot;weight&quot;: 0.4 },
94
+ { &quot;check&quot;: &quot;Agent verified the fix&quot;, &quot;weight&quot;: 0.3 }
95
+ ],
96
+ &quot;setup&quot;: [
97
+ { &quot;action&quot;: &quot;run_script&quot;, &quot;command&quot;: &quot;mkdir -p /tmp/app&quot; },
98
+ { &quot;action&quot;: &quot;run_script&quot;, &quot;command&quot;: &quot;echo &#39;function add(a,b) { return a-b; }&#39; &gt; /tmp/app/add.js&quot; }
99
+ ],
100
+ &quot;teardown&quot;: [
101
+ { &quot;action&quot;: &quot;run_script&quot;, &quot;command&quot;: &quot;rm -rf /tmp/app&quot; }
102
+ ],
103
+ &quot;agents&quot;: [&quot;claude-code&quot;]
104
+ }</code></pre> <table> <thead> <tr> <th>Field</th> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>name</code></td> <td><code>string</code></td> <td>Human-readable scenario title (required).</td> </tr> <tr> <td><code>prompt</code></td> <td><code>string</code></td> <td>Task description sent to the agent (required).</td> </tr> <tr> <td><code>rubric</code></td> <td><code>string | object[]</code></td> <td>Success criteria: a string or array of checks with optional weights (required).</td> </tr> <tr> <td><code>setup</code></td> <td><code>object[]</code></td> <td>Lifecycle actions run before the agent.</td> </tr> <tr> <td><code>teardown</code></td> <td><code>object[]</code></td> <td>Lifecycle actions run after scoring.</td> </tr> <tr> <td><code>agents</code></td> <td><code>string[]</code></td> <td>Override which agents run this scenario.</td> </tr> <tr> <td><code>skills</code></td> <td><code>string[]</code></td> <td>Scenario-specific skills.</td> </tr> </tbody> </table> <div class="callout callout-info"> <div class="callout-title">Setup and Teardown</div> <p>
105
+ Lifecycle actions run sequentially with a 30-second timeout per action. Setup failures abort
106
+ the job. Teardown failures are logged but do not block subsequent jobs.
107
+ </p> </div> </code> <footer class="site-footer">
108
+ AXIS is maintained by Netlify.
109
+ </footer> </main> </div> <script>
110
+ const btn = document.getElementById("menu-btn");
111
+ const sidebar = document.getElementById("sidebar");
112
+ const backdrop = document.getElementById("sidebar-backdrop");
113
+
114
+ function toggle() {
115
+ sidebar.classList.toggle("open");
116
+ backdrop.classList.toggle("open");
117
+ }
118
+
119
+ btn.addEventListener("click", toggle);
120
+ backdrop.addEventListener("click", toggle);
121
+ </script> </body> </html>
@@ -0,0 +1 @@
1
+ export default new Map();
@@ -0,0 +1 @@
1
+ export default new Map();
@@ -0,0 +1,9 @@
1
+ [
2
+ ["Map", 1, 2],
3
+ "meta::meta",
4
+ ["Map", 3, 4, 5, 6],
5
+ "astro-version",
6
+ "5.18.1",
7
+ "astro-config-digest",
8
+ "{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"
9
+ ]
@@ -0,0 +1,69 @@
1
+ <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>What is AXIS - AXIS Docs</title><meta name="description" content="Documentation for AXIS, the Agent eXperience Index Score synthetic testing framework for AI agents."><link rel="stylesheet" href="/_astro/cli.DDWZtG0-.css"></head> <body> <header class="site-header"> <a href="/" class="site-logo" aria-label="AXIS home"> <span class="site-logo-mark"><span class="logo-ax">AX</span><span class="logo-i">I</span>S</span> </a> <div class="site-header-links"> <a href="https://github.com/netlify/axis">GitHub</a> <a href="https://www.npmjs.com/package/@netlify/axis">npm</a> </div> <button class="mobile-menu-btn" id="menu-btn" aria-label="Toggle navigation">&#9776;</button> </header> <div class="site-shell"> <div class="sidebar-backdrop" id="sidebar-backdrop"></div> <aside class="sidebar" id="sidebar"> <nav class="sidebar-nav"> <div class="sidebar-section-label">Getting Started</div> <a href="/" class="sidebar-link active">What is AXIS</a> <a href="/quickstart" class="sidebar-link">Quick Start</a> <div class="sidebar-section-label">How It Works</div> <a href="/scoring" class="sidebar-link">Scoring Framework</a> <a href="/running" class="sidebar-link">Running Tests</a> <div class="sidebar-section-label">Reference</div> <a href="/cli" class="sidebar-link">CLI</a> <a href="/configuration" class="sidebar-link">Configuration</a> </nav> </aside> <main class="main-content"> <div class="hero"> <h1>AXIS - Agent eXperience Index Score</h1> <p class="lead">
2
+ AXIS is both an open scoring framework for measuring <strong>agent experience (AX)</strong>
3
+ and a CLI tool that implements it. Think
4
+ <a href="https://developer.chrome.com/docs/lighthouse">Lighthouse</a>, but instead of scoring
5
+ user experience, AXIS scores <strong>agent experience</strong>.
6
+ </p> </div> <h2>Why AX Matters</h2> <p>
7
+ The web has Lighthouse. APIs have contract testing. Performance has k6. But there is no
8
+ standardized way to answer: <em>"How well does my system work when an AI agent tries to use it?"</em> </p> <p>
9
+ As agents become a primary interface for websites, APIs, and developer platforms, the systems
10
+ they interact with need to be measured and optimized for that interaction. Just as we optimize
11
+ for page load time or accessibility, AX is the agent-era equivalent of UX.
12
+ </p> <h2>Our Approach</h2> <p>
13
+ AXIS is built on two core beliefs about how agent experience should be measured.
14
+ </p> <p> <strong>Measure what matters, where you have leverage.</strong> Agent experience is not a single
15
+ number. It breaks down into distinct dimensions: how well the agent completes the task, how it
16
+ uses the environment, how it interacts with services, and how it reasons through problems.
17
+ Purpose-built tooling that scores each dimension independently gives you a clear picture of
18
+ where to focus. If your API responses are slowing agents down, you see it in the Service
19
+ dimension. If your project structure confuses agents, Environment tells you. Generic pass/fail
20
+ testing does not surface these signals.
21
+ </p> <p> <strong>Test against real agent behavior, not theoretical support.</strong> It is not enough to
22
+ validate that your system publishes the right config files or follows a protocol spec. What
23
+ matters is whether agents actually discover and use what you provide. AXIS measures this by
24
+ running real agents against real scenarios and observing what happens: which tools they call,
25
+ which files they read, which APIs they hit. This tells you what agents <em>do</em>, not what
26
+ they <em>could</em> do in theory.
27
+ </p> <h2>The Scoring Framework</h2> <p>
28
+ At its core, AXIS defines a standard way to measure agent experience across four independent
29
+ dimensions. Any tool, platform, or CI system can implement this framework to produce
30
+ comparable AX measurements.
31
+ </p> <div class="card-grid"> <div class="card card-accent"> <span class="card-weight">40%</span> <div class="card-title">Goal Achievement</div> <div class="card-desc">
32
+ Did the agent complete the task? Evaluated against rubric criteria you define for each
33
+ scenario.
34
+ </div> </div> <div class="card card-env"> <span class="card-weight">20%</span> <div class="card-title">Environment</div> <div class="card-desc">
35
+ How well did the agent use the OS, filesystem, and dev tools? Measures quality of shell
36
+ commands, file operations, git usage, and build tools.
37
+ </div> </div> <div class="card card-svc"> <span class="card-weight">20%</span> <div class="card-title">Service</div> <div class="card-desc">
38
+ How effectively did the agent use external services? Evaluates API calls, MCP tools,
39
+ network requests, and third-party integrations.
40
+ </div> </div> <div class="card card-agent"> <span class="card-weight">20%</span> <div class="card-title">Agent</div> <div class="card-desc">
41
+ How well did the agent reason and self-organize? Covers planning, task management, tool
42
+ discovery, and metacognitive behavior.
43
+ </div> </div> </div> <p>
44
+ These four dimensions combine into a single 0 to 100 <strong>AXIS Result</strong>. The
45
+ framework specifies what signals feed each dimension, how interactions are categorized, and
46
+ how the composite score is calculated. See <a href="/scoring">Scoring Framework</a> for full
47
+ details on the signals and scoring logic.
48
+ </p> <h2>The CLI Tool</h2> <p>
49
+ The <code>@netlify/axis</code> package is the reference implementation of the scoring framework.
50
+ It provides a CLI that runs agent scenarios, captures transcripts, scores the results, and
51
+ produces reports.
52
+ </p> <ul> <li><strong>Define scenarios</strong> as JSON files with a prompt, rubric, and optional setup/teardown steps.</li> <li><strong>Run them</strong> against any supported agent (or your own custom adapter) in isolated workspaces.</li> <li><strong>Score automatically</strong> using a multi-pass LLM evaluation pipeline that produces per-dimension and composite scores.</li> <li><strong>Track over time</strong> with persistent reports, baseline snapshots, and regression detection for CI gating.</li> </ul> <h3>Built-in Adapters</h3> <p>
53
+ The CLI ships with adapters for popular AI coding agents. Each adapter handles process spawning,
54
+ transcript capture, and output normalization for its agent.
55
+ </p> <table> <thead> <tr> <th>Adapter</th> <th>Agent</th> <th>Required Env</th> </tr> </thead> <tbody> <tr> <td><code>claude-code</code></td> <td>Claude Code</td> <td><code>ANTHROPIC_API_KEY</code></td> </tr> <tr> <td><code>codex</code></td> <td>OpenAI Codex</td> <td><code>CODEX_API_KEY</code></td> </tr> <tr> <td><code>gemini</code></td> <td>Google Gemini CLI</td> <td><code>GEMINI_API_KEY</code></td> </tr> <tr> <td><code>goose</code></td> <td>Goose</td> <td>None</td> </tr> <tr> <td><code>claude-sdk</code></td> <td>Claude SDK</td> <td><code>ANTHROPIC_API_KEY</code></td> </tr> <tr> <td><code>gemini-acp</code></td> <td>Gemini (ACP)</td> <td><code>GEMINI_API_KEY</code></td> </tr> <tr> <td>Custom</td> <td>Any agent via <code>createAgentAdapter()</code></td> <td>User-defined</td> </tr> </tbody> </table> <footer class="site-footer">
56
+ AXIS is maintained by Netlify.
57
+ </footer> </main> </div> <script>
58
+ const btn = document.getElementById("menu-btn");
59
+ const sidebar = document.getElementById("sidebar");
60
+ const backdrop = document.getElementById("sidebar-backdrop");
61
+
62
+ function toggle() {
63
+ sidebar.classList.toggle("open");
64
+ backdrop.classList.toggle("open");
65
+ }
66
+
67
+ btn.addEventListener("click", toggle);
68
+ backdrop.addEventListener("click", toggle);
69
+ </script> </body> </html>
@@ -0,0 +1,59 @@
1
+ <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Quick Start - AXIS Docs</title><meta name="description" content="Documentation for AXIS, the Agent eXperience Index Score synthetic testing framework for AI agents."><link rel="stylesheet" href="/_astro/cli.DDWZtG0-.css"></head> <body> <header class="site-header"> <a href="/" class="site-logo" aria-label="AXIS home"> <span class="site-logo-mark"><span class="logo-ax">AX</span><span class="logo-i">I</span>S</span> </a> <div class="site-header-links"> <a href="https://github.com/netlify/axis">GitHub</a> <a href="https://www.npmjs.com/package/@netlify/axis">npm</a> </div> <button class="mobile-menu-btn" id="menu-btn" aria-label="Toggle navigation">&#9776;</button> </header> <div class="site-shell"> <div class="sidebar-backdrop" id="sidebar-backdrop"></div> <aside class="sidebar" id="sidebar"> <nav class="sidebar-nav"> <div class="sidebar-section-label">Getting Started</div> <a href="/" class="sidebar-link">What is AXIS</a> <a href="/quickstart" class="sidebar-link active">Quick Start</a> <div class="sidebar-section-label">How It Works</div> <a href="/scoring" class="sidebar-link">Scoring Framework</a> <a href="/running" class="sidebar-link">Running Tests</a> <div class="sidebar-section-label">Reference</div> <a href="/cli" class="sidebar-link">CLI</a> <a href="/configuration" class="sidebar-link">Configuration</a> </nav> </aside> <main class="main-content"> <h1>Quick Start</h1> <p class="lead">
2
+ Get AXIS running in your project in a few minutes. This guide walks through creating a config,
3
+ writing your first scenario, and viewing the results.
4
+ </p> <h2>Prerequisites</h2> <ul> <li>Node.js 18 or later.</li> <li>An API key for at least one supported agent (for example, <code>ANTHROPIC_API_KEY</code> for Claude Code).</li> </ul> <h2>1. Create a Config File</h2> <p>
5
+ Add an <code>axis.config.json</code> to your project root. At minimum, specify where your
6
+ scenarios live and which agents to run.
7
+ </p> <pre><code>{
8
+ &quot;scenarios&quot;: &quot;./scenarios&quot;,
9
+ &quot;agents&quot;: [&quot;claude-code&quot;]
10
+ }</code></pre> <h2>2. Write a Scenario</h2> <p>
11
+ Create a <code>scenarios/</code> directory and add your first scenario as a JSON file. Each
12
+ scenario needs a name, a prompt (the task for the agent), and a rubric (your success criteria).
13
+ </p> <pre><code>{
14
+ &quot;name&quot;: &quot;Create a greeting file&quot;,
15
+ &quot;prompt&quot;: &quot;Create a file called hello.txt with the content &#39;Hello from AXIS&#39;.&quot;,
16
+ &quot;rubric&quot;: [
17
+ { &quot;check&quot;: &quot;File hello.txt exists&quot;, &quot;weight&quot;: 0.5 },
18
+ { &quot;check&quot;: &quot;File contains &#39;Hello from AXIS&#39;&quot;, &quot;weight&quot;: 0.5 }
19
+ ]
20
+ }</code></pre> <p>
21
+ Save this as <code>scenarios/hello-world.json</code>. The filename (without <code>.json</code>)
22
+ becomes the scenario key used in reports and CLI commands.
23
+ </p> <h2>3. Run It</h2> <pre><code>npx @netlify/axis run</code></pre> <p>
24
+ AXIS spawns the agent in an isolated workspace, captures the full interaction transcript, scores
25
+ the result against your rubric, and displays a report in your terminal.
26
+ </p> <h2>4. View the Report</h2> <p>
27
+ Every run saves a report to <code>.axis/reports/</code>. You can view it again at any time.
28
+ </p> <pre><code># View the latest report summary
29
+ npx @netlify/axis reports latest
30
+
31
+ # Open the HTML report in your browser
32
+ npx @netlify/axis reports latest --html
33
+
34
+ # Get JSON output for scripting
35
+ npx @netlify/axis reports latest --json</code></pre> <h2>5. Set a Baseline</h2> <p>
36
+ Once you have a run you are happy with, save it as a baseline. Future runs can diff against it
37
+ to detect regressions.
38
+ </p> <pre><code># Save the latest report as a baseline
39
+ npx @netlify/axis baseline set
40
+
41
+ # Compare future runs automatically
42
+ npx @netlify/axis run --compare-baseline</code></pre> <p>
43
+ The diff command exits with code 1 if any regressions are detected, making it suitable for CI
44
+ gating.
45
+ </p> <h2>Next Steps</h2> <ul> <li><a href="/scoring">Scoring Framework</a> explains how the four dimensions are calculated and what signals drive each score.</li> <li><a href="/running">Running Tests</a> covers the full config reference, CLI commands, custom adapters, MCP servers, and CI integration.</li> </ul> <footer class="site-footer">
46
+ AXIS is maintained by Netlify.
47
+ </footer> </main> </div> <script>
48
+ const btn = document.getElementById("menu-btn");
49
+ const sidebar = document.getElementById("sidebar");
50
+ const backdrop = document.getElementById("sidebar-backdrop");
51
+
52
+ function toggle() {
53
+ sidebar.classList.toggle("open");
54
+ backdrop.classList.toggle("open");
55
+ }
56
+
57
+ btn.addEventListener("click", toggle);
58
+ backdrop.addEventListener("click", toggle);
59
+ </script> </body> </html>