@kirrosh/zond 0.26.1 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +39 -22
- package/bin/zond.mjs +23 -0
- package/package.json +36 -23
- package/scripts/npm/postinstall.mjs +76 -0
- package/src/CLAUDE.md +0 -112
- package/src/bun-types.d.ts +0 -5
- package/src/cli/argv.ts +0 -122
- package/src/cli/commands/add-api.ts +0 -146
- package/src/cli/commands/api/annotate/idempotency.ts +0 -59
- package/src/cli/commands/api/annotate/index.ts +0 -880
- package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
- package/src/cli/commands/api/annotate/overlay.ts +0 -206
- package/src/cli/commands/api/annotate/pagination.ts +0 -64
- package/src/cli/commands/api/annotate/prompts.ts +0 -220
- package/src/cli/commands/api/annotate/readback.ts +0 -58
- package/src/cli/commands/api/annotate/resources.ts +0 -91
- package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
- package/src/cli/commands/audit.ts +0 -786
- package/src/cli/commands/catalog.ts +0 -97
- package/src/cli/commands/check.ts +0 -361
- package/src/cli/commands/checks.ts +0 -1072
- package/src/cli/commands/ci-init.ts +0 -223
- package/src/cli/commands/clean.ts +0 -212
- package/src/cli/commands/cleanup.ts +0 -236
- package/src/cli/commands/completions.ts +0 -192
- package/src/cli/commands/coverage.ts +0 -872
- package/src/cli/commands/db.ts +0 -611
- package/src/cli/commands/describe.ts +0 -120
- package/src/cli/commands/discover.ts +0 -1356
- package/src/cli/commands/doctor.ts +0 -661
- package/src/cli/commands/fixtures.ts +0 -402
- package/src/cli/commands/generate.ts +0 -577
- package/src/cli/commands/init/agents-md.ts +0 -61
- package/src/cli/commands/init/bootstrap.ts +0 -111
- package/src/cli/commands/init/index.ts +0 -244
- package/src/cli/commands/init/skills.ts +0 -141
- package/src/cli/commands/init/templates/agents.md +0 -86
- package/src/cli/commands/init/templates/markdown.d.ts +0 -4
- package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
- package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
- package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
- package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
- package/src/cli/commands/init/templates/skills/zond.md +0 -861
- package/src/cli/commands/init/templates/zond-config.yml +0 -14
- package/src/cli/commands/prepare-fixtures.ts +0 -97
- package/src/cli/commands/probe/_seed-bodies.ts +0 -52
- package/src/cli/commands/probe/mass-assignment.ts +0 -594
- package/src/cli/commands/probe/security.ts +0 -537
- package/src/cli/commands/probe/static.ts +0 -255
- package/src/cli/commands/probe/webhooks.ts +0 -163
- package/src/cli/commands/probe.ts +0 -535
- package/src/cli/commands/reference.ts +0 -87
- package/src/cli/commands/refresh-api.ts +0 -227
- package/src/cli/commands/remove-api.ts +0 -150
- package/src/cli/commands/report-bundle.ts +0 -310
- package/src/cli/commands/report.ts +0 -241
- package/src/cli/commands/request.ts +0 -548
- package/src/cli/commands/run.ts +0 -1162
- package/src/cli/commands/schema-from-runs.ts +0 -128
- package/src/cli/commands/secrets.ts +0 -133
- package/src/cli/commands/session.ts +0 -244
- package/src/cli/commands/use.ts +0 -74
- package/src/cli/index.ts +0 -55
- package/src/cli/json-envelope.ts +0 -108
- package/src/cli/json-schemas.ts +0 -314
- package/src/cli/output.ts +0 -40
- package/src/cli/program.ts +0 -219
- package/src/cli/resolve.ts +0 -105
- package/src/cli/runtime.ts +0 -7
- package/src/cli/safe-live.ts +0 -24
- package/src/cli/status-filter.ts +0 -114
- package/src/cli/util/api-context.ts +0 -85
- package/src/cli/version.ts +0 -8
- package/src/core/audit/persist.ts +0 -183
- package/src/core/checks/budget.ts +0 -59
- package/src/core/checks/checks/_crud-helpers.ts +0 -133
- package/src/core/checks/checks/_negative_mutator.ts +0 -133
- package/src/core/checks/checks/_readback-helpers.ts +0 -133
- package/src/core/checks/checks/content_type_conformance.ts +0 -39
- package/src/core/checks/checks/cross_call_references.ts +0 -147
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
- package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
- package/src/core/checks/checks/idempotency_replay.ts +0 -242
- package/src/core/checks/checks/ignored_auth.ts +0 -254
- package/src/core/checks/checks/index.ts +0 -68
- package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
- package/src/core/checks/checks/missing_required_header.ts +0 -40
- package/src/core/checks/checks/negative_data_rejection.ts +0 -148
- package/src/core/checks/checks/not_a_server_error.ts +0 -35
- package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
- package/src/core/checks/checks/pagination_invariants.ts +0 -419
- package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
- package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
- package/src/core/checks/checks/response_headers_conformance.ts +0 -74
- package/src/core/checks/checks/response_schema_conformance.ts +0 -30
- package/src/core/checks/checks/status_code_conformance.ts +0 -132
- package/src/core/checks/checks/unsupported_method.ts +0 -63
- package/src/core/checks/checks/use_after_free.ts +0 -78
- package/src/core/checks/index.ts +0 -30
- package/src/core/checks/mode.ts +0 -82
- package/src/core/checks/recommended-action.ts +0 -68
- package/src/core/checks/registry.ts +0 -78
- package/src/core/checks/runner.ts +0 -1461
- package/src/core/checks/sarif.ts +0 -230
- package/src/core/checks/spec-findings.ts +0 -308
- package/src/core/checks/stateful.ts +0 -121
- package/src/core/checks/types.ts +0 -305
- package/src/core/checks/zond-extensions.ts +0 -73
- package/src/core/classifier/recommended-action.ts +0 -251
- package/src/core/context/current.ts +0 -51
- package/src/core/context/session.ts +0 -78
- package/src/core/coverage/loader.ts +0 -216
- package/src/core/coverage/reasons.ts +0 -300
- package/src/core/diagnostics/db-analysis.ts +0 -666
- package/src/core/diagnostics/failure-class.ts +0 -140
- package/src/core/diagnostics/failure-hints.ts +0 -112
- package/src/core/diagnostics/spec-pointer.ts +0 -99
- package/src/core/diagnostics/suggested-fixes.ts +0 -155
- package/src/core/exporter/case-study/index.ts +0 -270
- package/src/core/exporter/curl.ts +0 -40
- package/src/core/exporter/exporter.ts +0 -48
- package/src/core/exporter/html-report/escape.ts +0 -24
- package/src/core/exporter/html-report/index.ts +0 -479
- package/src/core/exporter/html-report/script.ts +0 -100
- package/src/core/exporter/html-report/styles.ts +0 -408
- package/src/core/generator/catalog-builder.ts +0 -179
- package/src/core/generator/chunker.ts +0 -78
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/coverage-scanner.ts +0 -87
- package/src/core/generator/data-factory.ts +0 -759
- package/src/core/generator/describe.ts +0 -302
- package/src/core/generator/endpoint-warnings.ts +0 -52
- package/src/core/generator/fixtures-builder.ts +0 -332
- package/src/core/generator/index.ts +0 -14
- package/src/core/generator/openapi-reader.ts +0 -297
- package/src/core/generator/path-param-disambig.ts +0 -140
- package/src/core/generator/resources-builder.ts +0 -898
- package/src/core/generator/schema-utils.ts +0 -112
- package/src/core/generator/serializer.ts +0 -343
- package/src/core/generator/suite-generator.ts +0 -1301
- package/src/core/generator/types.ts +0 -63
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/lint/affects.ts +0 -28
- package/src/core/lint/config.ts +0 -96
- package/src/core/lint/format.ts +0 -42
- package/src/core/lint/index.ts +0 -94
- package/src/core/lint/reporter.ts +0 -128
- package/src/core/lint/rules/consistency.ts +0 -158
- package/src/core/lint/rules/heuristics.ts +0 -97
- package/src/core/lint/rules/strictness.ts +0 -109
- package/src/core/lint/types.ts +0 -96
- package/src/core/lint/walker.ts +0 -248
- package/src/core/meta/meta-store.ts +0 -11
- package/src/core/output/README.md +0 -73
- package/src/core/output/index.ts +0 -13
- package/src/core/output/run.ts +0 -91
- package/src/core/output/types.ts +0 -122
- package/src/core/parser/dynamic-values.ts +0 -160
- package/src/core/parser/env-interpolation.ts +0 -104
- package/src/core/parser/filter.ts +0 -97
- package/src/core/parser/schema.ts +0 -359
- package/src/core/parser/types.ts +0 -117
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +0 -181
- package/src/core/probe/bootstrap.ts +0 -34
- package/src/core/probe/dry-run-envelope.ts +0 -61
- package/src/core/probe/mass-assignment/classify.ts +0 -175
- package/src/core/probe/mass-assignment/cleanup.ts +0 -52
- package/src/core/probe/mass-assignment/digest.ts +0 -114
- package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
- package/src/core/probe/mass-assignment/regression.ts +0 -141
- package/src/core/probe/mass-assignment/suspects.ts +0 -92
- package/src/core/probe/mass-assignment/types.ts +0 -135
- package/src/core/probe/mass-assignment-probe-class.ts +0 -198
- package/src/core/probe/mass-assignment-probe.ts +0 -27
- package/src/core/probe/mass-assignment-template.ts +0 -240
- package/src/core/probe/method-probe.ts +0 -164
- package/src/core/probe/method-shared.ts +0 -69
- package/src/core/probe/negative-probe.ts +0 -691
- package/src/core/probe/orphan-tracker.ts +0 -188
- package/src/core/probe/path-discovery.ts +0 -439
- package/src/core/probe/probe-harness.ts +0 -119
- package/src/core/probe/registry.ts +0 -89
- package/src/core/probe/runner.ts +0 -136
- package/src/core/probe/security/baseline.ts +0 -174
- package/src/core/probe/security/classify.ts +0 -341
- package/src/core/probe/security/cleanup.ts +0 -125
- package/src/core/probe/security/detectors.ts +0 -71
- package/src/core/probe/security/digest.ts +0 -104
- package/src/core/probe/security/orchestrator.ts +0 -398
- package/src/core/probe/security/regression.ts +0 -103
- package/src/core/probe/security/types.ts +0 -151
- package/src/core/probe/security-probe-class.ts +0 -207
- package/src/core/probe/security-probe.ts +0 -32
- package/src/core/probe/shared.ts +0 -531
- package/src/core/probe/static-probe-class.ts +0 -125
- package/src/core/probe/types.ts +0 -165
- package/src/core/probe/verdict-aggregator.ts +0 -33
- package/src/core/probe/webhooks-probe.ts +0 -282
- package/src/core/reporter/console.ts +0 -240
- package/src/core/reporter/index.ts +0 -22
- package/src/core/reporter/json.ts +0 -22
- package/src/core/reporter/junit.ts +0 -93
- package/src/core/reporter/ndjson.ts +0 -37
- package/src/core/reporter/types.ts +0 -15
- package/src/core/runner/assertions.ts +0 -383
- package/src/core/runner/async-pool.ts +0 -108
- package/src/core/runner/auth-path.ts +0 -8
- package/src/core/runner/ci-context.ts +0 -72
- package/src/core/runner/executor.ts +0 -652
- package/src/core/runner/expr-eval.ts +0 -41
- package/src/core/runner/form-encode.ts +0 -41
- package/src/core/runner/http-client.ts +0 -224
- package/src/core/runner/learn-drift.ts +0 -293
- package/src/core/runner/preflight-vars.ts +0 -153
- package/src/core/runner/progress-tracker.ts +0 -73
- package/src/core/runner/rate-limiter.ts +0 -185
- package/src/core/runner/run-kind.ts +0 -45
- package/src/core/runner/schema-validator.ts +0 -308
- package/src/core/runner/send-request.ts +0 -232
- package/src/core/runner/transforms.ts +0 -65
- package/src/core/runner/types.ts +0 -94
- package/src/core/secrets/registry.ts +0 -164
- package/src/core/secrets/secrets-file.ts +0 -115
- package/src/core/selectors/operation-filter.ts +0 -144
- package/src/core/setup-api.ts +0 -590
- package/src/core/severity/category.ts +0 -94
- package/src/core/severity/index.ts +0 -58
- package/src/core/spec/infer-schema.ts +0 -102
- package/src/core/spec/layers.ts +0 -154
- package/src/core/spec/merge-specs.ts +0 -156
- package/src/core/spec/schema-from-runs.ts +0 -117
- package/src/core/spec/schema-overlay.ts +0 -130
- package/src/core/util/ajv.ts +0 -13
- package/src/core/util/format-eta.ts +0 -21
- package/src/core/util/headers.ts +0 -9
- package/src/core/util/url.ts +0 -24
- package/src/core/utils.ts +0 -13
- package/src/core/workspace/config.ts +0 -129
- package/src/core/workspace/fixture-gap-report.ts +0 -84
- package/src/core/workspace/fixture-gaps.ts +0 -71
- package/src/core/workspace/manifest.ts +0 -283
- package/src/core/workspace/output-rotation.ts +0 -62
- package/src/core/workspace/root.ts +0 -96
- package/src/core/workspace/triage-path.ts +0 -87
- package/src/db/lint-runs.ts +0 -47
- package/src/db/migrate.ts +0 -128
- package/src/db/migrations/0001_run_kind.sql +0 -25
- package/src/db/migrations/0002_run_kind_request.sql +0 -59
- package/src/db/migrations/sql.d.ts +0 -4
- package/src/db/queries/collections.ts +0 -133
- package/src/db/queries/coverage.ts +0 -9
- package/src/db/queries/dashboard.ts +0 -59
- package/src/db/queries/results.ts +0 -216
- package/src/db/queries/runs.ts +0 -289
- package/src/db/queries/sessions.ts +0 -42
- package/src/db/queries/settings.ts +0 -28
- package/src/db/queries/types.ts +0 -172
- package/src/db/queries.ts +0 -75
- package/src/db/schema.ts +0 -298
package/src/cli/commands/db.ts
DELETED
|
@@ -1,611 +0,0 @@
|
|
|
1
|
-
import { stringify as stringifyYaml } from "yaml";
|
|
2
|
-
import { getCollections, getRuns, getRunDetail, diagnoseRun, compareRuns } from "../../core/diagnostics/db-analysis.ts";
|
|
3
|
-
import { getFilteredResults, getLatestFailingRunId, getLatestRunId, runKindStats, deleteRunsOlderThan, listRunsBySession, listRunsByCollectionFiltered, findCollectionByNameOrId } from "../../db/queries.ts";
|
|
4
|
-
import { getDb } from "../../db/schema.ts";
|
|
5
|
-
import { printError } from "../output.ts";
|
|
6
|
-
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
7
|
-
import { parseStatusFilter, compileStatusFilterToSql, type StatusMatcher } from "../status-filter.ts";
|
|
8
|
-
import { parseUnion, type UnionSpec } from "./coverage.ts";
|
|
9
|
-
import { readCurrentSession } from "../../core/context/session.ts";
|
|
10
|
-
import { readCurrentApi } from "../../core/context/current.ts";
|
|
11
|
-
|
|
12
|
-
export interface DbOptions {
|
|
13
|
-
subcommand: string;
|
|
14
|
-
positional: string[];
|
|
15
|
-
limit?: number;
|
|
16
|
-
verbose?: boolean;
|
|
17
|
-
dbPath?: string;
|
|
18
|
-
json?: boolean;
|
|
19
|
-
method?: string;
|
|
20
|
-
/** Raw `--status` argument (TASK-140). Parsed lazily so help text can show
|
|
21
|
-
* the literal user input in error messages. */
|
|
22
|
-
status?: string;
|
|
23
|
-
/** TASK-266: `db diagnose --latest` — pick the most recent run regardless
|
|
24
|
-
* of failure status (default is most recent failing run). */
|
|
25
|
-
latest?: boolean;
|
|
26
|
-
/** TASK-266: explicit run id override (`--run-id N`). Same effect as
|
|
27
|
-
* passing the id positionally; kept as a flag because the `zond-triage`
|
|
28
|
-
* skill and other agents prefer self-documenting flags. */
|
|
29
|
-
runId?: number;
|
|
30
|
-
/** ARV-266: `db prune --older-than <spec>` — cutoff for retention.
|
|
31
|
-
* Accepts `30d`, `12h`, or a bare integer (days). Omitted ⇒ per-kind
|
|
32
|
-
* documented defaults are applied. */
|
|
33
|
-
olderThan?: string;
|
|
34
|
-
/** ARV-266: restrict prune to one run_kind. */
|
|
35
|
-
kind?: string;
|
|
36
|
-
/** ARV-266: show what would be deleted without deleting. */
|
|
37
|
-
dryRun?: boolean;
|
|
38
|
-
/** ARV-338: `--report yaml` — emit the same payload as YAML instead of
|
|
39
|
-
* JSON, so agents can drop it next to suite YAMLs and diff runs as text. */
|
|
40
|
-
report?: string;
|
|
41
|
-
/** ARV-380: `db diagnose --union <spec>` — aggregate `by_recommended_action`
|
|
42
|
-
* across a run set using coverage's `--union` vocabulary
|
|
43
|
-
* (session | since:<dur> | tag:<name> | runs:<id1,id2,…>). */
|
|
44
|
-
union?: string;
|
|
45
|
-
/** ARV-380: API name for scoping since:/tag: unions to a collection. */
|
|
46
|
-
api?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** ARV-338: shared stdout emitter for run/diagnose/compare payloads. */
|
|
50
|
-
function emitPayload(payload: unknown, format?: string): void {
|
|
51
|
-
console.log(format === "yaml" ? stringifyYaml(payload).trimEnd() : JSON.stringify(payload, null, 2));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** ARV-266: per-run_kind default retention. Noise kinds (check/probe/
|
|
55
|
-
* request/fixture runs) are cheap to regenerate and dominate DB growth —
|
|
56
|
-
* drop after a week. `regular` runs are the signal a user compares against
|
|
57
|
-
* over time — kept forever unless an explicit --older-than says otherwise. */
|
|
58
|
-
const RETENTION_DEFAULT_DAYS: Record<string, number | null> = {
|
|
59
|
-
regular: null,
|
|
60
|
-
check: 7,
|
|
61
|
-
probe: 7,
|
|
62
|
-
request: 7,
|
|
63
|
-
fixture: 7,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/** ARV-266: parse `30d` / `12h` / `30` into whole days. Returns null on a
|
|
67
|
-
* malformed spec so the caller can surface a clear error. */
|
|
68
|
-
export function parseRetentionDays(spec: string): number | null {
|
|
69
|
-
const m = spec.trim().match(/^(\d+)\s*([dh]?)$/i);
|
|
70
|
-
if (!m) return null;
|
|
71
|
-
const n = Number.parseInt(m[1]!, 10);
|
|
72
|
-
if (!Number.isFinite(n)) return null;
|
|
73
|
-
if (m[2]?.toLowerCase() === "h") return n / 24;
|
|
74
|
-
return n; // bare number or `d` ⇒ days
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** ARV-266: cutoff ISO timestamp `days` before now. */
|
|
78
|
-
function cutoffIso(days: number, now: Date): string {
|
|
79
|
-
return new Date(now.getTime() - days * 86_400_000).toISOString();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** ARV-380: resolve a coverage-style `--union` spec to the concrete run-ids
|
|
83
|
-
* it selects. session/since/tag need context (active session / collection);
|
|
84
|
-
* `runs:` is self-contained. */
|
|
85
|
-
function resolveUnionRunIds(spec: UnionSpec, apiName?: string): { ids: number[] } | { error: string } {
|
|
86
|
-
switch (spec.kind) {
|
|
87
|
-
case "runIds":
|
|
88
|
-
return { ids: spec.ids };
|
|
89
|
-
case "session": {
|
|
90
|
-
const cur = readCurrentSession();
|
|
91
|
-
if (!cur) return { error: "--union session: no active session (start one with `zond session start`, or use --union runs:<ids>)." };
|
|
92
|
-
return { ids: listRunsBySession(cur.id).map((r) => (r as { id: number }).id) };
|
|
93
|
-
}
|
|
94
|
-
case "since":
|
|
95
|
-
case "tag": {
|
|
96
|
-
// Mirror the standard --api chain: explicit flag > ZOND_API(_GLOBAL) > `zond use`.
|
|
97
|
-
const resolvedApi = apiName ?? readCurrentApi();
|
|
98
|
-
if (!resolvedApi) return { error: `--union ${spec.kind}:<...> needs --api <name> (or ZOND_API / \`zond use\`) to scope runs to a collection.` };
|
|
99
|
-
const col = findCollectionByNameOrId(resolvedApi);
|
|
100
|
-
if (!col) return { error: `API '${apiName}' not found. Register it with \`zond add api\` or check the name.` };
|
|
101
|
-
const filter = spec.kind === "since"
|
|
102
|
-
? { since: new Date(Date.now() - spec.durationMs).toISOString() }
|
|
103
|
-
: { tag: spec.name };
|
|
104
|
-
return { ids: listRunsByCollectionFiltered((col as { id: number }).id, filter).map((r) => r.id) };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** ARV-380: fold `by_recommended_action` (+ summary counts) across a run set.
|
|
110
|
-
* Examples are deduped by method+path+status and capped at 5 per action,
|
|
111
|
-
* mirroring the single-run cap in `diagnoseRun`. */
|
|
112
|
-
function aggregateDiagnose(ids: number[], options: DbOptions) {
|
|
113
|
-
type Example = NonNullable<ReturnType<typeof diagnoseRun>["by_recommended_action"]>[string]["examples"][number];
|
|
114
|
-
const byAction: Record<string, { count: number; examples: Example[]; seen: Set<string> }> = {};
|
|
115
|
-
const summary = { total: 0, passed: 0, failed: 0, api_errors: 0, assertion_failures: 0, network_errors: 0 };
|
|
116
|
-
for (const id of ids) {
|
|
117
|
-
const r = diagnoseRun(id, options.verbose, options.dbPath, options.limit);
|
|
118
|
-
summary.total += r.summary.total;
|
|
119
|
-
summary.passed += r.summary.passed;
|
|
120
|
-
summary.failed += r.summary.failed;
|
|
121
|
-
summary.api_errors += r.summary.api_errors;
|
|
122
|
-
summary.assertion_failures += r.summary.assertion_failures;
|
|
123
|
-
summary.network_errors += r.summary.network_errors;
|
|
124
|
-
for (const [action, bucket] of Object.entries(r.by_recommended_action ?? {})) {
|
|
125
|
-
const agg = byAction[action] ??= { count: 0, examples: [], seen: new Set<string>() };
|
|
126
|
-
agg.count += bucket.count;
|
|
127
|
-
for (const ex of bucket.examples) {
|
|
128
|
-
const key = `${ex.method} ${ex.path} ${ex.status}`;
|
|
129
|
-
if (agg.seen.has(key) || agg.examples.length >= 5) continue;
|
|
130
|
-
agg.seen.add(key);
|
|
131
|
-
agg.examples.push(ex);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const by_recommended_action: Record<string, { count: number; examples: Example[] }> = {};
|
|
136
|
-
for (const [action, agg] of Object.entries(byAction)) {
|
|
137
|
-
by_recommended_action[action] = { count: agg.count, examples: agg.examples };
|
|
138
|
-
}
|
|
139
|
-
return { runs_diagnosed: ids.length, run_ids: ids, summary, by_recommended_action };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function dbCommand(options: DbOptions): Promise<number> {
|
|
143
|
-
const { subcommand, positional, json } = options;
|
|
144
|
-
|
|
145
|
-
if (options.report !== undefined && options.report !== "yaml" && options.report !== "json") {
|
|
146
|
-
const msg = `Invalid --report format: ${options.report}. Supported: json, yaml`;
|
|
147
|
-
if (json) printJson(jsonError(`db ${subcommand}`, [msg]));
|
|
148
|
-
else printError(msg);
|
|
149
|
-
return 2;
|
|
150
|
-
}
|
|
151
|
-
if (options.report === "yaml" && json) {
|
|
152
|
-
const msg = "--report yaml and --json are mutually exclusive — pick one output format";
|
|
153
|
-
printJson(jsonError(`db ${subcommand}`, [msg]));
|
|
154
|
-
return 2;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
switch (subcommand) {
|
|
159
|
-
case "collections": {
|
|
160
|
-
const collections = getCollections(options.dbPath);
|
|
161
|
-
if (json) {
|
|
162
|
-
printJson(jsonOk("db collections", { collections }));
|
|
163
|
-
} else {
|
|
164
|
-
if (collections.length === 0) {
|
|
165
|
-
console.log("No collections found.");
|
|
166
|
-
} else {
|
|
167
|
-
for (const c of collections) {
|
|
168
|
-
console.log(`[${(c as any).id}] ${(c as any).name} — ${(c as any).test_path ?? "no test path"}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return 0;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
case "runs": {
|
|
176
|
-
const runs = getRuns(options.limit ?? 10, options.dbPath);
|
|
177
|
-
if (json) {
|
|
178
|
-
printJson(jsonOk("db runs", { runs }));
|
|
179
|
-
} else {
|
|
180
|
-
if (runs.length === 0) {
|
|
181
|
-
console.log("No runs found.");
|
|
182
|
-
} else {
|
|
183
|
-
for (const r of runs) {
|
|
184
|
-
const run = r as any;
|
|
185
|
-
const isFail = run.failed > 0 || (run.total > 0 && run.passed === 0);
|
|
186
|
-
const status = isFail ? "FAIL" : "PASS";
|
|
187
|
-
console.log(`#${run.id} ${status} ${run.passed}/${run.total} passed (${run.started_at})`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return 0;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
case "run": {
|
|
195
|
-
const id = parseInt(positional[0] ?? "", 10);
|
|
196
|
-
if (isNaN(id)) {
|
|
197
|
-
const msg = "Missing run ID. Usage: zond db run <id>";
|
|
198
|
-
if (json) printJson(jsonError("db run", [msg]));
|
|
199
|
-
else printError(msg);
|
|
200
|
-
return 2;
|
|
201
|
-
}
|
|
202
|
-
// If filtering by method/status, show filtered results instead of full detail
|
|
203
|
-
if (options.method || options.status !== undefined) {
|
|
204
|
-
getDb(options.dbPath);
|
|
205
|
-
let statusSql: { sql: string; params: number[] } | undefined;
|
|
206
|
-
if (options.status !== undefined) {
|
|
207
|
-
let matcher: StatusMatcher;
|
|
208
|
-
try {
|
|
209
|
-
matcher = parseStatusFilter(options.status);
|
|
210
|
-
} catch (err) {
|
|
211
|
-
const msg = `Invalid --status: ${(err as Error).message}`;
|
|
212
|
-
if (json) printJson(jsonError("db run", [msg]));
|
|
213
|
-
else printError(msg);
|
|
214
|
-
return 2;
|
|
215
|
-
}
|
|
216
|
-
const compiled = compileStatusFilterToSql(matcher, "response_status");
|
|
217
|
-
if (compiled) statusSql = compiled;
|
|
218
|
-
}
|
|
219
|
-
const results = getFilteredResults(id, { method: options.method, statusSql });
|
|
220
|
-
if (json) {
|
|
221
|
-
printJson(jsonOk("db run", { run_id: id, count: results.length, results }));
|
|
222
|
-
} else {
|
|
223
|
-
emitPayload({ run_id: id, count: results.length, results }, options.report);
|
|
224
|
-
}
|
|
225
|
-
} else {
|
|
226
|
-
const detail = getRunDetail(id, options.verbose, options.dbPath);
|
|
227
|
-
if (json) {
|
|
228
|
-
printJson(jsonOk("db run", detail));
|
|
229
|
-
} else {
|
|
230
|
-
emitPayload(detail, options.report);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return 0;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
case "diagnose": {
|
|
237
|
-
getDb(options.dbPath);
|
|
238
|
-
|
|
239
|
-
// ARV-380: `--union <spec>` aggregates by_recommended_action across a
|
|
240
|
-
// run set (same vocabulary as `zond coverage --union`) instead of the
|
|
241
|
-
// single-run default, so an agent stops looping `db diagnose $id` per
|
|
242
|
-
// run and merging client-side.
|
|
243
|
-
if (options.union !== undefined) {
|
|
244
|
-
let spec: UnionSpec;
|
|
245
|
-
try {
|
|
246
|
-
spec = parseUnion(options.union);
|
|
247
|
-
} catch (err) {
|
|
248
|
-
const msg = (err as Error).message;
|
|
249
|
-
if (json) printJson(jsonError("db diagnose", [msg])); else printError(msg);
|
|
250
|
-
return 2;
|
|
251
|
-
}
|
|
252
|
-
const resolved = resolveUnionRunIds(spec, options.api);
|
|
253
|
-
if ("error" in resolved) {
|
|
254
|
-
if (json) printJson(jsonError("db diagnose", [resolved.error])); else printError(resolved.error);
|
|
255
|
-
return 2;
|
|
256
|
-
}
|
|
257
|
-
if (resolved.ids.length === 0) {
|
|
258
|
-
const msg = `No runs match --union ${options.union}.`;
|
|
259
|
-
if (json) printJson(jsonError("db diagnose", [msg])); else printError(msg);
|
|
260
|
-
return 1;
|
|
261
|
-
}
|
|
262
|
-
const agg = aggregateDiagnose(resolved.ids, options);
|
|
263
|
-
const payload = { ...agg, union: options.union };
|
|
264
|
-
if (json) printJson(jsonOk("db diagnose", payload));
|
|
265
|
-
else emitPayload(payload, options.report);
|
|
266
|
-
return 0;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// TASK-266: resolve run id with the priority
|
|
270
|
-
// explicit positional > --run-id > --latest > last failing run.
|
|
271
|
-
// The bare `zond db diagnose` is the agent-friendly default the
|
|
272
|
-
// `zond-triage` skill relies on; `--latest` exists for "show me the
|
|
273
|
-
// last run, even if it passed".
|
|
274
|
-
let id: number | null = null;
|
|
275
|
-
let resolution: "explicit" | "run-id-flag" | "latest" | "latest-failing" = "explicit";
|
|
276
|
-
const positionalRaw = positional[0];
|
|
277
|
-
if (positionalRaw !== undefined && positionalRaw !== "") {
|
|
278
|
-
const n = parseInt(positionalRaw, 10);
|
|
279
|
-
if (isNaN(n)) {
|
|
280
|
-
const msg = `Invalid run id: ${positionalRaw}. Expected a positive integer.`;
|
|
281
|
-
if (json) printJson(jsonError("db diagnose", [msg]));
|
|
282
|
-
else printError(msg);
|
|
283
|
-
return 2;
|
|
284
|
-
}
|
|
285
|
-
id = n;
|
|
286
|
-
} else if (options.runId !== undefined) {
|
|
287
|
-
id = options.runId;
|
|
288
|
-
resolution = "run-id-flag";
|
|
289
|
-
} else if (options.latest) {
|
|
290
|
-
id = getLatestRunId();
|
|
291
|
-
resolution = "latest";
|
|
292
|
-
} else {
|
|
293
|
-
id = getLatestFailingRunId();
|
|
294
|
-
resolution = "latest-failing";
|
|
295
|
-
if (id == null) {
|
|
296
|
-
// No failures — fall back to the latest run so the user still
|
|
297
|
-
// gets a useful payload, with a "no failures" hint.
|
|
298
|
-
const fallback = getLatestRunId();
|
|
299
|
-
if (fallback == null) {
|
|
300
|
-
const msg = "No runs in the database yet. Try `zond run <suite>` first.";
|
|
301
|
-
if (json) printJson(jsonError("db diagnose", [msg]));
|
|
302
|
-
else printError(msg);
|
|
303
|
-
return 1;
|
|
304
|
-
}
|
|
305
|
-
const result = diagnoseRun(fallback, options.verbose, options.dbPath, options.limit);
|
|
306
|
-
const warning = `No failing runs — diagnosing latest run #${fallback} (all passed).`;
|
|
307
|
-
if (json) {
|
|
308
|
-
printJson(jsonOk("db diagnose", { ...result, resolution: "latest-no-failures", run_id: fallback }, [warning]));
|
|
309
|
-
} else {
|
|
310
|
-
process.stderr.write(`zond: ${warning}\n`);
|
|
311
|
-
emitPayload({ ...result, resolution: "latest-no-failures", run_id: fallback }, options.report);
|
|
312
|
-
}
|
|
313
|
-
return 0;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (id == null) {
|
|
317
|
-
const msg = "Missing run ID. Usage: zond db diagnose [id] (default: last failing run)";
|
|
318
|
-
if (json) printJson(jsonError("db diagnose", [msg]));
|
|
319
|
-
else printError(msg);
|
|
320
|
-
return 2;
|
|
321
|
-
}
|
|
322
|
-
const result = diagnoseRun(id, options.verbose, options.dbPath, options.limit);
|
|
323
|
-
if (json) {
|
|
324
|
-
printJson(jsonOk("db diagnose", { ...result, resolution, run_id: id }));
|
|
325
|
-
} else {
|
|
326
|
-
// ARV-208: in human mode, embed resolution + run_id in the stdout
|
|
327
|
-
// JSON so users (and the zond-triage skill) don't need --json just
|
|
328
|
-
// to discover which run was auto-selected by `zond db diagnose --api`.
|
|
329
|
-
// The stderr hint stays as a quick visual cue when running interactively.
|
|
330
|
-
if (resolution !== "explicit") {
|
|
331
|
-
const label = resolution === "latest-failing"
|
|
332
|
-
? `last failing run #${id}`
|
|
333
|
-
: resolution === "latest"
|
|
334
|
-
? `latest run #${id}`
|
|
335
|
-
: `run #${id}`;
|
|
336
|
-
process.stderr.write(`zond: diagnosing ${label}\n`);
|
|
337
|
-
}
|
|
338
|
-
emitPayload({ ...result, resolution, run_id: id }, options.report);
|
|
339
|
-
}
|
|
340
|
-
return 0;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
case "compare": {
|
|
344
|
-
const idA = parseInt(positional[0] ?? "", 10);
|
|
345
|
-
const idB = parseInt(positional[1] ?? "", 10);
|
|
346
|
-
if (isNaN(idA) || isNaN(idB)) {
|
|
347
|
-
const msg = "Missing run IDs. Usage: zond db compare <idA> <idB>";
|
|
348
|
-
if (json) printJson(jsonError("db compare", [msg]));
|
|
349
|
-
else printError(msg);
|
|
350
|
-
return 2;
|
|
351
|
-
}
|
|
352
|
-
// ARV-360: a run compared against itself is always a degenerate green
|
|
353
|
-
// (0 regressions / 0 body_changes) and signals nothing. This bit an
|
|
354
|
-
// audit pipeline that resolved "latest run id" to the same stale run
|
|
355
|
-
// for both A and B — the meaningless diff read as "contract stable".
|
|
356
|
-
// Fail loud instead of emitting a fake-passing comparison.
|
|
357
|
-
if (idA === idB) {
|
|
358
|
-
const msg = `Cannot compare run #${idA} against itself — pass two distinct run ids (a self-compare always reports 0 changes and hides that the ids were resolved wrong).`;
|
|
359
|
-
if (json) printJson(jsonError("db compare", [msg]));
|
|
360
|
-
else printError(msg);
|
|
361
|
-
return 2;
|
|
362
|
-
}
|
|
363
|
-
const result = compareRuns(idA, idB, options.dbPath);
|
|
364
|
-
if (json) {
|
|
365
|
-
printJson(jsonOk("db compare", result));
|
|
366
|
-
} else {
|
|
367
|
-
emitPayload(result, options.report);
|
|
368
|
-
}
|
|
369
|
-
return 0;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
case "stats": {
|
|
373
|
-
// ARV-266: per-kind row counts so users see DB growth before it bites.
|
|
374
|
-
getDb(options.dbPath);
|
|
375
|
-
const stats = runKindStats();
|
|
376
|
-
const totalRuns = stats.reduce((a, s) => a + s.runs, 0);
|
|
377
|
-
const totalResults = stats.reduce((a, s) => a + s.results, 0);
|
|
378
|
-
if (json) {
|
|
379
|
-
printJson(jsonOk("db stats", { by_kind: stats, totals: { runs: totalRuns, results: totalResults } }));
|
|
380
|
-
} else if (stats.length === 0) {
|
|
381
|
-
console.log("No runs in the database yet.");
|
|
382
|
-
} else {
|
|
383
|
-
console.log(`Row counts by run_kind (${totalRuns} runs, ${totalResults} result rows):`);
|
|
384
|
-
for (const s of stats) {
|
|
385
|
-
const retention = RETENTION_DEFAULT_DAYS[s.run_kind];
|
|
386
|
-
const ret = retention == null ? "kept forever" : `default prune ${retention}d`;
|
|
387
|
-
console.log(` ${s.run_kind.padEnd(9)} ${String(s.runs).padStart(6)} runs ${String(s.results).padStart(8)} results (${ret})`);
|
|
388
|
-
}
|
|
389
|
-
console.log("\nPrune noise kinds with `zond db prune` (per-kind defaults) or `zond db prune --older-than 30d`.");
|
|
390
|
-
}
|
|
391
|
-
return 0;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
case "prune": {
|
|
395
|
-
// ARV-266: explicit, opt-in retention — never runs unless the user
|
|
396
|
-
// asks. Default (no --older-than) applies the documented per-kind
|
|
397
|
-
// cutoffs; `regular` runs are never dropped by the defaults.
|
|
398
|
-
getDb(options.dbPath);
|
|
399
|
-
const now = new Date();
|
|
400
|
-
// Build the (kind, cutoffIso) work-list.
|
|
401
|
-
let jobs: Array<{ kind?: string; cutoff: string; label: string }> = [];
|
|
402
|
-
if (options.olderThan) {
|
|
403
|
-
const days = parseRetentionDays(options.olderThan);
|
|
404
|
-
if (days == null) {
|
|
405
|
-
const msg = `Invalid --older-than "${options.olderThan}" (expected e.g. 30d, 12h, or a day count).`;
|
|
406
|
-
if (json) printJson(jsonError("db prune", [msg])); else printError(msg);
|
|
407
|
-
return 2;
|
|
408
|
-
}
|
|
409
|
-
jobs = [{ kind: options.kind, cutoff: cutoffIso(days, now), label: `${options.kind ?? "all"} < ${options.olderThan}` }];
|
|
410
|
-
} else {
|
|
411
|
-
// Per-kind defaults. A --kind filter narrows to that one kind.
|
|
412
|
-
for (const [kind, retDays] of Object.entries(RETENTION_DEFAULT_DAYS)) {
|
|
413
|
-
if (retDays == null) continue; // regular ⇒ forever
|
|
414
|
-
if (options.kind && options.kind !== kind) continue;
|
|
415
|
-
jobs.push({ kind, cutoff: cutoffIso(retDays, now), label: `${kind} < ${retDays}d` });
|
|
416
|
-
}
|
|
417
|
-
if (options.kind && RETENTION_DEFAULT_DAYS[options.kind] == null) {
|
|
418
|
-
const msg = `run_kind "${options.kind}" is retained forever by default. Pass --older-than to prune it explicitly.`;
|
|
419
|
-
if (json) printJson(jsonOk("db prune", { deleted: [], dry_run: options.dryRun === true, note: msg }, [msg]));
|
|
420
|
-
else console.log(msg);
|
|
421
|
-
return 0;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const deleted: Array<{ scope: string; runs: number; results: number }> = [];
|
|
426
|
-
for (const jobItem of jobs) {
|
|
427
|
-
if (options.dryRun) {
|
|
428
|
-
// Count-only: reuse the delete query's SELECT by inspecting stats
|
|
429
|
-
// is imprecise, so do a lightweight targeted count.
|
|
430
|
-
const { getDb: gd } = await import("../../db/schema.ts");
|
|
431
|
-
const dbh = gd();
|
|
432
|
-
const kindClause = jobItem.kind ? "AND run_kind = ?" : "";
|
|
433
|
-
const params = jobItem.kind ? [jobItem.cutoff, jobItem.kind] : [jobItem.cutoff];
|
|
434
|
-
const row = dbh.query(`SELECT COUNT(*) AS runs FROM runs WHERE started_at < ? ${kindClause}`).get(...params) as { runs: number };
|
|
435
|
-
deleted.push({ scope: jobItem.label, runs: row.runs, results: 0 });
|
|
436
|
-
} else {
|
|
437
|
-
const r = deleteRunsOlderThan(jobItem.cutoff, jobItem.kind);
|
|
438
|
-
deleted.push({ scope: jobItem.label, runs: r.runs, results: r.results });
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
const totalRuns = deleted.reduce((a, d) => a + d.runs, 0);
|
|
442
|
-
if (!options.dryRun && totalRuns > 0) {
|
|
443
|
-
// Reclaim freed pages after a large delete.
|
|
444
|
-
const { getDb: gd } = await import("../../db/schema.ts");
|
|
445
|
-
gd().exec("VACUUM");
|
|
446
|
-
}
|
|
447
|
-
if (json) {
|
|
448
|
-
printJson(jsonOk("db prune", { deleted, total_runs: totalRuns, dry_run: options.dryRun === true }));
|
|
449
|
-
} else if (totalRuns === 0) {
|
|
450
|
-
console.log(options.dryRun ? "Nothing would be pruned." : "Nothing to prune.");
|
|
451
|
-
} else {
|
|
452
|
-
console.log(`${options.dryRun ? "Would prune" : "Pruned"} ${totalRuns} run(s):`);
|
|
453
|
-
for (const d of deleted) {
|
|
454
|
-
if (d.runs > 0) console.log(` ${d.scope.padEnd(20)} ${d.runs} run(s)${options.dryRun ? "" : `, ${d.results} result row(s)`}`);
|
|
455
|
-
}
|
|
456
|
-
if (!options.dryRun) console.log("VACUUM reclaimed freed pages.");
|
|
457
|
-
}
|
|
458
|
-
return 0;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
default: {
|
|
462
|
-
const msg = `Unknown db subcommand: ${subcommand}. Available: collections, runs, run, diagnose, compare, stats, prune`;
|
|
463
|
-
if (json) printJson(jsonError("db", [msg]));
|
|
464
|
-
else printError(msg);
|
|
465
|
-
return 2;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
} catch (err) {
|
|
469
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
470
|
-
if (json) {
|
|
471
|
-
printJson(jsonError(`db ${subcommand}`, [message]));
|
|
472
|
-
} else {
|
|
473
|
-
printError(message);
|
|
474
|
-
}
|
|
475
|
-
return 2;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
import type { Command } from "commander";
|
|
480
|
-
import { globalJson } from "../resolve.ts";
|
|
481
|
-
import { parsePositiveInt } from "../argv.ts";
|
|
482
|
-
|
|
483
|
-
export function registerDb(program: Command): void {
|
|
484
|
-
const db = program.command("db").description("Query the test database");
|
|
485
|
-
|
|
486
|
-
db
|
|
487
|
-
.command("collections")
|
|
488
|
-
.description("List all API collections")
|
|
489
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
490
|
-
.action(async (opts, cmd: Command) => {
|
|
491
|
-
process.exitCode = await dbCommand({
|
|
492
|
-
subcommand: "collections",
|
|
493
|
-
positional: [],
|
|
494
|
-
dbPath: opts.db,
|
|
495
|
-
json: globalJson(cmd),
|
|
496
|
-
});
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
db
|
|
500
|
-
.command("runs")
|
|
501
|
-
.description("List recent test runs")
|
|
502
|
-
.option("--limit <N>", "Maximum number of runs to display", parsePositiveInt("--limit"))
|
|
503
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
504
|
-
.action(async (opts, cmd: Command) => {
|
|
505
|
-
process.exitCode = await dbCommand({
|
|
506
|
-
subcommand: "runs",
|
|
507
|
-
positional: [],
|
|
508
|
-
limit: opts.limit,
|
|
509
|
-
dbPath: opts.db,
|
|
510
|
-
json: globalJson(cmd),
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
db
|
|
515
|
-
.command("run <id>")
|
|
516
|
-
.description("Show run details")
|
|
517
|
-
.option("--verbose", "Show all results")
|
|
518
|
-
.option("--method <method>", "Filter by HTTP method")
|
|
519
|
-
.option(
|
|
520
|
-
"--status <expr>",
|
|
521
|
-
"Filter by HTTP status. Accepts: exact code (502), class (5xx), range (500-599), comparison (>=500, <400), or comma-separated mix (5xx,429).",
|
|
522
|
-
)
|
|
523
|
-
.option("--report <format>", "Output format: json (default) or yaml (ARV-338: agent-friendly run snapshot)")
|
|
524
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
525
|
-
.action(async (id: string, opts, cmd: Command) => {
|
|
526
|
-
process.exitCode = await dbCommand({
|
|
527
|
-
subcommand: "run",
|
|
528
|
-
positional: [id],
|
|
529
|
-
verbose: opts.verbose === true,
|
|
530
|
-
method: opts.method,
|
|
531
|
-
status: opts.status,
|
|
532
|
-
report: opts.report,
|
|
533
|
-
dbPath: opts.db,
|
|
534
|
-
json: globalJson(cmd),
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
db
|
|
539
|
-
.command("diagnose [id]")
|
|
540
|
-
.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.")
|
|
541
|
-
.option("--latest", "Diagnose the most recent run regardless of status (TASK-266)")
|
|
542
|
-
.option("--run-id <N>", "Explicit run id override (same as positional [id]; preferred form for agents)", parsePositiveInt("--run-id"))
|
|
543
|
-
.option("--union <spec>", "ARV-380: aggregate by_recommended_action across a run set instead of one run. Vocabulary matches `zond coverage --union`: session | since:<dur> | tag:<name> | runs:<id1,id2,…>. since:/tag: need --api to scope the collection.")
|
|
544
|
-
.option("--api <name>", "API/collection to scope --union since:/tag: against. Falls back to ZOND_API / .zond/current-api.")
|
|
545
|
-
.option("--limit <N>", "Examples per failure group", parsePositiveInt("--limit"))
|
|
546
|
-
.option("--verbose", "Show all examples (not grouped)")
|
|
547
|
-
.option("--report <format>", "Output format: json (default) or yaml (ARV-338: agent-friendly summary)")
|
|
548
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
549
|
-
.action(async (id: string | undefined, opts, cmd: Command) => {
|
|
550
|
-
process.exitCode = await dbCommand({
|
|
551
|
-
subcommand: "diagnose",
|
|
552
|
-
positional: id !== undefined ? [id] : [],
|
|
553
|
-
limit: opts.limit,
|
|
554
|
-
verbose: opts.verbose === true,
|
|
555
|
-
latest: opts.latest === true,
|
|
556
|
-
runId: opts.runId,
|
|
557
|
-
union: opts.union,
|
|
558
|
-
api: opts.api,
|
|
559
|
-
report: opts.report,
|
|
560
|
-
dbPath: opts.db,
|
|
561
|
-
json: globalJson(cmd),
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
db
|
|
566
|
-
.command("compare <idA> <idB>")
|
|
567
|
-
.description("Compare two runs")
|
|
568
|
-
.option("--report <format>", "Output format: json (default) or yaml (ARV-338: agent-friendly diff)")
|
|
569
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
570
|
-
.action(async (idA: string, idB: string, opts, cmd: Command) => {
|
|
571
|
-
process.exitCode = await dbCommand({
|
|
572
|
-
subcommand: "compare",
|
|
573
|
-
positional: [idA, idB],
|
|
574
|
-
report: opts.report,
|
|
575
|
-
dbPath: opts.db,
|
|
576
|
-
json: globalJson(cmd),
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
db
|
|
581
|
-
.command("stats")
|
|
582
|
-
.description("ARV-266: row counts per run_kind + default retention, so DB growth is visible before it bites")
|
|
583
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
584
|
-
.action(async (opts, cmd: Command) => {
|
|
585
|
-
process.exitCode = await dbCommand({
|
|
586
|
-
subcommand: "stats",
|
|
587
|
-
positional: [],
|
|
588
|
-
dbPath: opts.db,
|
|
589
|
-
json: globalJson(cmd),
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
db
|
|
594
|
-
.command("prune")
|
|
595
|
-
.description("ARV-266: delete old runs to bound DB growth. Default (no --older-than) applies per-kind retention: check/probe/request/fixture runs older than 7d are dropped; 'regular' runs are kept forever. VACUUMs after a delete. Opt-in — never runs unless invoked.")
|
|
596
|
-
.option("--older-than <spec>", "Uniform cutoff for all kinds (e.g. 30d, 12h, or a day count). Overrides per-kind defaults; prunes 'regular' too.")
|
|
597
|
-
.option("--kind <run_kind>", "Restrict to one run_kind: regular | check | probe | request | fixture")
|
|
598
|
-
.option("--dry-run", "Report what would be deleted without deleting")
|
|
599
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
600
|
-
.action(async (opts, cmd: Command) => {
|
|
601
|
-
process.exitCode = await dbCommand({
|
|
602
|
-
subcommand: "prune",
|
|
603
|
-
positional: [],
|
|
604
|
-
olderThan: opts.olderThan,
|
|
605
|
-
kind: opts.kind,
|
|
606
|
-
dryRun: opts.dryRun === true,
|
|
607
|
-
dbPath: opts.db,
|
|
608
|
-
json: globalJson(cmd),
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
}
|