@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.
- package/CHANGELOG.md +758 -3
- package/README.md +78 -15
- package/package.json +17 -10
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +134 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +525 -0
- package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
- package/src/cli/commands/api/annotate/overlay.ts +206 -0
- package/src/cli/commands/api/annotate/pagination.ts +60 -0
- package/src/cli/commands/api/annotate/prompts.ts +183 -0
- package/src/cli/commands/api/annotate/readback.ts +58 -0
- package/src/cli/commands/api/annotate/resources.ts +91 -0
- package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
- package/src/cli/commands/audit.ts +480 -0
- package/src/cli/commands/bootstrap.ts +710 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +348 -0
- package/src/cli/commands/checks.ts +756 -0
- package/src/cli/commands/ci-init.ts +55 -6
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +262 -0
- package/src/cli/commands/completions.ts +192 -0
- package/src/cli/commands/coverage.ts +605 -132
- package/src/cli/commands/db.ts +180 -8
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1236 -0
- package/src/cli/commands/doctor.ts +607 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +420 -47
- package/src/cli/commands/init/agents-md.ts +61 -0
- package/src/cli/commands/init/bootstrap.ts +108 -0
- package/src/cli/commands/init/index.ts +244 -0
- package/src/cli/commands/init/skills.ts +98 -0
- package/src/cli/commands/init/templates/agents.md +77 -0
- package/src/cli/commands/init/templates/markdown.d.ts +4 -0
- package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
- package/src/cli/commands/init/templates/skills/zond.md +651 -0
- package/src/cli/commands/init/templates/zond-config.yml +14 -0
- package/src/cli/commands/prepare-fixtures.ts +135 -0
- package/src/cli/commands/probe/mass-assignment.ts +503 -0
- package/src/cli/commands/probe/security.ts +454 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +161 -0
- package/src/cli/commands/probe.ts +459 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +169 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +318 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +379 -4
- package/src/cli/commands/run.ts +911 -33
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +74 -0
- package/src/cli/index.ts +36 -607
- package/src/cli/json-envelope.ts +112 -3
- package/src/cli/json-schemas.ts +263 -0
- package/src/cli/program.ts +218 -0
- package/src/cli/resolve.ts +105 -0
- package/src/cli/status-filter.ts +124 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +8 -0
- package/src/core/anti-fp/bootstrap.ts +34 -0
- package/src/core/anti-fp/index.ts +33 -0
- package/src/core/anti-fp/registry.ts +44 -0
- package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
- package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
- package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
- package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
- package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
- package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
- package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
- package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
- package/src/core/anti-fp/types.ts +68 -0
- package/src/core/checks/checks/_crud-helpers.ts +133 -0
- package/src/core/checks/checks/_negative_mutator.ts +133 -0
- package/src/core/checks/checks/_readback-helpers.ts +133 -0
- package/src/core/checks/checks/content_type_conformance.ts +39 -0
- package/src/core/checks/checks/cross_call_references.ts +134 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +246 -0
- package/src/core/checks/checks/ignored_auth.ts +211 -0
- package/src/core/checks/checks/index.ts +65 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +45 -0
- package/src/core/checks/checks/not_a_server_error.ts +27 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
- package/src/core/checks/checks/pagination_invariants.ts +238 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
- package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
- package/src/core/checks/checks/response_headers_conformance.ts +74 -0
- package/src/core/checks/checks/response_schema_conformance.ts +30 -0
- package/src/core/checks/checks/status_code_conformance.ts +61 -0
- package/src/core/checks/checks/unsupported_method.ts +63 -0
- package/src/core/checks/checks/use_after_free.ts +78 -0
- package/src/core/checks/index.ts +30 -0
- package/src/core/checks/mode.ts +79 -0
- package/src/core/checks/recommended-action.ts +64 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +874 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +189 -0
- package/src/core/classifier/recommended-action.ts +222 -0
- package/src/core/context/current.ts +51 -0
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +185 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +161 -12
- package/src/core/diagnostics/failure-class.ts +120 -0
- package/src/core/diagnostics/failure-hints.ts +212 -9
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +156 -0
- package/src/core/exporter/case-study/index.ts +270 -0
- package/src/core/exporter/curl.ts +40 -0
- package/src/core/exporter/exporter.ts +48 -0
- package/src/core/exporter/html-report/escape.ts +24 -0
- package/src/core/exporter/html-report/index.ts +479 -0
- package/src/core/exporter/html-report/script.ts +100 -0
- package/src/core/exporter/html-report/styles.ts +408 -0
- package/src/core/generator/chunker.ts +53 -15
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/create-body.ts +89 -0
- package/src/core/generator/data-factory.ts +490 -33
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +325 -0
- package/src/core/generator/index.ts +7 -5
- package/src/core/generator/openapi-reader.ts +55 -3
- package/src/core/generator/path-param-disambig.ts +114 -0
- package/src/core/generator/resources-builder.ts +648 -0
- package/src/core/generator/schema-utils.ts +11 -3
- package/src/core/generator/serializer.ts +114 -15
- package/src/core/generator/suite-generator.ts +484 -77
- package/src/core/generator/types.ts +8 -0
- package/src/core/identity/identity-file.ts +129 -0
- package/src/core/lint/affects.ts +28 -0
- package/src/core/lint/config.ts +96 -0
- package/src/core/lint/format.ts +42 -0
- package/src/core/lint/index.ts +94 -0
- package/src/core/lint/reporter.ts +128 -0
- package/src/core/lint/rules/consistency.ts +158 -0
- package/src/core/lint/rules/heuristics.ts +97 -0
- package/src/core/lint/rules/strictness.ts +109 -0
- package/src/core/lint/types.ts +96 -0
- package/src/core/lint/walker.ts +248 -0
- package/src/core/meta/meta-store.ts +6 -73
- package/src/core/output/README.md +91 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +126 -0
- package/src/core/output/types.ts +129 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +132 -5
- package/src/core/parser/types.ts +29 -2
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +108 -13
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +57 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +1122 -0
- package/src/core/probe/mass-assignment-template.ts +212 -0
- package/src/core/probe/method-probe.ts +164 -0
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +691 -0
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +440 -0
- package/src/core/probe/probe-harness.ts +120 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security-probe-class.ts +201 -0
- package/src/core/probe/security-probe.ts +1453 -0
- package/src/core/probe/shared.ts +505 -0
- package/src/core/probe/static-probe-class.ts +125 -0
- package/src/core/probe/types.ts +165 -0
- package/src/core/probe/verdict-aggregator.ts +33 -0
- package/src/core/probe/webhooks-probe.ts +284 -0
- package/src/core/reporter/console.ts +69 -4
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +15 -2
- package/src/core/reporter/junit.ts +27 -12
- package/src/core/reporter/ndjson.ts +37 -0
- package/src/core/reporter/types.ts +3 -0
- package/src/core/runner/assertions.ts +62 -2
- package/src/core/runner/async-pool.ts +108 -0
- package/src/core/runner/auth-path.ts +8 -0
- package/src/core/runner/ci-context.ts +72 -0
- package/src/core/runner/executor.ts +391 -52
- package/src/core/runner/form-encode.ts +51 -0
- package/src/core/runner/http-client.ts +115 -7
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +149 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +203 -0
- package/src/core/runner/run-kind.ts +39 -0
- package/src/core/runner/schema-validator.ts +312 -0
- package/src/core/runner/send-request.ts +153 -20
- package/src/core/runner/types.ts +38 -0
- package/src/core/secrets/registry.ts +164 -0
- package/src/core/secrets/secrets-file.ts +115 -0
- package/src/core/selectors/operation-filter.ts +144 -0
- package/src/core/setup-api.ts +419 -17
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +121 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/root.ts +94 -0
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +126 -0
- package/src/db/migrations/0001_run_kind.sql +25 -0
- package/src/db/migrations/sql.d.ts +4 -0
- package/src/db/queries/collections.ts +133 -0
- package/src/db/queries/coverage.ts +9 -0
- package/src/db/queries/dashboard.ts +59 -0
- package/src/db/queries/results.ts +128 -0
- package/src/db/queries/runs.ts +235 -0
- package/src/db/queries/sessions.ts +42 -0
- package/src/db/queries/settings.ts +28 -0
- package/src/db/queries/types.ts +172 -0
- package/src/db/queries.ts +72 -802
- package/src/db/schema.ts +179 -48
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init.ts +0 -57
- package/src/cli/commands/serve.ts +0 -81
- package/src/cli/commands/sync.ts +0 -269
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -21
- package/src/core/parser/index.ts +0 -21
- package/src/core/runner/execute-run.ts +0 -132
- package/src/core/runner/index.ts +0 -12
- package/src/core/sync/spec-differ.ts +0 -38
- package/src/web/data/collection-state.ts +0 -362
- package/src/web/routes/api.ts +0 -314
- package/src/web/routes/dashboard.ts +0 -350
- package/src/web/routes/runs.ts +0 -64
- package/src/web/schemas.ts +0 -121
- package/src/web/server.ts +0 -134
- package/src/web/static/htmx.min.cjs +0 -1
- package/src/web/static/style.css +0 -1148
- package/src/web/views/endpoints-tab.ts +0 -174
- package/src/web/views/explorer-tab.ts +0 -402
- package/src/web/views/health-strip.ts +0 -92
- package/src/web/views/layout.ts +0 -48
- package/src/web/views/results.ts +0 -210
- package/src/web/views/runs-tab.ts +0 -126
- package/src/web/views/suites-tab.ts +0 -181
package/src/cli/json-envelope.ts
CHANGED
|
@@ -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:
|
|
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(
|
|
14
|
-
|
|
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
|
+
}
|