@kirrosh/zond 0.21.0 → 0.23.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 (256) hide show
  1. package/CHANGELOG.md +758 -3
  2. package/README.md +78 -15
  3. package/package.json +17 -10
  4. package/src/cli/argv.ts +122 -0
  5. package/src/cli/commands/add-api.ts +134 -0
  6. package/src/cli/commands/api/annotate/idempotency.ts +59 -0
  7. package/src/cli/commands/api/annotate/index.ts +525 -0
  8. package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
  9. package/src/cli/commands/api/annotate/overlay.ts +206 -0
  10. package/src/cli/commands/api/annotate/pagination.ts +60 -0
  11. package/src/cli/commands/api/annotate/prompts.ts +183 -0
  12. package/src/cli/commands/api/annotate/readback.ts +58 -0
  13. package/src/cli/commands/api/annotate/resources.ts +91 -0
  14. package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
  15. package/src/cli/commands/audit.ts +480 -0
  16. package/src/cli/commands/bootstrap.ts +710 -0
  17. package/src/cli/commands/catalog.ts +35 -0
  18. package/src/cli/commands/check.ts +348 -0
  19. package/src/cli/commands/checks.ts +756 -0
  20. package/src/cli/commands/ci-init.ts +55 -6
  21. package/src/cli/commands/clean.ts +212 -0
  22. package/src/cli/commands/cleanup.ts +262 -0
  23. package/src/cli/commands/completions.ts +192 -0
  24. package/src/cli/commands/coverage.ts +605 -132
  25. package/src/cli/commands/db.ts +180 -8
  26. package/src/cli/commands/describe.ts +37 -2
  27. package/src/cli/commands/discover.ts +1236 -0
  28. package/src/cli/commands/doctor.ts +607 -0
  29. package/src/cli/commands/fixtures.ts +402 -0
  30. package/src/cli/commands/generate.ts +420 -47
  31. package/src/cli/commands/init/agents-md.ts +61 -0
  32. package/src/cli/commands/init/bootstrap.ts +108 -0
  33. package/src/cli/commands/init/index.ts +244 -0
  34. package/src/cli/commands/init/skills.ts +98 -0
  35. package/src/cli/commands/init/templates/agents.md +77 -0
  36. package/src/cli/commands/init/templates/markdown.d.ts +4 -0
  37. package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
  38. package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
  39. package/src/cli/commands/init/templates/skills/zond.md +651 -0
  40. package/src/cli/commands/init/templates/zond-config.yml +14 -0
  41. package/src/cli/commands/prepare-fixtures.ts +135 -0
  42. package/src/cli/commands/probe/mass-assignment.ts +503 -0
  43. package/src/cli/commands/probe/security.ts +454 -0
  44. package/src/cli/commands/probe/static.ts +255 -0
  45. package/src/cli/commands/probe/webhooks.ts +161 -0
  46. package/src/cli/commands/probe.ts +459 -0
  47. package/src/cli/commands/reference.ts +87 -0
  48. package/src/cli/commands/refresh-api.ts +169 -0
  49. package/src/cli/commands/remove-api.ts +150 -0
  50. package/src/cli/commands/report-bundle.ts +318 -0
  51. package/src/cli/commands/report.ts +241 -0
  52. package/src/cli/commands/request.ts +379 -4
  53. package/src/cli/commands/run.ts +911 -33
  54. package/src/cli/commands/session.ts +244 -0
  55. package/src/cli/commands/use.ts +74 -0
  56. package/src/cli/index.ts +36 -607
  57. package/src/cli/json-envelope.ts +112 -3
  58. package/src/cli/json-schemas.ts +263 -0
  59. package/src/cli/program.ts +218 -0
  60. package/src/cli/resolve.ts +105 -0
  61. package/src/cli/status-filter.ts +124 -0
  62. package/src/cli/util/api-context.ts +85 -0
  63. package/src/cli/version.ts +8 -0
  64. package/src/core/anti-fp/bootstrap.ts +34 -0
  65. package/src/core/anti-fp/index.ts +33 -0
  66. package/src/core/anti-fp/registry.ts +44 -0
  67. package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
  68. package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
  69. package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
  70. package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
  71. package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
  72. package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
  73. package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
  74. package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
  75. package/src/core/anti-fp/types.ts +68 -0
  76. package/src/core/checks/checks/_crud-helpers.ts +133 -0
  77. package/src/core/checks/checks/_negative_mutator.ts +133 -0
  78. package/src/core/checks/checks/_readback-helpers.ts +133 -0
  79. package/src/core/checks/checks/content_type_conformance.ts +39 -0
  80. package/src/core/checks/checks/cross_call_references.ts +134 -0
  81. package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
  82. package/src/core/checks/checks/idempotency_replay.ts +246 -0
  83. package/src/core/checks/checks/ignored_auth.ts +211 -0
  84. package/src/core/checks/checks/index.ts +65 -0
  85. package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
  86. package/src/core/checks/checks/missing_required_header.ts +40 -0
  87. package/src/core/checks/checks/negative_data_rejection.ts +45 -0
  88. package/src/core/checks/checks/not_a_server_error.ts +27 -0
  89. package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
  90. package/src/core/checks/checks/pagination_invariants.ts +238 -0
  91. package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
  92. package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
  93. package/src/core/checks/checks/response_headers_conformance.ts +74 -0
  94. package/src/core/checks/checks/response_schema_conformance.ts +30 -0
  95. package/src/core/checks/checks/status_code_conformance.ts +61 -0
  96. package/src/core/checks/checks/unsupported_method.ts +63 -0
  97. package/src/core/checks/checks/use_after_free.ts +78 -0
  98. package/src/core/checks/index.ts +30 -0
  99. package/src/core/checks/mode.ts +79 -0
  100. package/src/core/checks/recommended-action.ts +64 -0
  101. package/src/core/checks/registry.ts +78 -0
  102. package/src/core/checks/runner.ts +874 -0
  103. package/src/core/checks/sarif.ts +230 -0
  104. package/src/core/checks/stateful.ts +121 -0
  105. package/src/core/checks/types.ts +189 -0
  106. package/src/core/classifier/recommended-action.ts +222 -0
  107. package/src/core/context/current.ts +51 -0
  108. package/src/core/context/session.ts +78 -0
  109. package/src/core/coverage/loader.ts +185 -0
  110. package/src/core/coverage/reasons.ts +300 -0
  111. package/src/core/diagnostics/db-analysis.ts +161 -12
  112. package/src/core/diagnostics/failure-class.ts +120 -0
  113. package/src/core/diagnostics/failure-hints.ts +212 -9
  114. package/src/core/diagnostics/spec-pointer.ts +99 -0
  115. package/src/core/diagnostics/suggested-fixes.ts +156 -0
  116. package/src/core/exporter/case-study/index.ts +270 -0
  117. package/src/core/exporter/curl.ts +40 -0
  118. package/src/core/exporter/exporter.ts +48 -0
  119. package/src/core/exporter/html-report/escape.ts +24 -0
  120. package/src/core/exporter/html-report/index.ts +479 -0
  121. package/src/core/exporter/html-report/script.ts +100 -0
  122. package/src/core/exporter/html-report/styles.ts +408 -0
  123. package/src/core/generator/chunker.ts +53 -15
  124. package/src/core/generator/coverage-phase.ts +0 -0
  125. package/src/core/generator/create-body.ts +89 -0
  126. package/src/core/generator/data-factory.ts +490 -33
  127. package/src/core/generator/describe.ts +1 -1
  128. package/src/core/generator/fixtures-builder.ts +325 -0
  129. package/src/core/generator/index.ts +7 -5
  130. package/src/core/generator/openapi-reader.ts +55 -3
  131. package/src/core/generator/path-param-disambig.ts +114 -0
  132. package/src/core/generator/resources-builder.ts +648 -0
  133. package/src/core/generator/schema-utils.ts +11 -3
  134. package/src/core/generator/serializer.ts +114 -15
  135. package/src/core/generator/suite-generator.ts +484 -77
  136. package/src/core/generator/types.ts +8 -0
  137. package/src/core/identity/identity-file.ts +129 -0
  138. package/src/core/lint/affects.ts +28 -0
  139. package/src/core/lint/config.ts +96 -0
  140. package/src/core/lint/format.ts +42 -0
  141. package/src/core/lint/index.ts +94 -0
  142. package/src/core/lint/reporter.ts +128 -0
  143. package/src/core/lint/rules/consistency.ts +158 -0
  144. package/src/core/lint/rules/heuristics.ts +97 -0
  145. package/src/core/lint/rules/strictness.ts +109 -0
  146. package/src/core/lint/types.ts +96 -0
  147. package/src/core/lint/walker.ts +248 -0
  148. package/src/core/meta/meta-store.ts +6 -73
  149. package/src/core/output/README.md +91 -0
  150. package/src/core/output/index.ts +13 -0
  151. package/src/core/output/run.ts +126 -0
  152. package/src/core/output/types.ts +129 -0
  153. package/src/core/parser/env-interpolation.ts +104 -0
  154. package/src/core/parser/filter.ts +57 -0
  155. package/src/core/parser/schema.ts +132 -5
  156. package/src/core/parser/types.ts +29 -2
  157. package/src/core/parser/variables.ts +0 -0
  158. package/src/core/parser/yaml-parser.ts +108 -13
  159. package/src/core/probe/bootstrap.ts +34 -0
  160. package/src/core/probe/dry-run-envelope.ts +57 -0
  161. package/src/core/probe/mass-assignment-probe-class.ts +198 -0
  162. package/src/core/probe/mass-assignment-probe.ts +1122 -0
  163. package/src/core/probe/mass-assignment-template.ts +212 -0
  164. package/src/core/probe/method-probe.ts +164 -0
  165. package/src/core/probe/method-shared.ts +69 -0
  166. package/src/core/probe/negative-probe.ts +691 -0
  167. package/src/core/probe/orphan-tracker.ts +188 -0
  168. package/src/core/probe/path-discovery.ts +440 -0
  169. package/src/core/probe/probe-harness.ts +120 -0
  170. package/src/core/probe/registry.ts +89 -0
  171. package/src/core/probe/runner.ts +136 -0
  172. package/src/core/probe/security-probe-class.ts +201 -0
  173. package/src/core/probe/security-probe.ts +1453 -0
  174. package/src/core/probe/shared.ts +505 -0
  175. package/src/core/probe/static-probe-class.ts +125 -0
  176. package/src/core/probe/types.ts +165 -0
  177. package/src/core/probe/verdict-aggregator.ts +33 -0
  178. package/src/core/probe/webhooks-probe.ts +284 -0
  179. package/src/core/reporter/console.ts +69 -4
  180. package/src/core/reporter/index.ts +2 -3
  181. package/src/core/reporter/json.ts +15 -2
  182. package/src/core/reporter/junit.ts +27 -12
  183. package/src/core/reporter/ndjson.ts +37 -0
  184. package/src/core/reporter/types.ts +3 -0
  185. package/src/core/runner/assertions.ts +62 -2
  186. package/src/core/runner/async-pool.ts +108 -0
  187. package/src/core/runner/auth-path.ts +8 -0
  188. package/src/core/runner/ci-context.ts +72 -0
  189. package/src/core/runner/executor.ts +391 -52
  190. package/src/core/runner/form-encode.ts +51 -0
  191. package/src/core/runner/http-client.ts +115 -7
  192. package/src/core/runner/learn-drift.ts +293 -0
  193. package/src/core/runner/preflight-vars.ts +149 -0
  194. package/src/core/runner/progress-tracker.ts +73 -0
  195. package/src/core/runner/rate-limiter.ts +203 -0
  196. package/src/core/runner/run-kind.ts +39 -0
  197. package/src/core/runner/schema-validator.ts +312 -0
  198. package/src/core/runner/send-request.ts +153 -20
  199. package/src/core/runner/types.ts +38 -0
  200. package/src/core/secrets/registry.ts +164 -0
  201. package/src/core/secrets/secrets-file.ts +115 -0
  202. package/src/core/selectors/operation-filter.ts +144 -0
  203. package/src/core/setup-api.ts +419 -17
  204. package/src/core/severity/category.ts +94 -0
  205. package/src/core/severity/index.ts +121 -0
  206. package/src/core/spec/layers.ts +154 -0
  207. package/src/core/util/format-eta.ts +21 -0
  208. package/src/core/utils.ts +5 -1
  209. package/src/core/workspace/config.ts +129 -0
  210. package/src/core/workspace/manifest.ts +283 -0
  211. package/src/core/workspace/output-rotation.ts +62 -0
  212. package/src/core/workspace/root.ts +94 -0
  213. package/src/core/workspace/triage-path.ts +87 -0
  214. package/src/db/lint-runs.ts +47 -0
  215. package/src/db/migrate.ts +126 -0
  216. package/src/db/migrations/0001_run_kind.sql +25 -0
  217. package/src/db/migrations/sql.d.ts +4 -0
  218. package/src/db/queries/collections.ts +133 -0
  219. package/src/db/queries/coverage.ts +9 -0
  220. package/src/db/queries/dashboard.ts +59 -0
  221. package/src/db/queries/results.ts +128 -0
  222. package/src/db/queries/runs.ts +235 -0
  223. package/src/db/queries/sessions.ts +42 -0
  224. package/src/db/queries/settings.ts +28 -0
  225. package/src/db/queries/types.ts +172 -0
  226. package/src/db/queries.ts +72 -802
  227. package/src/db/schema.ts +179 -48
  228. package/src/cli/commands/export.ts +0 -144
  229. package/src/cli/commands/guide.ts +0 -127
  230. package/src/cli/commands/init.ts +0 -57
  231. package/src/cli/commands/serve.ts +0 -81
  232. package/src/cli/commands/sync.ts +0 -269
  233. package/src/cli/commands/update.ts +0 -189
  234. package/src/cli/commands/validate.ts +0 -34
  235. package/src/core/exporter/postman.ts +0 -963
  236. package/src/core/generator/guide-builder.ts +0 -253
  237. package/src/core/meta/types.ts +0 -21
  238. package/src/core/parser/index.ts +0 -21
  239. package/src/core/runner/execute-run.ts +0 -132
  240. package/src/core/runner/index.ts +0 -12
  241. package/src/core/sync/spec-differ.ts +0 -38
  242. package/src/web/data/collection-state.ts +0 -362
  243. package/src/web/routes/api.ts +0 -314
  244. package/src/web/routes/dashboard.ts +0 -350
  245. package/src/web/routes/runs.ts +0 -64
  246. package/src/web/schemas.ts +0 -121
  247. package/src/web/server.ts +0 -134
  248. package/src/web/static/htmx.min.cjs +0 -1
  249. package/src/web/static/style.css +0 -1148
  250. package/src/web/views/endpoints-tab.ts +0 -174
  251. package/src/web/views/explorer-tab.ts +0 -402
  252. package/src/web/views/health-strip.ts +0 -92
  253. package/src/web/views/layout.ts +0 -48
  254. package/src/web/views/results.ts +0 -210
  255. package/src/web/views/runs-tab.ts +0 -126
  256. package/src/web/views/suites-tab.ts +0 -181
@@ -1,19 +1,128 @@
1
+ /**
2
+ * Single source of truth for `--json` output across all CLI commands.
3
+ *
4
+ * Every `--json` response carries the same envelope:
5
+ *
6
+ * { ok, command, data, warnings, errors, exit_code? }
7
+ *
8
+ * Commands construct the payload (`data`) and ask one of the helpers
9
+ * below to render it. Don't `console.log(JSON.stringify(...))` ad-hoc
10
+ * for `--json` paths — go through `printJson` / `writeEnvelope` so the
11
+ * shape stays uniform (TASK-73, TASK-74, closed by TASK-184).
12
+ *
13
+ * TASK-296: errors[] is a list of `{code, message, details?}` so an
14
+ * agent can route on `code` without parsing the human message. Helpers
15
+ * accept either a bare `string` (auto-wrapped with code `unknown_error`)
16
+ * or a structured `ZondError` to keep call sites short.
17
+ */
18
+
19
+ // TASK-295: types are derived from the zod schemas in `./json-schemas.ts`
20
+ // so the published JSON Schema (docs/json-schema/) and the runtime types
21
+ // can never drift. Edit the enum/object there, run `bun run schemas`,
22
+ // commit the regenerated docs.
23
+ import type { z } from "zod";
24
+ import {
25
+ ZondErrorCodeSchema,
26
+ ZondErrorSchema,
27
+ } from "./json-schemas.ts";
28
+
29
+ export type ZondErrorCode = z.infer<typeof ZondErrorCodeSchema>;
30
+ export type ZondError = z.infer<typeof ZondErrorSchema>;
31
+
1
32
  export interface JsonEnvelope<T = unknown> {
2
33
  ok: boolean;
3
34
  command: string;
4
35
  data: T;
5
36
  warnings: string[];
6
- errors: string[];
37
+ errors: ZondError[];
38
+ /** Exit code the process will return. Present on error envelopes so a
39
+ * caller can read the taxonomy class without re-parsing $? from a shell
40
+ * (see ZOND.md → "Exit codes"). */
41
+ exit_code?: number;
42
+ }
43
+
44
+ /** Accept either a bare string (auto-coded `unknown_error`) or a fully-
45
+ * structured `ZondError`. Lets us migrate ~100 call sites incrementally
46
+ * without breaking the schema. */
47
+ export type ErrorInput = string | ZondError;
48
+
49
+ function normalizeErrors(errs: readonly ErrorInput[]): ZondError[] {
50
+ return errs.map(e =>
51
+ typeof e === "string" ? { code: "unknown_error" as const, message: e } : e,
52
+ );
7
53
  }
8
54
 
9
55
  export function jsonOk<T>(command: string, data: T, warnings?: string[]): JsonEnvelope<T> {
10
56
  return { ok: true, command, data, warnings: warnings ?? [], errors: [] };
11
57
  }
12
58
 
13
- export function jsonError(command: string, errors: string[], warnings?: string[]): JsonEnvelope<null> {
14
- return { ok: false, command, data: null, warnings: warnings ?? [], errors };
59
+ export function jsonError(
60
+ command: string,
61
+ errors: readonly ErrorInput[],
62
+ warnings?: string[],
63
+ exitCode = 2,
64
+ ): JsonEnvelope<null> {
65
+ return {
66
+ ok: false,
67
+ command,
68
+ data: null,
69
+ warnings: warnings ?? [],
70
+ errors: normalizeErrors(errors),
71
+ exit_code: exitCode,
72
+ };
15
73
  }
16
74
 
17
75
  export function printJson(envelope: JsonEnvelope): void {
18
76
  process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
19
77
  }
78
+
79
+ /** Convenience constructor: `zerr("env_missing", "base_url not set", { var: "base_url" })`. */
80
+ export function zerr(code: ZondErrorCode, message: string, details?: Record<string, unknown>): ZondError {
81
+ return details ? { code, message, details } : { code, message };
82
+ }
83
+
84
+ /** Discriminated-union result an action can hand back to {@link writeEnvelope}. */
85
+ export type EnvelopeResult<T> =
86
+ | { ok: true; data: T; warnings?: string[] }
87
+ | { ok: false; errors: readonly ErrorInput[]; warnings?: string[]; exitCode?: number };
88
+
89
+ /**
90
+ * Render a typed `EnvelopeResult` to stdout as a JSON envelope and return
91
+ * the process exit code (0 on ok, `result.exitCode ?? 2` on error). This
92
+ * is the recommended wrapper for new commands — it lets the handler
93
+ * focus on producing a payload and surfaces the right exit code in one
94
+ * call:
95
+ *
96
+ * const result = await doWork();
97
+ * if (options.json) return writeEnvelope("my-cmd", result);
98
+ * // …human path…
99
+ */
100
+ export function writeEnvelope<T>(command: string, result: EnvelopeResult<T>): number {
101
+ if (result.ok) {
102
+ printJson(jsonOk(command, result.data, result.warnings));
103
+ return 0;
104
+ }
105
+ const exit = result.exitCode ?? 2;
106
+ printJson(jsonError(command, result.errors, result.warnings, exit));
107
+ return exit;
108
+ }
109
+
110
+ /**
111
+ * High-order wrapper for `--json` action handlers. Accepts an async
112
+ * producer and renders its return value (or thrown error) as an
113
+ * envelope. Errors thrown synchronously or asynchronously become
114
+ * `{ ok: false, errors: [{code: "unknown_error", message}] }` with
115
+ * `exitCode = 2`.
116
+ */
117
+ export async function withEnvelope<T>(
118
+ command: string,
119
+ produce: () => Promise<{ data: T; warnings?: string[] }>,
120
+ ): Promise<number> {
121
+ try {
122
+ const { data, warnings } = await produce();
123
+ return writeEnvelope(command, { ok: true, data, warnings });
124
+ } catch (err) {
125
+ const message = err instanceof Error ? err.message : String(err);
126
+ return writeEnvelope(command, { ok: false, errors: [message] });
127
+ }
128
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * TASK-295: zod sources of truth for the `--json` envelope shape and its
3
+ * sub-types. Run `bun run scripts/emit-json-schemas.ts` after changing
4
+ * any of these to regenerate `docs/json-schema/*.schema.json`.
5
+ *
6
+ * Why zod first, JSON Schema second: zod is the type AND the validator
7
+ * we already ship; emitting JSON Schema from it keeps the published
8
+ * schema and the runtime checks in lock-step. New fields land here and
9
+ * propagate, instead of drifting between two hand-maintained shapes.
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ /** TASK-296 closed enum — must stay in sync with `ZondErrorCode` in
15
+ * `src/cli/json-envelope.ts`. */
16
+ export const ZondErrorCodeSchema = z.enum([
17
+ "unknown_error",
18
+ "env_missing",
19
+ "fixture_missing",
20
+ "network_timeout",
21
+ "network_error",
22
+ "sandbox_blocked",
23
+ "spec_load_failure",
24
+ "yaml_parse_error",
25
+ "workspace_not_found",
26
+ "file_not_found",
27
+ "permission_denied",
28
+ "argument_invalid",
29
+ "api_not_registered",
30
+ "db_error",
31
+ "auth_config_error",
32
+ ]);
33
+
34
+ export const ZondErrorSchema = z.object({
35
+ code: ZondErrorCodeSchema,
36
+ message: z.string(),
37
+ details: z.record(z.string(), z.unknown()).optional(),
38
+ });
39
+
40
+ /** TASK-294 closed enum — must stay in sync with `RecommendedAction` in
41
+ * `src/core/diagnostics/failure-hints.ts` and the per-check mapping in
42
+ * `src/core/checks/recommended-action.ts` (ARV-11).
43
+ * ARV-11 added three values for the depth-checks framework:
44
+ * - `tighten_validation` — server accepted invalid input.
45
+ * - `add_required_header` — server didn't enforce a required header.
46
+ * - `wontfix_known_limitation` — known/accepted gap; agent should
47
+ * not retry or report. */
48
+ export const RecommendedActionSchema = z.enum([
49
+ "report_backend_bug",
50
+ "fix_auth_config",
51
+ "fix_test_logic",
52
+ "fix_network_config",
53
+ "fix_env",
54
+ "fix_spec",
55
+ "fix_fixture",
56
+ // ARV-42 — re-run `zond generate` for failures rooted in generator-emitted
57
+ // bodies; editing the YAML directly is overwritten by the next regenerate.
58
+ "regenerate_suite",
59
+ "tighten_validation",
60
+ "add_required_header",
61
+ "wontfix_known_limitation",
62
+ ]);
63
+
64
+ /** Envelope body. `data` is open (`unknown`) so this schema covers every
65
+ * command without enumerating each payload — command-specific schemas
66
+ * can refine `data` per-command in a follow-up. */
67
+ export const JsonEnvelopeSchema = z.object({
68
+ ok: z.boolean(),
69
+ command: z.string(),
70
+ data: z.unknown(),
71
+ warnings: z.array(z.string()),
72
+ errors: z.array(ZondErrorSchema),
73
+ exit_code: z.number().int().optional(),
74
+ });
75
+
76
+ /** ARV-1 (m-15): shape of `data` for `zond checks run --json`. The
77
+ * envelope itself stays the generic JsonEnvelopeSchema; this schema
78
+ * pins the per-command payload so agents can validate findings without
79
+ * parsing them by-hand. ARV-11 adds `recommended_action` as a closed
80
+ * enum on each finding. */
81
+ export const SeveritySchema = z.enum(["critical", "high", "medium", "low", "info"]);
82
+ export const CategorySchema = z.enum(["security", "reliability", "contract", "hygiene"]);
83
+
84
+ export const CheckFindingSchema = z.object({
85
+ check: z.string(),
86
+ severity: SeveritySchema,
87
+ // ARV-251: category drives per-section roll-up. Optional on the wire
88
+ // for backwards compat with older NDJSON streams — derived by reader
89
+ // from check id if absent.
90
+ category: CategorySchema.optional(),
91
+ operation: z.object({
92
+ path: z.string(),
93
+ method: z.string(),
94
+ operationId: z.string().optional(),
95
+ }),
96
+ request_signature: z.string(),
97
+ response_summary: z.object({
98
+ status: z.number().int(),
99
+ content_type: z.string().optional(),
100
+ }),
101
+ message: z.string(),
102
+ evidence: z.record(z.string(), z.unknown()).optional(),
103
+ // ARV-11: recommended_action is now a closed enum so agents can
104
+ // route on it without parsing free-form strings. Same enum used by
105
+ // `db diagnose` (TASK-294) plus three depth-check additions.
106
+ recommended_action: RecommendedActionSchema.optional(),
107
+ });
108
+
109
+ export const CheckRunSummarySchema = z.object({
110
+ operations: z.number().int().nonnegative(),
111
+ cases: z.number().int().nonnegative(),
112
+ checks_run: z.number().int().nonnegative(),
113
+ findings: z.number().int().nonnegative(),
114
+ by_severity: z.object({
115
+ critical: z.number().int().nonnegative(),
116
+ high: z.number().int().nonnegative(),
117
+ medium: z.number().int().nonnegative(),
118
+ low: z.number().int().nonnegative(),
119
+ info: z.number().int().nonnegative(),
120
+ }),
121
+ by_category: z.object({
122
+ security: z.number().int().nonnegative(),
123
+ reliability: z.number().int().nonnegative(),
124
+ contract: z.number().int().nonnegative(),
125
+ hygiene: z.number().int().nonnegative(),
126
+ }),
127
+ // ARV-26: per-(check, reason) skip tally — surfaces probe outcomes that
128
+ // never produced a checkable response (e.g. probe got 4xx, schema only on
129
+ // 200) so "0 findings" doesn't read as "all green".
130
+ skipped_outcomes: z.record(z.string(), z.number().int().nonnegative()),
131
+ });
132
+
133
+ export const ChecksRunDataSchema = z.object({
134
+ findings: z.array(CheckFindingSchema),
135
+ summary: CheckRunSummarySchema,
136
+ });
137
+
138
+ /** ARV-10 (m-15): NDJSON streaming events emitted by `zond checks run
139
+ * --ndjson`. Each event is a snapshot JSON line on stdout — agents pipe
140
+ * the stream into `jq` / a validator and consume findings as they happen
141
+ * rather than waiting for the run to finish. The discriminated union
142
+ * below is the schema we publish — every emitted line MUST match one
143
+ * branch exactly (verified by ajv in tests). */
144
+ const OperationRefSchema = z.object({
145
+ path: z.string(),
146
+ method: z.string(),
147
+ operationId: z.string().optional(),
148
+ });
149
+
150
+ export const NdjsonCheckStartEventSchema = z.object({
151
+ type: z.literal("check_start"),
152
+ ts: z.string(),
153
+ operation: OperationRefSchema,
154
+ });
155
+
156
+ export const NdjsonCheckResultEventSchema = z.object({
157
+ type: z.literal("check_result"),
158
+ ts: z.string(),
159
+ check: z.string(),
160
+ verdict: z.enum(["pass", "fail"]),
161
+ operation: OperationRefSchema,
162
+ request_signature: z.string(),
163
+ response: z.object({
164
+ status: z.number().int(),
165
+ content_type: z.string().optional(),
166
+ }),
167
+ });
168
+
169
+ export const NdjsonFindingEventSchema = z.object({
170
+ type: z.literal("finding"),
171
+ ts: z.string(),
172
+ // ARV-156: mirror the top-level `check` field carried by check_start /
173
+ // check_result so consumer pipelines can `jq -c '.check'` uniformly
174
+ // across event types without branching on `.type`. The same value lives
175
+ // inside `.finding.check` — existing consumers reading the nested form
176
+ // keep working (back-compat addition, not a rename).
177
+ check: z.string(),
178
+ finding: CheckFindingSchema,
179
+ });
180
+
181
+ export const NdjsonSummaryEventSchema = z.object({
182
+ type: z.literal("summary"),
183
+ ts: z.string(),
184
+ summary: CheckRunSummarySchema,
185
+ });
186
+
187
+ export const NdjsonEventSchema = z.discriminatedUnion("type", [
188
+ NdjsonCheckStartEventSchema,
189
+ NdjsonCheckResultEventSchema,
190
+ NdjsonFindingEventSchema,
191
+ NdjsonSummaryEventSchema,
192
+ ]);
193
+
194
+ /** m-17 / ARV-50: shape of `data` for `zond probe <class> --dry-run --json`.
195
+ * Severity is intentionally absent — nothing is classified yet, so
196
+ * reusing the run-time bucket would mislead CI gates (F1-15). The
197
+ * `skip_reason` enum is open across probe families (e.g. security has
198
+ * `isolated-protected`, mass-assignment has its own subset); we keep
199
+ * it as a string with documented values rather than a closed enum
200
+ * that needs to be rev'd every time a new class lands. */
201
+ export const ProbeEndpointPlanSchema = z.object({
202
+ path: z.string(),
203
+ method: z.string(),
204
+ planned: z.boolean(),
205
+ classes_planned: z.array(z.string()),
206
+ fields_planned: z.array(z.string()),
207
+ skip_reason: z.string().nullable(),
208
+ });
209
+
210
+ export const ProbeDryRunDataSchema = z.object({
211
+ endpoints: z.array(ProbeEndpointPlanSchema),
212
+ summary: z.object({
213
+ totalEndpoints: z.number().int().nonnegative(),
214
+ planned: z.number().int().nonnegative(),
215
+ skipped: z.number().int().nonnegative(),
216
+ }),
217
+ });
218
+
219
+ /** m-17 / ARV-51: shape of `data` for live probe runs (`zond probe <class>
220
+ * --report json` or the default `--json`). One entry per endpoint with
221
+ * structured findings — no markdown blob. The legacy `data.digest.stdout`
222
+ * field is gone (F3-15 / F4-15). */
223
+ export const ProbeFindingSchema = z.object({
224
+ class: z.string(),
225
+ severity: z.enum(["high", "low", "inconclusive", "ok"]),
226
+ evidence: z.record(z.string(), z.unknown()),
227
+ });
228
+
229
+ export const ProbeEndpointResultSchema = z.object({
230
+ path: z.string(),
231
+ method: z.string(),
232
+ classes_run: z.array(z.string()),
233
+ findings: z.array(ProbeFindingSchema),
234
+ status: z.enum(["ok", "high", "low", "inconclusive", "skipped"]),
235
+ skip_reason: z.string().optional(),
236
+ });
237
+
238
+ export const ProbeRunDataSchema = z.object({
239
+ endpoints: z.array(ProbeEndpointResultSchema),
240
+ summary: z.object({
241
+ totalEndpoints: z.number().int().nonnegative(),
242
+ probed: z.number().int().nonnegative(),
243
+ by_status: z.object({
244
+ ok: z.number().int().nonnegative(),
245
+ high: z.number().int().nonnegative(),
246
+ low: z.number().int().nonnegative(),
247
+ inconclusive: z.number().int().nonnegative(),
248
+ skipped: z.number().int().nonnegative(),
249
+ }),
250
+ }),
251
+ });
252
+
253
+ export const SCHEMAS = {
254
+ envelope: JsonEnvelopeSchema,
255
+ error: ZondErrorSchema,
256
+ errorCode: ZondErrorCodeSchema,
257
+ recommendedAction: RecommendedActionSchema,
258
+ checksRunData: ChecksRunDataSchema,
259
+ checkFinding: CheckFindingSchema,
260
+ "ndjson-events": NdjsonEventSchema,
261
+ probeDryRun: ProbeDryRunDataSchema,
262
+ probeRun: ProbeRunDataSchema,
263
+ } as const;
@@ -0,0 +1,218 @@
1
+ import { Command } from "commander";
2
+
3
+ import { registerRun } from "./commands/run.ts";
4
+ import { registerCheck, registerLint } from "./commands/check.ts";
5
+ import { registerChecks } from "./commands/checks.ts";
6
+ import { registerCoverage } from "./commands/coverage.ts";
7
+ import { registerCi } from "./commands/ci-init.ts";
8
+ import { registerClean } from "./commands/clean.ts";
9
+ import { registerCleanup } from "./commands/cleanup.ts";
10
+ import { registerInit } from "./commands/init/index.ts";
11
+ import { registerDescribe } from "./commands/describe.ts";
12
+ import { registerDb } from "./commands/db.ts";
13
+ import { registerRequest } from "./commands/request.ts";
14
+ import { registerGenerate } from "./commands/generate.ts";
15
+ import { registerPrepareFixtures } from "./commands/prepare-fixtures.ts";
16
+ import { registerFixtures } from "./commands/fixtures.ts";
17
+ import { registerProbes } from "./commands/probe.ts";
18
+ import { bootstrapProbes } from "../core/probe/bootstrap.ts";
19
+ import { bootstrapAntiFp } from "../core/anti-fp/bootstrap.ts";
20
+ import { registerReport } from "./commands/report.ts";
21
+ import { registerCatalog } from "./commands/catalog.ts";
22
+ import { registerCompletions } from "./commands/completions.ts";
23
+ import { registerUse } from "./commands/use.ts";
24
+ import { registerSession } from "./commands/session.ts";
25
+ import { registerDoctor } from "./commands/doctor.ts";
26
+ import { registerRefreshApi } from "./commands/refresh-api.ts";
27
+ import { registerAdd } from "./commands/add-api.ts";
28
+ import { registerRemove } from "./commands/remove-api.ts";
29
+ import { registerAudit } from "./commands/audit.ts";
30
+ import { registerReference } from "./commands/reference.ts";
31
+ import { registerApiAnnotate } from "./commands/api/annotate/index.ts";
32
+
33
+ import { getSecretRegistry } from "../core/secrets/registry.ts";
34
+ import { getRuntimeInfo } from "./runtime.ts";
35
+ import { VERSION } from "./version.ts";
36
+ import { preprocessArgv } from "./argv.ts";
37
+
38
+ export { preprocessArgv };
39
+
40
+ // ── Program builder ──
41
+
42
+ export function buildProgram(): Command {
43
+ const program = new Command("zond")
44
+ .description("API Testing Platform")
45
+ .version(`${VERSION} (${getRuntimeInfo()})`, "-v, --version", "Show version")
46
+ .helpOption("-h, --help", "Show this help")
47
+ .showHelpAfterError("(run 'zond --help' for usage)")
48
+ .exitOverride()
49
+ // TASK-166 (m-10): global escape hatch for local debugging — disables
50
+ // the secret registry's redaction pass everywhere (DB writes,
51
+ // exporters, stdout). Default is redact-on. Hook is read from the
52
+ // env var so it survives across nested subcommand parsers.
53
+ .option("--no-redact", "Disable auto-redaction of registered secret values (debug only)")
54
+ .option(
55
+ "--api <name>",
56
+ "TASK-290: Select the active API for this invocation. Resolution order: per-command --api > global --api (this flag) > ZOND_API env > .zond/current-api file (set via `zond use <name>`).",
57
+ )
58
+ .hook("preAction", (thisCommand) => {
59
+ const enabled = thisCommand.opts().redact !== false;
60
+ // Mirror the flag into env so deeply-nested code that doesn't have
61
+ // access to `cmd` (e.g. setup-api, exporters) can still consult it.
62
+ process.env.ZOND_REDACT = enabled ? "1" : "0";
63
+ getSecretRegistry().setEnabled(enabled);
64
+ // TASK-290: mirror the global --api flag into env so the resolution
65
+ // chain in core/context/current.ts (which has no `cmd` ref) sees it.
66
+ // Per-command --api still wins because it is passed positionally to
67
+ // resolveSpecArg / resolveApiCollection.
68
+ const apiGlobal = thisCommand.opts().api;
69
+ if (typeof apiGlobal === "string" && apiGlobal.length > 0) {
70
+ process.env.ZOND_API_GLOBAL = apiGlobal;
71
+ } else {
72
+ delete process.env.ZOND_API_GLOBAL;
73
+ }
74
+ });
75
+
76
+ registerRun(program);
77
+
78
+ registerCheck(program);
79
+ registerChecks(program);
80
+ registerLint(program);
81
+
82
+ registerCi(program);
83
+
84
+ registerUse(program);
85
+ registerRefreshApi(program);
86
+ registerDoctor(program);
87
+
88
+ registerSession(program);
89
+ registerCoverage(program);
90
+
91
+ registerInit(program);
92
+ registerAdd(program);
93
+ registerRemove(program);
94
+
95
+ registerDescribe(program);
96
+ registerDb(program);
97
+ registerRequest(program);
98
+
99
+ registerClean(program);
100
+ registerCleanup(program);
101
+
102
+ registerGenerate(program);
103
+ registerPrepareFixtures(program);
104
+ registerFixtures(program);
105
+ registerAudit(program);
106
+
107
+ // m-17 / ARV-49: validate registered probes implement the Probe
108
+ // contract before commander gets to wire them up. Boot-throws on a
109
+ // missing slot, so a regression can't slip past CI.
110
+ bootstrapProbes();
111
+ // ARV-123/124: populate the anti-FP rule registry before checks run
112
+ // — checks call applyAntiFp() and need every shipped rule present.
113
+ bootstrapAntiFp();
114
+ registerProbes(program);
115
+
116
+ registerCatalog(program);
117
+ registerReport(program);
118
+
119
+ registerCompletions(program);
120
+ registerReference(program);
121
+ registerApiAnnotate(program);
122
+
123
+ // TASK-267: group top-level commands by phase in `zond --help`. Without
124
+ // grouping, the flat 20+ command list buries the workflow shape; with it,
125
+ // a new tester can see "setup → generate → run → analyze → report" at a
126
+ // glance. Commands not listed below stay in the default group.
127
+ const HELP_GROUPS: Record<string, string> = {
128
+ // setup: register an API, prepare workspace
129
+ "init": "Setup:",
130
+ "add": "Setup:",
131
+ "remove": "Setup:",
132
+ "use": "Setup:",
133
+ "refresh-api": "Setup:",
134
+ "doctor": "Setup:",
135
+ "clean": "Setup:",
136
+ "cleanup": "Setup:",
137
+ // generate: produce suites/probes from the spec
138
+ "generate": "Generate:",
139
+ "prepare-fixtures": "Generate:",
140
+ "probe": "Generate:",
141
+ // run: execute suites against a live API
142
+ "run": "Run:",
143
+ "session": "Run:",
144
+ "request": "Run:",
145
+ // analyze: post-run inspection and triage
146
+ "coverage": "Analyze:",
147
+ "db": "Analyze:",
148
+ "audit": "Analyze:",
149
+ "check": "Analyze:",
150
+ "checks": "Analyze:",
151
+ "describe": "Analyze:",
152
+ // report: outbound artefacts (HTML, bundles, catalog)
153
+ "report": "Report:",
154
+ "catalog": "Report:",
155
+ // other: scaffolding / shell integration
156
+ "ci": "Other:",
157
+ "completions": "Other:",
158
+ "reference": "Other:",
159
+ };
160
+ for (const sub of program.commands) {
161
+ const group = HELP_GROUPS[sub.name()];
162
+ if (group) sub.helpGroup(group);
163
+ }
164
+
165
+ // TASK-73: previously `--json` was a top-level/global option that propagated
166
+ // to every subcommand, which collided with `run --report json` (and broke
167
+ // `run --json` outright). Now it is per-command. Attach `--json` to every
168
+ // subcommand that previously read it via globalJson(), EXCEPT `run` —
169
+ // run's only JSON output path is `--report json`.
170
+ // Skip by fully-qualified path so `db run` (inner) keeps --json while
171
+ // top-level `run` does not.
172
+ const skipJson = new Set(["run", "completions"]);
173
+ const attachJson = (cmd: Command, parentPath: string): void => {
174
+ const path = parentPath ? `${parentPath} ${cmd.name()}` : cmd.name();
175
+ // Only leaf commands (those with action handlers) get --json — parent
176
+ // namespace commands like `db` and `ci` would otherwise shadow the option
177
+ // on their children and `cmd.opts()` on the leaf would not see --json.
178
+ const hasAction = (cmd as unknown as { _actionHandler?: unknown })._actionHandler != null;
179
+ if (hasAction && !skipJson.has(path)) {
180
+ cmd.option(
181
+ "--json",
182
+ "Emit a `{ok, command, data, warnings, errors}` envelope on stdout. " +
183
+ "Distinct from `zond run --report json` (which is a per-test test-run report).",
184
+ );
185
+ }
186
+ for (const sub of cmd.commands) attachJson(sub, path);
187
+ };
188
+ for (const sub of program.commands) attachJson(sub, "");
189
+
190
+ // TASK-297: stamp every leaf with a "related skill" footer so `zond <cmd>
191
+ // --help` is a single-stop entry point for an agent: discover the flag
192
+ // surface AND know which skill file to open for the workflow context.
193
+ // Mapped by fully-qualified path; `*` matches any unnamed leaf and is
194
+ // tried last.
195
+ const skillFor: Record<string, string> = {
196
+ // Depth-check & probe commands → checks skill (annotate flow + m-20).
197
+ "checks run": "skills/zond-checks.md",
198
+ "checks list": "skills/zond-checks.md",
199
+ "api annotate": "skills/zond-checks.md",
200
+ "probe security": "skills/zond-checks.md",
201
+ "probe mass-assignment": "skills/zond-checks.md",
202
+ // Triage-class commands.
203
+ "db diagnose": "skills/zond-triage.md",
204
+ "db compare": "skills/zond-triage.md",
205
+ };
206
+ const attachHelp = (cmd: Command, parentPath: string): void => {
207
+ const path = parentPath ? `${parentPath} ${cmd.name()}` : cmd.name();
208
+ const hasAction = (cmd as unknown as { _actionHandler?: unknown })._actionHandler != null;
209
+ if (hasAction) {
210
+ const skill = skillFor[path] ?? "skills/zond.md";
211
+ cmd.addHelpText("after", `\nRelated skill: ${skill}`);
212
+ }
213
+ for (const sub of cmd.commands) attachHelp(sub, path);
214
+ };
215
+ for (const sub of program.commands) attachHelp(sub, "");
216
+
217
+ return program;
218
+ }