@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,8 +1,9 @@
1
1
  import { getCollections, getRuns, getRunDetail, diagnoseRun, compareRuns } from "../../core/diagnostics/db-analysis.ts";
2
- import { getFilteredResults } from "../../db/queries.ts";
2
+ import { getFilteredResults, getLatestFailingRunId, getLatestRunId } from "../../db/queries.ts";
3
3
  import { getDb } from "../../db/schema.ts";
4
4
  import { printError } from "../output.ts";
5
5
  import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
6
+ import { parseStatusFilter, compileStatusFilterToSql, type StatusMatcher } from "../status-filter.ts";
6
7
 
7
8
  export interface DbOptions {
8
9
  subcommand: string;
@@ -12,7 +13,16 @@ export interface DbOptions {
12
13
  dbPath?: string;
13
14
  json?: boolean;
14
15
  method?: string;
15
- status?: number;
16
+ /** Raw `--status` argument (TASK-140). Parsed lazily so help text can show
17
+ * the literal user input in error messages. */
18
+ status?: string;
19
+ /** TASK-266: `db diagnose --latest` — pick the most recent run regardless
20
+ * of failure status (default is most recent failing run). */
21
+ latest?: boolean;
22
+ /** TASK-266: explicit run id override (`--run-id N`). Same effect as
23
+ * passing the id positionally; kept as a flag because the `zond-triage`
24
+ * skill and other agents prefer self-documenting flags. */
25
+ runId?: number;
16
26
  }
17
27
 
18
28
  export async function dbCommand(options: DbOptions): Promise<number> {
@@ -46,7 +56,8 @@ export async function dbCommand(options: DbOptions): Promise<number> {
46
56
  } else {
47
57
  for (const r of runs) {
48
58
  const run = r as any;
49
- const status = run.failed > 0 ? "FAIL" : "PASS";
59
+ const isFail = run.failed > 0 || (run.total > 0 && run.passed === 0);
60
+ const status = isFail ? "FAIL" : "PASS";
50
61
  console.log(`#${run.id} ${status} ${run.passed}/${run.total} passed (${run.started_at})`);
51
62
  }
52
63
  }
@@ -65,7 +76,21 @@ export async function dbCommand(options: DbOptions): Promise<number> {
65
76
  // If filtering by method/status, show filtered results instead of full detail
66
77
  if (options.method || options.status !== undefined) {
67
78
  getDb(options.dbPath);
68
- const results = getFilteredResults(id, { method: options.method, status: options.status });
79
+ let statusSql: { sql: string; params: number[] } | undefined;
80
+ if (options.status !== undefined) {
81
+ let matcher: StatusMatcher;
82
+ try {
83
+ matcher = parseStatusFilter(options.status);
84
+ } catch (err) {
85
+ const msg = `Invalid --status: ${(err as Error).message}`;
86
+ if (json) printJson(jsonError("db run", [msg]));
87
+ else printError(msg);
88
+ return 2;
89
+ }
90
+ const compiled = compileStatusFilterToSql(matcher, "response_status");
91
+ if (compiled) statusSql = compiled;
92
+ }
93
+ const results = getFilteredResults(id, { method: options.method, statusSql });
69
94
  if (json) {
70
95
  printJson(jsonOk("db run", { run_id: id, count: results.length, results }));
71
96
  } else {
@@ -83,17 +108,72 @@ export async function dbCommand(options: DbOptions): Promise<number> {
83
108
  }
84
109
 
85
110
  case "diagnose": {
86
- const id = parseInt(positional[0] ?? "", 10);
87
- if (isNaN(id)) {
88
- const msg = "Missing run ID. Usage: zond db diagnose <id>";
111
+ // TASK-266: resolve run id with the priority
112
+ // explicit positional > --run-id > --latest > last failing run.
113
+ // The bare `zond db diagnose` is the agent-friendly default the
114
+ // `zond-triage` skill relies on; `--latest` exists for "show me the
115
+ // last run, even if it passed".
116
+ getDb(options.dbPath);
117
+ let id: number | null = null;
118
+ let resolution: "explicit" | "run-id-flag" | "latest" | "latest-failing" = "explicit";
119
+ const positionalRaw = positional[0];
120
+ if (positionalRaw !== undefined && positionalRaw !== "") {
121
+ const n = parseInt(positionalRaw, 10);
122
+ if (isNaN(n)) {
123
+ const msg = `Invalid run id: ${positionalRaw}. Expected a positive integer.`;
124
+ if (json) printJson(jsonError("db diagnose", [msg]));
125
+ else printError(msg);
126
+ return 2;
127
+ }
128
+ id = n;
129
+ } else if (options.runId !== undefined) {
130
+ id = options.runId;
131
+ resolution = "run-id-flag";
132
+ } else if (options.latest) {
133
+ id = getLatestRunId();
134
+ resolution = "latest";
135
+ } else {
136
+ id = getLatestFailingRunId();
137
+ resolution = "latest-failing";
138
+ if (id == null) {
139
+ // No failures — fall back to the latest run so the user still
140
+ // gets a useful payload, with a "no failures" hint.
141
+ const fallback = getLatestRunId();
142
+ if (fallback == null) {
143
+ const msg = "No runs in the database yet. Try `zond run <suite>` first.";
144
+ if (json) printJson(jsonError("db diagnose", [msg]));
145
+ else printError(msg);
146
+ return 1;
147
+ }
148
+ const result = diagnoseRun(fallback, options.verbose, options.dbPath, options.limit);
149
+ const warning = `No failing runs — diagnosing latest run #${fallback} (all passed).`;
150
+ if (json) {
151
+ printJson(jsonOk("db diagnose", { ...result, resolution: "latest-no-failures", run_id: fallback }, [warning]));
152
+ } else {
153
+ process.stderr.write(`zond: ${warning}\n`);
154
+ console.log(JSON.stringify(result, null, 2));
155
+ }
156
+ return 0;
157
+ }
158
+ }
159
+ if (id == null) {
160
+ const msg = "Missing run ID. Usage: zond db diagnose [id] (default: last failing run)";
89
161
  if (json) printJson(jsonError("db diagnose", [msg]));
90
162
  else printError(msg);
91
163
  return 2;
92
164
  }
93
165
  const result = diagnoseRun(id, options.verbose, options.dbPath, options.limit);
94
166
  if (json) {
95
- printJson(jsonOk("db diagnose", result));
167
+ printJson(jsonOk("db diagnose", { ...result, resolution, run_id: id }));
96
168
  } else {
169
+ if (resolution !== "explicit") {
170
+ const label = resolution === "latest-failing"
171
+ ? `last failing run #${id}`
172
+ : resolution === "latest"
173
+ ? `latest run #${id}`
174
+ : `run #${id}`;
175
+ process.stderr.write(`zond: diagnosing ${label}\n`);
176
+ }
97
177
  console.log(JSON.stringify(result, null, 2));
98
178
  }
99
179
  return 0;
@@ -134,3 +214,95 @@ export async function dbCommand(options: DbOptions): Promise<number> {
134
214
  return 2;
135
215
  }
136
216
  }
217
+
218
+ import type { Command } from "commander";
219
+ import { globalJson } from "../resolve.ts";
220
+ import { parsePositiveInt } from "../argv.ts";
221
+
222
+ export function registerDb(program: Command): void {
223
+ const db = program.command("db").description("Query the test database");
224
+
225
+ db
226
+ .command("collections")
227
+ .description("List all API collections")
228
+ .option("--db <path>", "Path to SQLite database file")
229
+ .action(async (opts, cmd: Command) => {
230
+ process.exitCode = await dbCommand({
231
+ subcommand: "collections",
232
+ positional: [],
233
+ dbPath: opts.db,
234
+ json: globalJson(cmd),
235
+ });
236
+ });
237
+
238
+ db
239
+ .command("runs")
240
+ .description("List recent test runs")
241
+ .option("--limit <N>", "Maximum number of runs to display", parsePositiveInt("--limit"))
242
+ .option("--db <path>", "Path to SQLite database file")
243
+ .action(async (opts, cmd: Command) => {
244
+ process.exitCode = await dbCommand({
245
+ subcommand: "runs",
246
+ positional: [],
247
+ limit: opts.limit,
248
+ dbPath: opts.db,
249
+ json: globalJson(cmd),
250
+ });
251
+ });
252
+
253
+ db
254
+ .command("run <id>")
255
+ .description("Show run details")
256
+ .option("--verbose", "Show all results")
257
+ .option("--method <method>", "Filter by HTTP method")
258
+ .option(
259
+ "--status <expr>",
260
+ "Filter by HTTP status. Accepts: exact code (502), class (5xx), range (500-599), comparison (>=500, <400), or comma-separated mix (5xx,429).",
261
+ )
262
+ .option("--db <path>", "Path to SQLite database file")
263
+ .action(async (id: string, opts, cmd: Command) => {
264
+ process.exitCode = await dbCommand({
265
+ subcommand: "run",
266
+ positional: [id],
267
+ verbose: opts.verbose === true,
268
+ method: opts.method,
269
+ status: opts.status,
270
+ dbPath: opts.db,
271
+ json: globalJson(cmd),
272
+ });
273
+ });
274
+
275
+ db
276
+ .command("diagnose [id]")
277
+ .description("Diagnose run failures. Without [id]: defaults to the most recent failing run (TASK-266); falls back to latest run with a 'no failures' note when nothing has failed.")
278
+ .option("--latest", "Diagnose the most recent run regardless of status (TASK-266)")
279
+ .option("--run-id <N>", "Explicit run id override (same as positional [id]; preferred form for agents)", parsePositiveInt("--run-id"))
280
+ .option("--limit <N>", "Examples per failure group", parsePositiveInt("--limit"))
281
+ .option("--verbose", "Show all examples (not grouped)")
282
+ .option("--db <path>", "Path to SQLite database file")
283
+ .action(async (id: string | undefined, opts, cmd: Command) => {
284
+ process.exitCode = await dbCommand({
285
+ subcommand: "diagnose",
286
+ positional: id !== undefined ? [id] : [],
287
+ limit: opts.limit,
288
+ verbose: opts.verbose === true,
289
+ latest: opts.latest === true,
290
+ runId: opts.runId,
291
+ dbPath: opts.db,
292
+ json: globalJson(cmd),
293
+ });
294
+ });
295
+
296
+ db
297
+ .command("compare <idA> <idB>")
298
+ .description("Compare two runs")
299
+ .option("--db <path>", "Path to SQLite database file")
300
+ .action(async (idA: string, idB: string, opts, cmd: Command) => {
301
+ process.exitCode = await dbCommand({
302
+ subcommand: "compare",
303
+ positional: [idA, idB],
304
+ dbPath: opts.db,
305
+ json: globalJson(cmd),
306
+ });
307
+ });
308
+ }
@@ -37,7 +37,13 @@ export async function describeCommand(options: DescribeOptions): Promise<number>
37
37
  return 0;
38
38
  }
39
39
 
40
- if (options.compact) {
40
+ // No flags = compact listing. Erroring out (exit 2) on `zond describe`
41
+ // breaks scripts that pipe the listing; the listing IS the only useful
42
+ // default since the spec is the only required arg. Single-endpoint detail
43
+ // remains opt-in via --method/--path.
44
+ const useCompact = options.compact || (!options.method && !options.path);
45
+
46
+ if (useCompact) {
41
47
  const endpoints = await describeCompact(options.specPath);
42
48
 
43
49
  if (options.json) {
@@ -55,8 +61,10 @@ export async function describeCommand(options: DescribeOptions): Promise<number>
55
61
  return 0;
56
62
  }
57
63
 
64
+ // We get here only when one of --method/--path is set but not both —
65
+ // single-endpoint detail needs both halves of the address.
58
66
  if (!options.method || !options.path) {
59
- const msg = "Missing --method and --path. Use --compact for all endpoints, or specify --method and --path for one.";
67
+ const msg = "--method and --path must be used together for single-endpoint detail. Drop both for the compact listing.";
60
68
  if (options.json) {
61
69
  printJson(jsonError("describe", [msg]));
62
70
  } else {
@@ -83,3 +91,30 @@ export async function describeCommand(options: DescribeOptions): Promise<number>
83
91
  return 2;
84
92
  }
85
93
  }
94
+
95
+ import type { Command } from "commander";
96
+ import { globalJson, resolveSpecArg } from "../resolve.ts";
97
+
98
+ export function registerDescribe(program: Command): void {
99
+ program
100
+ .command("describe [spec]")
101
+ .description("Describe endpoints from OpenAPI spec")
102
+ .option("--api <name>", "Use the registered API's spec (apis/<name>/spec.json)")
103
+ .option("--db <path>", "Path to SQLite database file")
104
+ .option("--compact", "List all endpoints briefly")
105
+ .option("--list-params", "List all unique parameters across all endpoints")
106
+ .option("--method <method>", "HTTP method for single endpoint detail")
107
+ .option("--path <path>", "Endpoint path for single endpoint detail")
108
+ .action(async (specPos: string | undefined, opts, cmd: Command) => {
109
+ const resolved = resolveSpecArg(specPos, opts.api, opts.db);
110
+ if ("error" in resolved) { printError(resolved.error); process.exitCode = 2; return; }
111
+ process.exitCode = await describeCommand({
112
+ specPath: resolved.spec,
113
+ compact: opts.compact === true,
114
+ listParams: opts.listParams === true,
115
+ method: opts.method,
116
+ path: opts.path,
117
+ json: globalJson(cmd),
118
+ });
119
+ });
120
+ }