@kirrosh/zond 0.22.0 → 0.26.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 +811 -0
- package/README.md +59 -6
- package/package.json +9 -7
- package/src/CLAUDE.md +112 -0
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +146 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +880 -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 +64 -0
- package/src/cli/commands/api/annotate/prompts.ts +220 -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 +786 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +361 -0
- package/src/cli/commands/checks.ts +1072 -0
- package/src/cli/commands/ci-init.ts +43 -0
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +236 -0
- package/src/cli/commands/completions.ts +16 -0
- package/src/cli/commands/coverage.ts +823 -132
- package/src/cli/commands/db.ts +486 -12
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1356 -0
- package/src/cli/commands/doctor.ts +661 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +438 -47
- package/src/cli/commands/init/bootstrap.ts +34 -2
- package/src/cli/commands/{init.ts → init/index.ts} +99 -5
- package/src/cli/commands/init/skills.ts +99 -3
- package/src/cli/commands/init/templates/agents.md +77 -64
- package/src/cli/commands/init/templates/skills/warm-up-target.md +122 -0
- package/src/cli/commands/init/templates/skills/zond-checks.md +621 -0
- package/src/cli/commands/init/templates/skills/zond-seed.md +114 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +272 -0
- package/src/cli/commands/init/templates/skills/zond.md +802 -125
- package/src/cli/commands/init/templates/zond-config.yml +8 -9
- package/src/cli/commands/prepare-fixtures.ts +97 -0
- package/src/cli/commands/probe/_seed-bodies.ts +52 -0
- package/src/cli/commands/probe/mass-assignment.ts +594 -0
- package/src/cli/commands/probe/security.ts +537 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +163 -0
- package/src/cli/commands/probe.ts +535 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +227 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +310 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +495 -4
- package/src/cli/commands/run.ts +870 -53
- package/src/cli/commands/schema-from-runs.ts +128 -0
- package/src/cli/commands/secrets.ts +133 -0
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +18 -1
- package/src/cli/index.ts +20 -3
- package/src/cli/json-envelope.ts +92 -3
- package/src/cli/json-schemas.ts +314 -0
- package/src/cli/output.ts +17 -1
- package/src/cli/program.ts +199 -635
- package/src/cli/resolve.ts +105 -0
- package/src/cli/safe-live.ts +24 -0
- package/src/cli/status-filter.ts +114 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +5 -0
- package/src/core/audit/persist.ts +183 -0
- package/src/core/checks/budget.ts +59 -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 +147 -0
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +219 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +242 -0
- package/src/core/checks/checks/ignored_auth.ts +254 -0
- package/src/core/checks/checks/index.ts +68 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +416 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +148 -0
- package/src/core/checks/checks/not_a_server_error.ts +35 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +160 -0
- package/src/core/checks/checks/pagination_invariants.ts +419 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +33 -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 +132 -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 +82 -0
- package/src/core/checks/recommended-action.ts +68 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +1461 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/spec-findings.ts +308 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +305 -0
- package/src/core/checks/zond-extensions.ts +73 -0
- package/src/core/classifier/recommended-action.ts +251 -0
- package/src/core/context/current.ts +22 -6
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +216 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +293 -59
- package/src/core/diagnostics/failure-class.ts +140 -0
- package/src/core/diagnostics/failure-hints.ts +88 -89
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +155 -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 +38 -19
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/data-factory.ts +586 -22
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +332 -0
- package/src/core/generator/index.ts +5 -5
- package/src/core/generator/openapi-reader.ts +135 -7
- package/src/core/generator/path-param-disambig.ts +140 -0
- package/src/core/generator/resources-builder.ts +898 -0
- package/src/core/generator/schema-utils.ts +33 -3
- package/src/core/generator/serializer.ts +103 -13
- package/src/core/generator/suite-generator.ts +583 -122
- package/src/core/generator/types.ts +14 -0
- package/src/core/identity/identity-file.ts +0 -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 +73 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +91 -0
- package/src/core/output/types.ts +122 -0
- package/src/core/parser/dynamic-values.ts +160 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +129 -4
- package/src/core/parser/types.ts +19 -1
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +58 -12
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +61 -0
- package/src/core/probe/mass-assignment/classify.ts +175 -0
- package/src/core/probe/mass-assignment/cleanup.ts +52 -0
- package/src/core/probe/mass-assignment/digest.ts +114 -0
- package/src/core/probe/mass-assignment/orchestrator.ts +459 -0
- package/src/core/probe/mass-assignment/regression.ts +141 -0
- package/src/core/probe/mass-assignment/suspects.ts +92 -0
- package/src/core/probe/mass-assignment/types.ts +135 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +27 -0
- package/src/core/probe/mass-assignment-template.ts +240 -0
- package/src/core/probe/method-probe.ts +43 -76
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +183 -149
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +439 -0
- package/src/core/probe/probe-harness.ts +119 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security/baseline.ts +174 -0
- package/src/core/probe/security/classify.ts +341 -0
- package/src/core/probe/security/cleanup.ts +125 -0
- package/src/core/probe/security/detectors.ts +71 -0
- package/src/core/probe/security/digest.ts +104 -0
- package/src/core/probe/security/orchestrator.ts +398 -0
- package/src/core/probe/security/regression.ts +103 -0
- package/src/core/probe/security/types.ts +151 -0
- package/src/core/probe/security-probe-class.ts +207 -0
- package/src/core/probe/security-probe.ts +32 -0
- package/src/core/probe/shared.ts +531 -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 +282 -0
- package/src/core/reporter/console.ts +41 -2
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +11 -1
- 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 +59 -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 +265 -36
- package/src/core/runner/form-encode.ts +41 -0
- package/src/core/runner/http-client.ts +112 -2
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +153 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +87 -33
- package/src/core/runner/run-kind.ts +45 -0
- package/src/core/runner/schema-validator.ts +308 -0
- package/src/core/runner/send-request.ts +158 -20
- package/src/core/runner/types.ts +44 -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 +457 -20
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +58 -0
- package/src/core/spec/infer-schema.ts +102 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/spec/merge-specs.ts +156 -0
- package/src/core/spec/schema-from-runs.ts +117 -0
- package/src/core/spec/schema-overlay.ts +130 -0
- package/src/core/util/ajv.ts +13 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/util/headers.ts +9 -0
- package/src/core/util/url.ts +24 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/fixture-gap-report.ts +84 -0
- package/src/core/workspace/fixture-gaps.ts +71 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/root.ts +13 -11
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +128 -0
- package/src/db/migrations/0001_run_kind.sql +25 -0
- package/src/db/migrations/0002_run_kind_request.sql +59 -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 +216 -0
- package/src/db/queries/runs.ts +289 -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 +75 -802
- package/src/db/schema.ts +178 -50
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
- package/src/cli/commands/probe-methods.ts +0 -108
- package/src/cli/commands/probe-validation.ts +0 -124
- package/src/cli/commands/serve.ts +0 -114
- package/src/cli/commands/sync.ts +0 -268
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/diagnostics/render-md.ts +0 -112
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -19
- 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/README.md
CHANGED
|
@@ -15,18 +15,51 @@ curl -fsSL https://raw.githubusercontent.com/kirrosh/zond/master/install.sh | sh
|
|
|
15
15
|
iwr https://raw.githubusercontent.com/kirrosh/zond/master/install.ps1 | iex # Windows
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Bootstrap a workspace
|
|
18
|
+
Bootstrap a workspace, register your first API, then fill its fixtures:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
zond init
|
|
21
|
+
zond init # bootstrap workspace (no fixture changes)
|
|
22
|
+
zond add api my-api --spec ./openapi.json # register: copies spec.json + emits manifest
|
|
23
|
+
zond doctor --api my-api --missing-only # gap report: which vars are UNSET
|
|
24
|
+
zond prepare-fixtures --api my-api # gap report: verify fixtures + which FK vars need a value
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
`
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
`prepare-fixtures` **reports** gaps — it never harvests a value (which
|
|
28
|
+
record/field fills a path slot is your call). Fill each gap with the
|
|
29
|
+
manual helpers (ARV-195):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
zond fixtures add --api my-api customer_id=cus_123 --validate --apply
|
|
33
|
+
pbpaste | zond fixtures import --api my-api --from-curl --apply # paste a curl from devtools
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`zond init` writes a self-contained [`AGENTS.md`](AGENTS.md) and Claude Code
|
|
37
|
+
skills — agents read it and use the CLI directly (`zond run`,
|
|
38
|
+
`zond probe static`, `zond db diagnose`, …). No daemon, no transport, no
|
|
39
|
+
extra configuration. `init` is workspace-only — it never touches
|
|
40
|
+
`.env.yaml`; the fixture loop above is the canonical path.
|
|
41
|
+
|
|
42
|
+
Each registered API gets four files in `apis/<name>/`:
|
|
43
|
+
|
|
44
|
+
- `spec.json` — dereferenced OpenAPI snapshot (canonical machine source).
|
|
45
|
+
- `.api-catalog.yaml` — endpoint index for agents (cheap to read).
|
|
46
|
+
- `.api-resources.yaml` — CRUD chains, FK dependencies, ETag/soft-delete flags.
|
|
47
|
+
- `.api-fixtures.yaml` — **manifest** of required `{{vars}}` (read-only, auto-generated).
|
|
48
|
+
|
|
49
|
+
Plus a sibling `.env.yaml` that **you** fill with the **values** for those
|
|
50
|
+
vars (`zond prepare-fixtures` only reports which are missing). The
|
|
51
|
+
manifest/values split is strict — see the
|
|
52
|
+
[workspace contract](AGENTS.md#workspace-contract) for details.
|
|
53
|
+
|
|
54
|
+
Run `zond refresh-api <name> [--spec <new-source>]` to re-snapshot when the
|
|
55
|
+
upstream spec changes.
|
|
27
56
|
|
|
28
57
|
Then say to your agent: _"Safely cover the API from openapi.json with tests."_
|
|
29
58
|
|
|
59
|
+
Want the whole pipeline at once? `zond audit --api my-api` runs
|
|
60
|
+
prepare-fixtures → generate → probes → run → coverage → HTML report in a
|
|
61
|
+
single shot.
|
|
62
|
+
|
|
30
63
|
<details>
|
|
31
64
|
<summary>Other installation methods (npx)</summary>
|
|
32
65
|
|
|
@@ -58,6 +91,11 @@ Claude Code can write pytest from scratch — but it takes 30-60 minutes per flo
|
|
|
58
91
|
| **Spec-Grounded** | Tests are derived from your OpenAPI schema, not invented from scratch. The spec is the source of truth. |
|
|
59
92
|
| **Full Visibility** | Every run is stored in SQLite. Compare runs, track regressions, see exactly what the server returned. |
|
|
60
93
|
| **Coverage Tracking** | See which endpoints are tested, which aren't, and what broke since last run. |
|
|
94
|
+
| **Schema Validation** | `--validate-schema` checks every JSON response against the OpenAPI schema (types, required, enum, format, `$ref`) — catches contract drift the YAML expectations miss. |
|
|
95
|
+
| **Spec Linting** | `zond check spec` static-analyses the OpenAPI document for internal-consistency bugs (e.g. example violates `format: date-time`) and strictness gaps (path-params without `format`, integer params without min/max) — surfaces issues before any HTTP request. |
|
|
96
|
+
| **Depth Checks (m-15)** | `zond checks run` runs a schemathesis-style catalog of conformance + security probes (`status_code_conformance`, `negative_data_rejection`, `ignored_auth`, `use_after_free`, …) — boundary-value coverage, broken-auth detection, soft-deleted resource leaks. Every finding ships with a `recommended_action` enum so the agent triages without parsing messages. |
|
|
97
|
+
| **SARIF for Code Scanning** | `--report sarif` emits SARIF v2.1.0 with stable `partialFingerprints` — drop-in for `github/codeql-action/upload-sarif@v3` so depth-checks findings show up in GitHub's Security tab. |
|
|
98
|
+
| **Concurrent Workers** | `--workers auto` parallelizes runs at the operation level (bounded async-pool, no threading) — runs that took minutes finish in seconds. Pair with `--rate-limit` to stay within an API's RPS budget. |
|
|
61
99
|
| **CI-Ready** | One command generates GitHub Actions or GitLab CI workflow. Tests in YAML, in git, with code review. |
|
|
62
100
|
|
|
63
101
|
## Try It
|
|
@@ -69,6 +107,21 @@ Claude Code can write pytest from scratch — but it takes 30-60 minutes per flo
|
|
|
69
107
|
"Set up CI for API tests"
|
|
70
108
|
```
|
|
71
109
|
|
|
110
|
+
## Upgrading
|
|
111
|
+
|
|
112
|
+
`zond update` was removed in favour of system package managers:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# macOS / Linux — re-run the installer
|
|
116
|
+
curl -fsSL https://raw.githubusercontent.com/kirrosh/zond/master/install.sh | sh
|
|
117
|
+
|
|
118
|
+
# npm
|
|
119
|
+
npm install -g @kirrosh/zond@latest
|
|
120
|
+
|
|
121
|
+
# bun
|
|
122
|
+
bun install -g @kirrosh/zond@latest
|
|
123
|
+
```
|
|
124
|
+
|
|
72
125
|
## Shell completions
|
|
73
126
|
|
|
74
127
|
```bash
|
|
@@ -82,7 +135,7 @@ zond completions fish > ~/.config/fish/completions/zond.fish
|
|
|
82
135
|
- [ZOND.md](ZOND.md) — full CLI reference
|
|
83
136
|
- [docs/quickstart.md](docs/quickstart.md) — step-by-step quickstart (RU)
|
|
84
137
|
- [docs/ci.md](docs/ci.md) — CI/CD integration
|
|
85
|
-
- [backlog/](backlog/) — project tasks (powered by [Backlog.md](https://backlog.md)
|
|
138
|
+
- [backlog/](backlog/) — project tasks (powered by [Backlog.md](https://backlog.md))
|
|
86
139
|
|
|
87
140
|
## License
|
|
88
141
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirrosh/zond",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "API testing platform — define tests in YAML, run from CLI
|
|
3
|
+
"version": "0.26.0",
|
|
4
|
+
"description": "API testing platform — define tests in YAML, run from CLI, generate from OpenAPI specs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "index.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -28,15 +28,17 @@
|
|
|
28
28
|
"backlog": "bunx backlog",
|
|
29
29
|
"board": "bunx backlog board",
|
|
30
30
|
"test": "bun run test:unit && bun run test:mocked",
|
|
31
|
-
"test:unit": "bun test tests/
|
|
31
|
+
"test:unit": "bun test tests/",
|
|
32
32
|
"test:mocked": "bun run scripts/run-mocked-tests.ts",
|
|
33
33
|
"check": "tsc --noEmit --project tsconfig.json",
|
|
34
|
+
"schemas": "bun run scripts/emit-json-schemas.ts",
|
|
35
|
+
"schemas:check": "bun run scripts/emit-json-schemas.ts --check",
|
|
34
36
|
"lint:dead": "knip --reporter compact",
|
|
35
|
-
"build": "bun build --compile src/cli/index.ts --outfile zond"
|
|
36
|
-
"bench:api": "bun benchmarks/api/server.ts"
|
|
37
|
+
"build": "bun build --compile src/cli/index.ts --outfile dist/zond && bun run scripts/codesign-darwin.ts ./dist/zond"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@types/bun": "latest",
|
|
41
|
+
"ajv-draft-04": "^1.0.0",
|
|
40
42
|
"backlog.md": "^1.44.0",
|
|
41
43
|
"knip": "^6.7.0"
|
|
42
44
|
},
|
|
@@ -47,10 +49,10 @@
|
|
|
47
49
|
"typescript": "^5"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
|
-
"@hono/zod-openapi": "^1.2.2",
|
|
51
52
|
"@readme/openapi-parser": "^5.5.0",
|
|
53
|
+
"ajv": "^8.20.0",
|
|
54
|
+
"ajv-formats": "^3.0.1",
|
|
52
55
|
"commander": "^14.0.0",
|
|
53
|
-
"hono": "^4.12.2",
|
|
54
56
|
"openapi-types": "^12.1.3",
|
|
55
57
|
"yaml": "^2.8.3",
|
|
56
58
|
"zod": "^4.3.6"
|
package/src/CLAUDE.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# src/ — architecture map
|
|
2
|
+
|
|
3
|
+
Этот файл — точка входа в **код** zond. Workspace-контракт (`.api-fixtures.yaml` vs `.env.yaml`) — в [`../AGENTS.md`](../AGENTS.md), пользовательский CLI-референс — в [`../ZOND.md`](../ZOND.md).
|
|
4
|
+
|
|
5
|
+
zond — API hygiene scanner. **Dumb-tool**: умеет дёргать API, собирать evidence, проверять конформность спеки. Не зовёт LLM, не делает решения — это работа агента, который скармливает zond'у YAML и читает обратно отчёты.
|
|
6
|
+
|
|
7
|
+
### Litmus-тест: что кладём в zond, а что оставляем агенту
|
|
8
|
+
|
|
9
|
+
m-24 срезал эвристический слой (discovery/seed/cascade, severity-калибраторы, anti-FP-гейты, annotate-auto). Чтобы он не наползал обратно по одному «разумному» фиксу за раз — **каждое изменение zond проходит один тест: детерминировано ли оно?** Один и тот же вход → один и тот же выход, без догадок про намерение / вину / серьёзность / «баг ли это на самом деле».
|
|
10
|
+
|
|
11
|
+
- ✅ **В zond** (детерминированное, быстрое, скучное): слать запросы, валидировать схемы, считать диффы, эмитить evidence + closed-enum хинты, **корректно ограничивать scope чека** (какие case-kinds / методы он оценивает), плюс plumbing/скорость/артефакты. zond — удобный работающий быстрый инструмент.
|
|
12
|
+
- ❌ **Агенту** (суждение): severity, приоритет, атрибуция вины (spec vs backend), «это false-positive?», «это эксплуатируемо?», выдумывание фикстур, многопроходный discovery. Сложное решает умный агент, читая сырой evidence.
|
|
13
|
+
|
|
14
|
+
Практическое следствие для «zond что-то пропустил/зашумил» из аудита: **чини scope чека детерминированно** (напр. ARV-340 — чек смотрел не те case-kinds) — но **не добавляй suppression/down-rank «это FP»** (это ровно тот anti-FP-гейт, что убрал ARV-337; FP отсекает агент в триаже). `recommended_action` — самая размытая из выживших поверхностей: это тонкий routing-хинт, держи его тупым, не выращивай в мини-классификатор.
|
|
15
|
+
|
|
16
|
+
**Evidence-over-inference (ARV-376):** статические инференсеры по форме URL (какой `/list` владеет `{id}`, какой ресурс за `{code}`) имеют бесконечный хвост идиосинкразий спеков. **Не** закрывай хвост растущими хардкод-списками маркеров/глаголов (`LIST_VERB_SUFFIXES`, `ACCESSOR_MARKER_SEGS`, версии) — это ровно тот creep «по одному разумному за раз». Правило: **новый маркер/глагол добавляется только под реальный спек, который его упражняет**, никогда «на всякий случай» (так был убран спекулятивный `bycode`). А когда вывод неуверенный — не тупик (`miss-no-list`), а **`item.candidates`**: правдоподобные эндпоинты, ранжированные по структурной близости, БЕЗ выбора значения. Граф — детерминизм zond'а; выбор — суждение агента. Выравнивание двух слоёв, где один уже что-то делает (напр. version-strip, ARV-381), — это консистентность, не новое знание, и ок.
|
|
17
|
+
|
|
18
|
+
## Top-level layout
|
|
19
|
+
|
|
20
|
+
| Каталог | Назначение |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `cli/` | CLI surface: парсинг argv, регистрация команд, форматирование вывода. |
|
|
23
|
+
| `core/` | Бизнес-логика: probes, checks, generators, runner, reporters. **Никакой зависимости от commander/process.argv** — это переиспользуемое ядро. |
|
|
24
|
+
| `db/` | SQLite-слой: schema, migrations, queries для истории runs/results. |
|
|
25
|
+
|
|
26
|
+
**Правило**: `cli/commands/<cmd>.ts` парсит флаги и зовёт функции из `core/`. Бизнес-логика в `cli/` — code-smell (см. ARV-257 про `bootstrap.ts`/`discover.ts`, которые лежат в `cli/commands/` но не регистрируют команды).
|
|
27
|
+
|
|
28
|
+
## cli/
|
|
29
|
+
|
|
30
|
+
| Файл | Что |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `index.ts` | Entry point бинаря. |
|
|
33
|
+
| `program.ts` | Commander root: регистрирует все top-level команды. |
|
|
34
|
+
| `argv.ts`, `resolve.ts` | Аргумент-парсинг и `--api` chain resolution (per-command > global > `ZOND_API` > `.zond/current-api`). |
|
|
35
|
+
| `runtime.ts` | Глобальный runtime context (`api`, workspace paths, http auditor). |
|
|
36
|
+
| `output.ts`, `json-envelope.ts`, `json-schemas.ts` | Forматирование результатов: текстовый/JSON envelope + Ajv-валидация envelope-схем. |
|
|
37
|
+
| `status-filter.ts` | Общий парсер `--status` фильтров. |
|
|
38
|
+
| `commands/` | Один файл на команду; `commands/init/`, `commands/api/`, `commands/probe/` — кластеры с subcommands. |
|
|
39
|
+
|
|
40
|
+
Контракт команды: парсит флаги, валидирует через зависимости из `core/`, эмиттит результат через `output.ts` (envelope) или streaming-репортер.
|
|
41
|
+
|
|
42
|
+
## core/ — подсистемы
|
|
43
|
+
|
|
44
|
+
| Подкаталог | Роль |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `parser/`, `spec/` | Парсинг OpenAPI, dereferenced spec.json, extraction схем. |
|
|
47
|
+
| `generator/` | Синтез тестовых YAML-сьютов из spec: `suite-generator.ts`, `data-factory.ts`, `resources-builder.ts`. |
|
|
48
|
+
| `runner/` | HTTP-исполнение: `executor.ts`, retry, assertions, schema validation. |
|
|
49
|
+
| `checks/` | Schemathesis-style depth checks (status_code_conformance, response_schema_conformance, idempotency, …). Орк — `checks/runner.ts`. Один файл = один check class. |
|
|
50
|
+
| `probe/` | Активные security/mass-assignment probes. Сейчас два монолита (`security-probe.ts`, `mass-assignment-probe.ts`) — кандидаты на split (ARV-295, ARV-296). |
|
|
51
|
+
| `lint/` | Static spec-lint (`check spec`). Не путать с `checks/` — это разные команды и разные envelope'ы. |
|
|
52
|
+
| `diagnostics/` | Triage финдингов: классификация ошибок, hints, `recommended_action` mapping. |
|
|
53
|
+
| `severity/` | Per-finding severity calibration (ARV-283–288 актуализирует). |
|
|
54
|
+
| `anti-fp/` | Anti-false-positive guard'ы; registry-pattern частично (см. ARV-259). |
|
|
55
|
+
| `coverage/` | Покрытие endpoint'ов: test-runs + audit-runs, dual-metric. |
|
|
56
|
+
| `reporter/` | Форматирование `Run`/`Check`/`Probe` результатов в текст/JSON/NDJSON/SARIF/JUnit/HTML. |
|
|
57
|
+
| `exporter/` | HTML-отчёты, case studies. |
|
|
58
|
+
| `audit/` | Высокоуровневая `audit` команда — wraps checks + probe + report для CI-smoke. |
|
|
59
|
+
| `workspace/` | Layout API-папки: `apis/<name>/{spec,catalog,resources,fixtures,env,secrets}`. |
|
|
60
|
+
| `context/`, `identity/`, `secrets/` | Загрузка контекста запуска, identity tokens, секреты. |
|
|
61
|
+
| `util/`, `utils.ts` | Общие хелперы. URL/headers/schema-валидация частично дублируются с `probe/shared.ts` — кандидат на консолидацию (ARV-297). |
|
|
62
|
+
| `selectors/`, `meta/`, `classifier/` | Endpoint-classification и meta-аттрибуты для дискавери и группировки. |
|
|
63
|
+
| `setup-api.ts` | Регистрация нового API в workspace (used by `add api`/`refresh-api`). |
|
|
64
|
+
|
|
65
|
+
## db/
|
|
66
|
+
|
|
67
|
+
SQLite на bun. `migrations/` — versioned migrations, `schema.ts` — текущий schema, `queries/` — типизированные SQL. Используется reporter'ами и `coverage` для исторических runs. Retention (ARV-266): `zond db stats` — счётчики строк per `run_kind`; `zond db prune` — opt-in удаление (per-kind defaults: check/probe/request/fixture старше 7d, `regular` — forever; `--older-than 30d` для uniform-cutoff), VACUUM после delete.
|
|
68
|
+
|
|
69
|
+
## Data-flow по фазам
|
|
70
|
+
|
|
71
|
+
zond работает по 5 фазам — это ментальная модель CLI и skills:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Setup ──► Generate ──► Run ──► Analyze ──► Report
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Фаза | Команды | Что происходит | Артефакты |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| **Setup** | `init`, `add api`, `refresh-api`, `use`, `doctor`, `prepare-fixtures` | Регистрируем API, тянем spec, заполняем `.env.yaml`. | `apis/<name>/{spec.json, .api-catalog.yaml, .api-resources.yaml, .api-fixtures.yaml, .env.yaml}` |
|
|
80
|
+
| **Generate** | `generate`, `api annotate`, `prepare-fixtures` | Синтез тестовых YAML из spec + аннотации (seed-bodies, idempotency, pagination, lifecycle). | `apis/<name>/tests/*.yaml`, annotations |
|
|
81
|
+
| **Run** | `run`, `session`, `request`, `audit`, `checks run`, `probe <class>` | Дергаем API: тесты, depth-checks, probes. Пишем runs/results в SQLite. | `runs/` (DB), HTTP-аудит |
|
|
82
|
+
| **Analyze** | `coverage`, `db {runs,run,collections,diagnose,compare}`, `check spec`, `describe`, `catalog` | Триаж результатов, coverage-гэпы, lint спеки. Финдинги через `recommended_action`. | Envelope JSON, finding-streams |
|
|
83
|
+
| **Report** | `report`, `report-bundle`, `--report {json,ndjson,sarif,junit,html,markdown}` | Конвертация runs/findings в shareable форматы. | NDJSON-stream, SARIF, HTML, JUnit |
|
|
84
|
+
|
|
85
|
+
Полная картина с iron-rules и pre-flight checklist'ами — в skill-документах (`src/cli/commands/init/templates/skills/*.md`), которые ставятся через `zond init`.
|
|
86
|
+
|
|
87
|
+
## Extension points
|
|
88
|
+
|
|
89
|
+
Когда добавляешь новую функциональность — это **типичные точки расширения**:
|
|
90
|
+
|
|
91
|
+
| Хочу добавить | Куда |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Новый depth-check (schemathesis-style) | `core/checks/checks/<name>.ts` + регистрация в `core/checks/runner.ts`. |
|
|
94
|
+
| Новый probe-class (security/mass-assignment-like) | `core/probe/<class>/` подмодуль; subcommand в `cli/commands/probe/`. Не клади всё в один монолит — см. ARV-295/ARV-296. |
|
|
95
|
+
| Новый reporter format | `core/reporter/<format>.ts`; зарегистрировать в `cli/output.ts` или per-command `--report`. |
|
|
96
|
+
| Новый CLI флаг с envelope-выводом | `cli/commands/<cmd>.ts` + Ajv-схема в `cli/json-schemas.ts`. Envelope shape — единый, не плоди вариации. |
|
|
97
|
+
| Анти-FP guard для check/probe | `core/anti-fp/rules/<rule>.ts` (registry-pattern в развитии — ARV-259). |
|
|
98
|
+
| Новая diagnostic-hint | `core/diagnostics/` — closed-enum `recommended_action`, не магические строки. |
|
|
99
|
+
| Новая DB-сущность | `src/db/migrations/<NNNN>-<name>.sql` + tiped query в `db/queries/`. |
|
|
100
|
+
|
|
101
|
+
## Workspace contract (ссылка)
|
|
102
|
+
|
|
103
|
+
Артефакты в `apis/<name>/` (`spec.json` / `.api-catalog.yaml` / `.api-resources.yaml` / **`.api-fixtures.yaml` manifest** / **`.env.yaml` values**) — описаны в [`../AGENTS.md`](../AGENTS.md). Главное правило: **manifest — source of truth о списке переменных, env — только values**.
|
|
104
|
+
|
|
105
|
+
## Conventions
|
|
106
|
+
|
|
107
|
+
- TypeScript strict mode, `bun run check` = `tsc --noEmit` без warnings (`noUnusedLocals`/`noUnusedParameters` включены).
|
|
108
|
+
- Тесты — `bun test` (unit + integration), `bun run test:mocked` (HTTP-mocked).
|
|
109
|
+
- Сборка — `bun run build` → single-file `dist/zond` бинарь.
|
|
110
|
+
- Никаких `console.log` в `core/` — выводи через `cli/output.ts` или reporter slot.
|
|
111
|
+
- Никаких SDK Anthropic/Ollama/прочих LLM-провайдеров внутри zond — это dumb-tool (см. `feedback_zond_no_llm_calls`).
|
|
112
|
+
- Финдинги — через closed-enum `recommended_action`, агент триажит по enum, не по тексту message.
|
package/src/cli/argv.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-commander argv handling and shared argument parsers used across the
|
|
3
|
+
* commander tree. Pulled out of program.ts (TASK-190, m-11) so program.ts
|
|
4
|
+
* shrinks toward "just command registration" and these helpers can be
|
|
5
|
+
* unit-tested in isolation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { InvalidArgumentError } from "commander";
|
|
9
|
+
import type { ReporterName } from "../core/reporter/types.ts";
|
|
10
|
+
|
|
11
|
+
// ── MSYS path preprocessing ──
|
|
12
|
+
//
|
|
13
|
+
// Git Bash on Windows converts API paths like "/users" → "C:/Program Files/Git/users".
|
|
14
|
+
// We reverse that for flags whose values are API paths, not filesystem paths.
|
|
15
|
+
|
|
16
|
+
const MSYS_PREFIX_RE = /^[A-Z]:[\\/](?:Program Files[\\/]Git|msys64|usr)[\\/]/i;
|
|
17
|
+
|
|
18
|
+
const API_PATH_FLAGS = new Set(["--path", "--json-path"]);
|
|
19
|
+
|
|
20
|
+
function stripMsysPath(value: string): string {
|
|
21
|
+
if (!MSYS_PREFIX_RE.test(value)) return value;
|
|
22
|
+
return value.replace(MSYS_PREFIX_RE, "/");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Pre-process argv before commander sees it: undo Git Bash's MSYS path conversion
|
|
27
|
+
* for `--path` and `--json-path` values (both `--path X` and `--path=X` forms).
|
|
28
|
+
*/
|
|
29
|
+
export function preprocessArgv(argv: string[]): string[] {
|
|
30
|
+
const out = [...argv];
|
|
31
|
+
for (let i = 0; i < out.length; i++) {
|
|
32
|
+
const arg = out[i]!;
|
|
33
|
+
|
|
34
|
+
// --flag=value form
|
|
35
|
+
const eqIdx = arg.indexOf("=");
|
|
36
|
+
if (arg.startsWith("--") && eqIdx !== -1) {
|
|
37
|
+
const flag = arg.slice(0, eqIdx);
|
|
38
|
+
if (API_PATH_FLAGS.has(flag)) {
|
|
39
|
+
out[i] = `${flag}=${stripMsysPath(arg.slice(eqIdx + 1))}`;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --flag value form
|
|
45
|
+
if (API_PATH_FLAGS.has(arg)) {
|
|
46
|
+
const next = out[i + 1];
|
|
47
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
48
|
+
out[i + 1] = stripMsysPath(next);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Argument parsers ──
|
|
56
|
+
|
|
57
|
+
export function parsePositiveInt(name: string): (raw: string) => number {
|
|
58
|
+
return (raw: string) => {
|
|
59
|
+
const n = Number.parseInt(raw, 10);
|
|
60
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
61
|
+
throw new InvalidArgumentError(`Invalid ${name} value: ${raw}`);
|
|
62
|
+
}
|
|
63
|
+
return n;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `--rate-limit` accepts a positive integer (req/sec cap) or the literal
|
|
68
|
+
* string `auto` (no static cap; throttle adaptively from ratelimit-* headers). */
|
|
69
|
+
export function parseRateLimit(raw: string): number | "auto" {
|
|
70
|
+
if (raw.toLowerCase() === "auto") return "auto";
|
|
71
|
+
const n = Number.parseInt(raw, 10);
|
|
72
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
73
|
+
throw new InvalidArgumentError(`Invalid --rate-limit value: ${raw} (expected a positive integer or "auto")`);
|
|
74
|
+
}
|
|
75
|
+
return n;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function parseNonNegativeInt(name: string): (raw: string) => number {
|
|
79
|
+
return (raw: string) => {
|
|
80
|
+
const n = Number.parseInt(raw, 10);
|
|
81
|
+
if (Number.isNaN(n) || n < 0 || String(n) !== raw.trim()) {
|
|
82
|
+
throw new InvalidArgumentError(`Invalid ${name} value: ${raw} (expected a non-negative integer)`);
|
|
83
|
+
}
|
|
84
|
+
return n;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function parseInteger(name: string): (raw: string) => number {
|
|
89
|
+
return (raw: string) => {
|
|
90
|
+
const n = Number.parseInt(raw, 10);
|
|
91
|
+
if (Number.isNaN(n)) {
|
|
92
|
+
throw new InvalidArgumentError(`Invalid ${name} value: ${raw}`);
|
|
93
|
+
}
|
|
94
|
+
return n;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function parsePercentage(raw: string): number {
|
|
99
|
+
const n = Number.parseInt(raw, 10);
|
|
100
|
+
if (Number.isNaN(n) || n < 0 || n > 100) {
|
|
101
|
+
throw new InvalidArgumentError(`Invalid --fail-on-coverage value: ${raw} (must be 0–100)`);
|
|
102
|
+
}
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const collect = (val: string, prev: string[]): string[] => [...prev, val];
|
|
107
|
+
|
|
108
|
+
const VALID_REPORTERS = new Set<string>(["console", "json", "junit"]);
|
|
109
|
+
|
|
110
|
+
export function parseReporter(raw: string): ReporterName {
|
|
111
|
+
if (!VALID_REPORTERS.has(raw)) {
|
|
112
|
+
throw new InvalidArgumentError(`Unknown reporter: ${raw}. Available: console, json, junit`);
|
|
113
|
+
}
|
|
114
|
+
return raw as ReporterName;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Helper: split repeatable values like ["a,b", "c"] → ["a", "b", "c"] */
|
|
118
|
+
export function flatSplit(values: string[] | undefined): string[] | undefined {
|
|
119
|
+
if (!values || values.length === 0) return undefined;
|
|
120
|
+
const out = values.flatMap((v) => v.split(",")).filter(Boolean);
|
|
121
|
+
return out.length > 0 ? out : undefined;
|
|
122
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `zond add api <name> --spec <path|url>` — register a new API in the
|
|
3
|
+
* current workspace.
|
|
4
|
+
*
|
|
5
|
+
* The split from `zond init --spec` exists so the two operations
|
|
6
|
+
* (workspace bootstrap vs. API registration) have separate names and
|
|
7
|
+
* separate skill mentions. `init --spec` still works as a deprecated
|
|
8
|
+
* alias.
|
|
9
|
+
*
|
|
10
|
+
* This command refuses to run when no workspace marker is present,
|
|
11
|
+
* pointing the user at `zond init` first. setupApi handles spec
|
|
12
|
+
* snapshot + artifact generation.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { setupApi, type SetupApiResult } from "../../core/setup-api.ts";
|
|
16
|
+
import { findWorkspaceRoot } from "../../core/workspace/root.ts";
|
|
17
|
+
import { jsonOk, jsonError, printJson, zerr } from "../json-envelope.ts";
|
|
18
|
+
import { printError, printSuccess } from "../output.ts";
|
|
19
|
+
|
|
20
|
+
export interface AddApiOptions {
|
|
21
|
+
name: string;
|
|
22
|
+
/** One or more specs. Multiple → deterministic union (ARV-375). */
|
|
23
|
+
specs?: string[];
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
dir?: string;
|
|
26
|
+
force?: boolean;
|
|
27
|
+
insecure?: boolean;
|
|
28
|
+
caPath?: string;
|
|
29
|
+
dbPath?: string;
|
|
30
|
+
json?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function addApiCommand(opts: AddApiOptions): Promise<number> {
|
|
34
|
+
const ws = findWorkspaceRoot();
|
|
35
|
+
if (ws.fromFallback) {
|
|
36
|
+
const m = `No workspace detected (no zond.config.yml / .zond / apis marker). Run \`zond init\` first to bootstrap a workspace.`;
|
|
37
|
+
if (opts.json) printJson(jsonError("add-api", [m])); else printError(m);
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const envVars: Record<string, string> = {};
|
|
42
|
+
if (opts.baseUrl) envVars.base_url = opts.baseUrl;
|
|
43
|
+
|
|
44
|
+
const hasSpec = (opts.specs?.length ?? 0) > 0;
|
|
45
|
+
let result: SetupApiResult;
|
|
46
|
+
try {
|
|
47
|
+
result = await setupApi({
|
|
48
|
+
name: opts.name,
|
|
49
|
+
specs: opts.specs,
|
|
50
|
+
dir: opts.dir,
|
|
51
|
+
envVars: Object.keys(envVars).length > 0 ? envVars : undefined,
|
|
52
|
+
dbPath: opts.dbPath,
|
|
53
|
+
force: opts.force,
|
|
54
|
+
insecure: opts.insecure,
|
|
55
|
+
caPath: opts.caPath,
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const m = (err as Error).message;
|
|
59
|
+
// Tag known spec-ingest failures with a structured code so downstream
|
|
60
|
+
// tooling (skills, retry logic) can branch on it (ARV-145). Cyclic
|
|
61
|
+
// structures escape decycleSchema only when @readme/openapi-parser
|
|
62
|
+
// builds an unusual graph — surface that as spec_load_failure with a
|
|
63
|
+
// pointer to the underlying serializer message.
|
|
64
|
+
const isCycleError = /cyclic structures|spec_serialize_failed/i.test(m);
|
|
65
|
+
const errInput = isCycleError ? zerr("spec_load_failure", m) : m;
|
|
66
|
+
if (opts.json) printJson(jsonError("add-api", [errInput])); else printError(m);
|
|
67
|
+
return 2;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mode: "spec" | "run-only" = hasSpec ? "spec" : "run-only";
|
|
71
|
+
const artifacts = mode === "spec"
|
|
72
|
+
? ["spec.json", ".api-catalog.yaml", ".api-resources.yaml", ".api-fixtures.yaml", ".env.yaml"]
|
|
73
|
+
: [".env.yaml"];
|
|
74
|
+
|
|
75
|
+
if (opts.json) {
|
|
76
|
+
printJson(jsonOk("add-api", {
|
|
77
|
+
api: opts.name,
|
|
78
|
+
mode,
|
|
79
|
+
collectionId: result.collectionId,
|
|
80
|
+
baseDir: result.baseDir,
|
|
81
|
+
testPath: result.testPath,
|
|
82
|
+
endpoints: result.specEndpoints,
|
|
83
|
+
artifacts,
|
|
84
|
+
}, result.warnings));
|
|
85
|
+
} else {
|
|
86
|
+
if (mode === "spec") {
|
|
87
|
+
printSuccess(`Registered API '${opts.name}' at ${result.baseDir} (${result.specEndpoints} endpoints)`);
|
|
88
|
+
process.stdout.write(` Artifacts: spec.json + .api-catalog.yaml + .api-resources.yaml + .api-fixtures.yaml\n`);
|
|
89
|
+
if (result.authVars && result.authVars.length > 0) {
|
|
90
|
+
const list = result.authVars.map((v) => `\`${v}\``).join(", ");
|
|
91
|
+
process.stdout.write(` Auth required: fill ${list} in ${result.baseDir}/.secrets.yaml (already wired via @secret in .env.yaml).\n`);
|
|
92
|
+
}
|
|
93
|
+
process.stdout.write(` Next: run \`zond doctor --api ${opts.name}\` to see required fixtures.\n`);
|
|
94
|
+
} else {
|
|
95
|
+
printSuccess(`Registered API '${opts.name}' at ${result.baseDir} (no spec — run-only mode)`);
|
|
96
|
+
process.stdout.write(` Artifacts: .env.yaml (base_url=${opts.baseUrl})\n`);
|
|
97
|
+
process.stdout.write(` Next: write tests in ${result.testPath}/, run \`zond run --api ${opts.name} <test.yaml>\`.\n`);
|
|
98
|
+
process.stdout.write(` To enable generate/probe/validate-schema, attach a spec: \`zond refresh-api ${opts.name} --spec <path|url>\`.\n`);
|
|
99
|
+
}
|
|
100
|
+
if (result.warnings) for (const w of result.warnings) process.stderr.write(`Warning: ${w}\n`);
|
|
101
|
+
}
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
import type { Command } from "commander";
|
|
106
|
+
import { globalJson } from "../resolve.ts";
|
|
107
|
+
|
|
108
|
+
export function registerAdd(program: Command): void {
|
|
109
|
+
const add = program.command("add").description("Register objects in the workspace");
|
|
110
|
+
add
|
|
111
|
+
.command("api <name>")
|
|
112
|
+
.description("Register an API: from an OpenAPI spec (full toolkit) or just --base-url (run-only mode)")
|
|
113
|
+
.option(
|
|
114
|
+
"--spec <path>",
|
|
115
|
+
"Path or URL to OpenAPI spec — enables generate/probe/validate-schema. Repeat --spec to union multiple specs (e.g. v1 + v2) into one merged audit target (ARV-375).",
|
|
116
|
+
(val: string, acc: string[]) => acc.concat(val),
|
|
117
|
+
[] as string[],
|
|
118
|
+
)
|
|
119
|
+
.option("--base-url <url>", "Base URL recorded in .env.yaml (required if --spec is omitted)")
|
|
120
|
+
.option("--dir <path>", "Target directory (defaults to apis/<name>/)")
|
|
121
|
+
.option("--force", "Overwrite an existing API with the same name")
|
|
122
|
+
.option("--insecure", "Skip TLS verification when fetching the spec from https")
|
|
123
|
+
.option("--ca <path>", "PEM CA bundle to trust for the spec fetch (adds to public roots; also reads NODE_EXTRA_CA_CERTS) — use instead of --insecure for internal/corp CAs")
|
|
124
|
+
.option("--db <path>", "Path to SQLite database file")
|
|
125
|
+
.action(async (name: string, opts, cmd: Command) => {
|
|
126
|
+
const json = globalJson(cmd);
|
|
127
|
+
const specs: string[] = Array.isArray(opts.spec) ? opts.spec : (opts.spec ? [opts.spec] : []);
|
|
128
|
+
if (specs.length === 0 && !opts.baseUrl) {
|
|
129
|
+
const m = "Provide --spec <path|url> for a full registration, or --base-url <url> for run-only mode.";
|
|
130
|
+
if (json) printJson(jsonError("add-api", [m])); else printError(m);
|
|
131
|
+
process.exitCode = 2;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
process.exitCode = await addApiCommand({
|
|
135
|
+
name,
|
|
136
|
+
specs,
|
|
137
|
+
baseUrl: opts.baseUrl,
|
|
138
|
+
dir: opts.dir,
|
|
139
|
+
force: opts.force === true,
|
|
140
|
+
insecure: opts.insecure === true,
|
|
141
|
+
caPath: typeof opts.ca === "string" ? opts.ca : undefined,
|
|
142
|
+
dbPath: typeof opts.db === "string" ? opts.db : undefined,
|
|
143
|
+
json,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARV-187 / idempotency: parser + expected shape.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ResourcePatch } from "./overlay.ts";
|
|
7
|
+
import type { ResourceSlice } from "./prompts.ts";
|
|
8
|
+
|
|
9
|
+
const IdempotencySchema = z.object({
|
|
10
|
+
header: z.string().default("Idempotency-Key"),
|
|
11
|
+
scope: z.enum(["endpoint", "global"]).optional(),
|
|
12
|
+
ignore_response_fields: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const ResponseSchema = z.object({
|
|
16
|
+
resource: z.string(),
|
|
17
|
+
idempotency: IdempotencySchema.nullable(),
|
|
18
|
+
rationale: z.string().optional(),
|
|
19
|
+
confidence: z.enum(["low", "medium", "high"]).optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const EXPECTED_OUTPUT_SHAPE = {
|
|
23
|
+
resource: "string (echo input)",
|
|
24
|
+
idempotency: {
|
|
25
|
+
header: "string (header name, e.g. 'Idempotency-Key')",
|
|
26
|
+
scope: "endpoint | global (optional)",
|
|
27
|
+
ignore_response_fields: "string[] (optional — fields that change between replays, e.g. 'created')",
|
|
28
|
+
},
|
|
29
|
+
rationale: "string (optional)",
|
|
30
|
+
confidence: "low | medium | high",
|
|
31
|
+
null_form: "if create endpoint doesn't support idempotency-replay, return { resource, idempotency: null }",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function parseIdempotencyResponse(parsed: unknown, slice: ResourceSlice): { patch: ResourcePatch; audit: Record<string, unknown> } {
|
|
35
|
+
const validated = ResponseSchema.safeParse(parsed);
|
|
36
|
+
if (!validated.success) {
|
|
37
|
+
throw new Error(`idempotency response failed schema for ${slice.resource}: ${validated.error.message}`);
|
|
38
|
+
}
|
|
39
|
+
const v = validated.data;
|
|
40
|
+
if (v.idempotency == null) {
|
|
41
|
+
return {
|
|
42
|
+
patch: { resource: slice.resource },
|
|
43
|
+
audit: { resource: slice.resource, rationale: v.rationale, confidence: v.confidence, dropped: "no Idempotency-Key support" },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
patch: {
|
|
48
|
+
resource: slice.resource,
|
|
49
|
+
idempotency: {
|
|
50
|
+
header: v.idempotency.header,
|
|
51
|
+
scope: v.idempotency.scope,
|
|
52
|
+
ignore_response_fields: v.idempotency.ignore_response_fields,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
audit: { resource: slice.resource, rationale: v.rationale, confidence: v.confidence },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isApplicable(slice: ResourceSlice): boolean { return Boolean(slice.endpoints.create); }
|