@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.
Files changed (282) hide show
  1. package/CHANGELOG.md +811 -0
  2. package/README.md +59 -6
  3. package/package.json +9 -7
  4. package/src/CLAUDE.md +112 -0
  5. package/src/cli/argv.ts +122 -0
  6. package/src/cli/commands/add-api.ts +146 -0
  7. package/src/cli/commands/api/annotate/idempotency.ts +59 -0
  8. package/src/cli/commands/api/annotate/index.ts +880 -0
  9. package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
  10. package/src/cli/commands/api/annotate/overlay.ts +206 -0
  11. package/src/cli/commands/api/annotate/pagination.ts +64 -0
  12. package/src/cli/commands/api/annotate/prompts.ts +220 -0
  13. package/src/cli/commands/api/annotate/readback.ts +58 -0
  14. package/src/cli/commands/api/annotate/resources.ts +91 -0
  15. package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
  16. package/src/cli/commands/audit.ts +786 -0
  17. package/src/cli/commands/catalog.ts +35 -0
  18. package/src/cli/commands/check.ts +361 -0
  19. package/src/cli/commands/checks.ts +1072 -0
  20. package/src/cli/commands/ci-init.ts +43 -0
  21. package/src/cli/commands/clean.ts +212 -0
  22. package/src/cli/commands/cleanup.ts +236 -0
  23. package/src/cli/commands/completions.ts +16 -0
  24. package/src/cli/commands/coverage.ts +823 -132
  25. package/src/cli/commands/db.ts +486 -12
  26. package/src/cli/commands/describe.ts +37 -2
  27. package/src/cli/commands/discover.ts +1356 -0
  28. package/src/cli/commands/doctor.ts +661 -0
  29. package/src/cli/commands/fixtures.ts +402 -0
  30. package/src/cli/commands/generate.ts +438 -47
  31. package/src/cli/commands/init/bootstrap.ts +34 -2
  32. package/src/cli/commands/{init.ts → init/index.ts} +99 -5
  33. package/src/cli/commands/init/skills.ts +99 -3
  34. package/src/cli/commands/init/templates/agents.md +77 -64
  35. package/src/cli/commands/init/templates/skills/warm-up-target.md +122 -0
  36. package/src/cli/commands/init/templates/skills/zond-checks.md +621 -0
  37. package/src/cli/commands/init/templates/skills/zond-seed.md +114 -0
  38. package/src/cli/commands/init/templates/skills/zond-triage.md +272 -0
  39. package/src/cli/commands/init/templates/skills/zond.md +802 -125
  40. package/src/cli/commands/init/templates/zond-config.yml +8 -9
  41. package/src/cli/commands/prepare-fixtures.ts +97 -0
  42. package/src/cli/commands/probe/_seed-bodies.ts +52 -0
  43. package/src/cli/commands/probe/mass-assignment.ts +594 -0
  44. package/src/cli/commands/probe/security.ts +537 -0
  45. package/src/cli/commands/probe/static.ts +255 -0
  46. package/src/cli/commands/probe/webhooks.ts +163 -0
  47. package/src/cli/commands/probe.ts +535 -0
  48. package/src/cli/commands/reference.ts +87 -0
  49. package/src/cli/commands/refresh-api.ts +227 -0
  50. package/src/cli/commands/remove-api.ts +150 -0
  51. package/src/cli/commands/report-bundle.ts +310 -0
  52. package/src/cli/commands/report.ts +241 -0
  53. package/src/cli/commands/request.ts +495 -4
  54. package/src/cli/commands/run.ts +870 -53
  55. package/src/cli/commands/schema-from-runs.ts +128 -0
  56. package/src/cli/commands/secrets.ts +133 -0
  57. package/src/cli/commands/session.ts +244 -0
  58. package/src/cli/commands/use.ts +18 -1
  59. package/src/cli/index.ts +20 -3
  60. package/src/cli/json-envelope.ts +92 -3
  61. package/src/cli/json-schemas.ts +314 -0
  62. package/src/cli/output.ts +17 -1
  63. package/src/cli/program.ts +199 -635
  64. package/src/cli/resolve.ts +105 -0
  65. package/src/cli/safe-live.ts +24 -0
  66. package/src/cli/status-filter.ts +114 -0
  67. package/src/cli/util/api-context.ts +85 -0
  68. package/src/cli/version.ts +5 -0
  69. package/src/core/audit/persist.ts +183 -0
  70. package/src/core/checks/budget.ts +59 -0
  71. package/src/core/checks/checks/_crud-helpers.ts +133 -0
  72. package/src/core/checks/checks/_negative_mutator.ts +133 -0
  73. package/src/core/checks/checks/_readback-helpers.ts +133 -0
  74. package/src/core/checks/checks/content_type_conformance.ts +39 -0
  75. package/src/core/checks/checks/cross_call_references.ts +147 -0
  76. package/src/core/checks/checks/cursor_boundary_fuzzing.ts +219 -0
  77. package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
  78. package/src/core/checks/checks/idempotency_replay.ts +242 -0
  79. package/src/core/checks/checks/ignored_auth.ts +254 -0
  80. package/src/core/checks/checks/index.ts +68 -0
  81. package/src/core/checks/checks/lifecycle_transitions.ts +416 -0
  82. package/src/core/checks/checks/missing_required_header.ts +40 -0
  83. package/src/core/checks/checks/negative_data_rejection.ts +148 -0
  84. package/src/core/checks/checks/not_a_server_error.ts +35 -0
  85. package/src/core/checks/checks/open_cors_on_sensitive.ts +160 -0
  86. package/src/core/checks/checks/pagination_invariants.ts +419 -0
  87. package/src/core/checks/checks/positive_data_acceptance.ts +33 -0
  88. package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
  89. package/src/core/checks/checks/response_headers_conformance.ts +74 -0
  90. package/src/core/checks/checks/response_schema_conformance.ts +30 -0
  91. package/src/core/checks/checks/status_code_conformance.ts +132 -0
  92. package/src/core/checks/checks/unsupported_method.ts +63 -0
  93. package/src/core/checks/checks/use_after_free.ts +78 -0
  94. package/src/core/checks/index.ts +30 -0
  95. package/src/core/checks/mode.ts +82 -0
  96. package/src/core/checks/recommended-action.ts +68 -0
  97. package/src/core/checks/registry.ts +78 -0
  98. package/src/core/checks/runner.ts +1461 -0
  99. package/src/core/checks/sarif.ts +230 -0
  100. package/src/core/checks/spec-findings.ts +308 -0
  101. package/src/core/checks/stateful.ts +121 -0
  102. package/src/core/checks/types.ts +305 -0
  103. package/src/core/checks/zond-extensions.ts +73 -0
  104. package/src/core/classifier/recommended-action.ts +251 -0
  105. package/src/core/context/current.ts +22 -6
  106. package/src/core/context/session.ts +78 -0
  107. package/src/core/coverage/loader.ts +216 -0
  108. package/src/core/coverage/reasons.ts +300 -0
  109. package/src/core/diagnostics/db-analysis.ts +293 -59
  110. package/src/core/diagnostics/failure-class.ts +140 -0
  111. package/src/core/diagnostics/failure-hints.ts +88 -89
  112. package/src/core/diagnostics/spec-pointer.ts +99 -0
  113. package/src/core/diagnostics/suggested-fixes.ts +155 -0
  114. package/src/core/exporter/case-study/index.ts +270 -0
  115. package/src/core/exporter/curl.ts +40 -0
  116. package/src/core/exporter/exporter.ts +48 -0
  117. package/src/core/exporter/html-report/escape.ts +24 -0
  118. package/src/core/exporter/html-report/index.ts +479 -0
  119. package/src/core/exporter/html-report/script.ts +100 -0
  120. package/src/core/exporter/html-report/styles.ts +408 -0
  121. package/src/core/generator/chunker.ts +38 -19
  122. package/src/core/generator/coverage-phase.ts +0 -0
  123. package/src/core/generator/data-factory.ts +586 -22
  124. package/src/core/generator/describe.ts +1 -1
  125. package/src/core/generator/fixtures-builder.ts +332 -0
  126. package/src/core/generator/index.ts +5 -5
  127. package/src/core/generator/openapi-reader.ts +135 -7
  128. package/src/core/generator/path-param-disambig.ts +140 -0
  129. package/src/core/generator/resources-builder.ts +898 -0
  130. package/src/core/generator/schema-utils.ts +33 -3
  131. package/src/core/generator/serializer.ts +103 -13
  132. package/src/core/generator/suite-generator.ts +583 -122
  133. package/src/core/generator/types.ts +14 -0
  134. package/src/core/identity/identity-file.ts +0 -0
  135. package/src/core/lint/affects.ts +28 -0
  136. package/src/core/lint/config.ts +96 -0
  137. package/src/core/lint/format.ts +42 -0
  138. package/src/core/lint/index.ts +94 -0
  139. package/src/core/lint/reporter.ts +128 -0
  140. package/src/core/lint/rules/consistency.ts +158 -0
  141. package/src/core/lint/rules/heuristics.ts +97 -0
  142. package/src/core/lint/rules/strictness.ts +109 -0
  143. package/src/core/lint/types.ts +96 -0
  144. package/src/core/lint/walker.ts +248 -0
  145. package/src/core/meta/meta-store.ts +6 -73
  146. package/src/core/output/README.md +73 -0
  147. package/src/core/output/index.ts +13 -0
  148. package/src/core/output/run.ts +91 -0
  149. package/src/core/output/types.ts +122 -0
  150. package/src/core/parser/dynamic-values.ts +160 -0
  151. package/src/core/parser/env-interpolation.ts +104 -0
  152. package/src/core/parser/filter.ts +57 -0
  153. package/src/core/parser/schema.ts +129 -4
  154. package/src/core/parser/types.ts +19 -1
  155. package/src/core/parser/variables.ts +0 -0
  156. package/src/core/parser/yaml-parser.ts +58 -12
  157. package/src/core/probe/bootstrap.ts +34 -0
  158. package/src/core/probe/dry-run-envelope.ts +61 -0
  159. package/src/core/probe/mass-assignment/classify.ts +175 -0
  160. package/src/core/probe/mass-assignment/cleanup.ts +52 -0
  161. package/src/core/probe/mass-assignment/digest.ts +114 -0
  162. package/src/core/probe/mass-assignment/orchestrator.ts +459 -0
  163. package/src/core/probe/mass-assignment/regression.ts +141 -0
  164. package/src/core/probe/mass-assignment/suspects.ts +92 -0
  165. package/src/core/probe/mass-assignment/types.ts +135 -0
  166. package/src/core/probe/mass-assignment-probe-class.ts +198 -0
  167. package/src/core/probe/mass-assignment-probe.ts +27 -0
  168. package/src/core/probe/mass-assignment-template.ts +240 -0
  169. package/src/core/probe/method-probe.ts +43 -76
  170. package/src/core/probe/method-shared.ts +69 -0
  171. package/src/core/probe/negative-probe.ts +183 -149
  172. package/src/core/probe/orphan-tracker.ts +188 -0
  173. package/src/core/probe/path-discovery.ts +439 -0
  174. package/src/core/probe/probe-harness.ts +119 -0
  175. package/src/core/probe/registry.ts +89 -0
  176. package/src/core/probe/runner.ts +136 -0
  177. package/src/core/probe/security/baseline.ts +174 -0
  178. package/src/core/probe/security/classify.ts +341 -0
  179. package/src/core/probe/security/cleanup.ts +125 -0
  180. package/src/core/probe/security/detectors.ts +71 -0
  181. package/src/core/probe/security/digest.ts +104 -0
  182. package/src/core/probe/security/orchestrator.ts +398 -0
  183. package/src/core/probe/security/regression.ts +103 -0
  184. package/src/core/probe/security/types.ts +151 -0
  185. package/src/core/probe/security-probe-class.ts +207 -0
  186. package/src/core/probe/security-probe.ts +32 -0
  187. package/src/core/probe/shared.ts +531 -0
  188. package/src/core/probe/static-probe-class.ts +125 -0
  189. package/src/core/probe/types.ts +165 -0
  190. package/src/core/probe/verdict-aggregator.ts +33 -0
  191. package/src/core/probe/webhooks-probe.ts +282 -0
  192. package/src/core/reporter/console.ts +41 -2
  193. package/src/core/reporter/index.ts +2 -3
  194. package/src/core/reporter/json.ts +11 -1
  195. package/src/core/reporter/junit.ts +27 -12
  196. package/src/core/reporter/ndjson.ts +37 -0
  197. package/src/core/reporter/types.ts +3 -0
  198. package/src/core/runner/assertions.ts +59 -2
  199. package/src/core/runner/async-pool.ts +108 -0
  200. package/src/core/runner/auth-path.ts +8 -0
  201. package/src/core/runner/ci-context.ts +72 -0
  202. package/src/core/runner/executor.ts +265 -36
  203. package/src/core/runner/form-encode.ts +41 -0
  204. package/src/core/runner/http-client.ts +112 -2
  205. package/src/core/runner/learn-drift.ts +293 -0
  206. package/src/core/runner/preflight-vars.ts +153 -0
  207. package/src/core/runner/progress-tracker.ts +73 -0
  208. package/src/core/runner/rate-limiter.ts +87 -33
  209. package/src/core/runner/run-kind.ts +45 -0
  210. package/src/core/runner/schema-validator.ts +308 -0
  211. package/src/core/runner/send-request.ts +158 -20
  212. package/src/core/runner/types.ts +44 -0
  213. package/src/core/secrets/registry.ts +164 -0
  214. package/src/core/secrets/secrets-file.ts +115 -0
  215. package/src/core/selectors/operation-filter.ts +144 -0
  216. package/src/core/setup-api.ts +457 -20
  217. package/src/core/severity/category.ts +94 -0
  218. package/src/core/severity/index.ts +58 -0
  219. package/src/core/spec/infer-schema.ts +102 -0
  220. package/src/core/spec/layers.ts +154 -0
  221. package/src/core/spec/merge-specs.ts +156 -0
  222. package/src/core/spec/schema-from-runs.ts +117 -0
  223. package/src/core/spec/schema-overlay.ts +130 -0
  224. package/src/core/util/ajv.ts +13 -0
  225. package/src/core/util/format-eta.ts +21 -0
  226. package/src/core/util/headers.ts +9 -0
  227. package/src/core/util/url.ts +24 -0
  228. package/src/core/utils.ts +5 -1
  229. package/src/core/workspace/config.ts +129 -0
  230. package/src/core/workspace/fixture-gap-report.ts +84 -0
  231. package/src/core/workspace/fixture-gaps.ts +71 -0
  232. package/src/core/workspace/manifest.ts +283 -0
  233. package/src/core/workspace/output-rotation.ts +62 -0
  234. package/src/core/workspace/root.ts +13 -11
  235. package/src/core/workspace/triage-path.ts +87 -0
  236. package/src/db/lint-runs.ts +47 -0
  237. package/src/db/migrate.ts +128 -0
  238. package/src/db/migrations/0001_run_kind.sql +25 -0
  239. package/src/db/migrations/0002_run_kind_request.sql +59 -0
  240. package/src/db/migrations/sql.d.ts +4 -0
  241. package/src/db/queries/collections.ts +133 -0
  242. package/src/db/queries/coverage.ts +9 -0
  243. package/src/db/queries/dashboard.ts +59 -0
  244. package/src/db/queries/results.ts +216 -0
  245. package/src/db/queries/runs.ts +289 -0
  246. package/src/db/queries/sessions.ts +42 -0
  247. package/src/db/queries/settings.ts +28 -0
  248. package/src/db/queries/types.ts +172 -0
  249. package/src/db/queries.ts +75 -802
  250. package/src/db/schema.ts +178 -50
  251. package/src/cli/commands/export.ts +0 -144
  252. package/src/cli/commands/guide.ts +0 -127
  253. package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
  254. package/src/cli/commands/probe-methods.ts +0 -108
  255. package/src/cli/commands/probe-validation.ts +0 -124
  256. package/src/cli/commands/serve.ts +0 -114
  257. package/src/cli/commands/sync.ts +0 -268
  258. package/src/cli/commands/update.ts +0 -189
  259. package/src/cli/commands/validate.ts +0 -34
  260. package/src/core/diagnostics/render-md.ts +0 -112
  261. package/src/core/exporter/postman.ts +0 -963
  262. package/src/core/generator/guide-builder.ts +0 -253
  263. package/src/core/meta/types.ts +0 -19
  264. package/src/core/parser/index.ts +0 -21
  265. package/src/core/runner/execute-run.ts +0 -132
  266. package/src/core/runner/index.ts +0 -12
  267. package/src/core/sync/spec-differ.ts +0 -38
  268. package/src/web/data/collection-state.ts +0 -362
  269. package/src/web/routes/api.ts +0 -314
  270. package/src/web/routes/dashboard.ts +0 -350
  271. package/src/web/routes/runs.ts +0 -64
  272. package/src/web/schemas.ts +0 -121
  273. package/src/web/server.ts +0 -134
  274. package/src/web/static/htmx.min.cjs +0 -1
  275. package/src/web/static/style.css +0 -1148
  276. package/src/web/views/endpoints-tab.ts +0 -174
  277. package/src/web/views/explorer-tab.ts +0 -402
  278. package/src/web/views/health-strip.ts +0 -92
  279. package/src/web/views/layout.ts +0 -48
  280. package/src/web/views/results.ts +0 -210
  281. package/src/web/views/runs-tab.ts +0 -126
  282. package/src/web/views/suites-tab.ts +0 -181
@@ -0,0 +1,408 @@
1
+ // Single-file inline CSS for the run report.
2
+ // Self-contained: no @import, no external fonts. System UI stack only.
3
+ // Light + dark via prefers-color-scheme. Print-friendly for PDF export.
4
+
5
+ export const STYLES = `
6
+ :root {
7
+ color-scheme: light dark;
8
+ --bg: #fbfbfd;
9
+ --bg-elev: #ffffff;
10
+ --bg-muted: #f3f4f6;
11
+ --bg-code: #f6f8fa;
12
+ --fg: #0b0d12;
13
+ --fg-muted: #5b6472;
14
+ --border: #e5e7eb;
15
+ --border-strong: #d1d5db;
16
+ --accent: #3b82f6;
17
+ --accent-fg: #ffffff;
18
+ --pass: #10b981;
19
+ --pass-bg: #d1fae5;
20
+ --fail: #ef4444;
21
+ --fail-bg: #fee2e2;
22
+ --warn: #f59e0b;
23
+ --warn-bg: #fef3c7;
24
+ --info: #6366f1;
25
+ --info-bg: #e0e7ff;
26
+ --shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.06);
27
+ }
28
+ @media (prefers-color-scheme: dark) {
29
+ :root {
30
+ --bg: #0b0d12;
31
+ --bg-elev: #11141b;
32
+ --bg-muted: #181c25;
33
+ --bg-code: #0f1219;
34
+ --fg: #e5e7eb;
35
+ --fg-muted: #9aa3b2;
36
+ --border: #1f242f;
37
+ --border-strong: #2c3340;
38
+ --accent: #60a5fa;
39
+ --accent-fg: #0b0d12;
40
+ --pass: #34d399;
41
+ --pass-bg: #052e22;
42
+ --fail: #f87171;
43
+ --fail-bg: #2a0e10;
44
+ --warn: #fbbf24;
45
+ --warn-bg: #2a1d05;
46
+ --info: #818cf8;
47
+ --info-bg: #1c1c3a;
48
+ --shadow: 0 1px 2px rgba(0,0,0,.4), 0 6px 24px rgba(0,0,0,.4);
49
+ }
50
+ }
51
+
52
+ * { box-sizing: border-box; }
53
+ html, body { margin: 0; padding: 0; }
54
+ body {
55
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
56
+ font-size: 14px;
57
+ line-height: 1.5;
58
+ background: var(--bg);
59
+ color: var(--fg);
60
+ -webkit-font-smoothing: antialiased;
61
+ }
62
+ .container { max-width: 1080px; margin: 0 auto; padding: 32px 24px 64px; }
63
+ .mono { font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; }
64
+
65
+ /* ── Hero ── */
66
+ .hero {
67
+ background: var(--bg-elev);
68
+ border: 1px solid var(--border);
69
+ border-radius: 16px;
70
+ padding: 28px 32px;
71
+ margin-bottom: 24px;
72
+ box-shadow: var(--shadow);
73
+ display: grid;
74
+ grid-template-columns: 1fr auto;
75
+ gap: 24px;
76
+ align-items: center;
77
+ }
78
+ .hero h1 {
79
+ margin: 0 0 6px;
80
+ font-size: 28px;
81
+ font-weight: 700;
82
+ letter-spacing: -0.02em;
83
+ }
84
+ .hero .sub {
85
+ font-size: 13px;
86
+ color: var(--fg-muted);
87
+ }
88
+ .hero .meta {
89
+ margin-top: 16px;
90
+ display: grid;
91
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
92
+ gap: 10px 18px;
93
+ }
94
+ .hero .meta dt { font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--fg-muted); margin: 0; }
95
+ .hero .meta dd { font-size: 13px; margin: 2px 0 0; word-break: break-all; }
96
+ .hero .meta dd.mono { font-size: 12px; }
97
+
98
+ /* Pass-rate ring */
99
+ .ring { position: relative; width: 140px; height: 140px; }
100
+ .ring svg { transform: rotate(-90deg); width: 100%; height: 100%; }
101
+ .ring .track { stroke: var(--border); }
102
+ .ring .fill { transition: stroke-dashoffset .6s ease; }
103
+ .ring .label {
104
+ position: absolute; inset: 0;
105
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
106
+ text-align: center;
107
+ }
108
+ .ring .label .pct { font-size: 30px; font-weight: 700; letter-spacing: -0.02em; }
109
+ .ring .label .lbl { font-size: 11px; color: var(--fg-muted); text-transform: uppercase; letter-spacing: .08em; }
110
+
111
+ /* ── Status badges ── */
112
+ .badge {
113
+ display: inline-flex; align-items: center; gap: 4px;
114
+ padding: 2px 8px; border-radius: 999px;
115
+ font-size: 11px; font-weight: 600;
116
+ background: var(--bg-muted); color: var(--fg-muted);
117
+ border: 1px solid var(--border);
118
+ white-space: nowrap;
119
+ }
120
+ .badge.pass { background: var(--pass-bg); color: var(--pass); border-color: transparent; }
121
+ .badge.fail { background: var(--fail-bg); color: var(--fail); border-color: transparent; }
122
+ .badge.warn { background: var(--warn-bg); color: var(--warn); border-color: transparent; }
123
+ .badge.info { background: var(--info-bg); color: var(--info); border-color: transparent; }
124
+ .badge.solid-pass { background: var(--pass); color: white; border-color: transparent; }
125
+ .badge.solid-fail { background: var(--fail); color: white; border-color: transparent; }
126
+ .badge.dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
127
+
128
+ /* ── KPI strip ── */
129
+ .kpis {
130
+ display: grid;
131
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
132
+ gap: 12px;
133
+ margin-bottom: 24px;
134
+ }
135
+ .kpi {
136
+ background: var(--bg-elev);
137
+ border: 1px solid var(--border);
138
+ border-radius: 12px;
139
+ padding: 14px 16px;
140
+ }
141
+ .kpi .n { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
142
+ .kpi .l { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--fg-muted); margin-top: 2px; }
143
+ .kpi.pass .n { color: var(--pass); }
144
+ .kpi.fail .n { color: var(--fail); }
145
+ .kpi.warn .n { color: var(--warn); }
146
+
147
+ /* ── Section ── */
148
+ section { margin-top: 32px; }
149
+ section h2 { font-size: 16px; font-weight: 600; margin: 0 0 12px; display: flex; align-items: center; gap: 10px; }
150
+ section h2 .count { font-size: 12px; color: var(--fg-muted); font-weight: 500; }
151
+
152
+ /* ── Filter bar ── */
153
+ .filters {
154
+ display: flex; flex-wrap: wrap; gap: 6px;
155
+ margin-bottom: 14px;
156
+ }
157
+ .filters button {
158
+ font: inherit;
159
+ font-size: 12px;
160
+ padding: 5px 10px;
161
+ border-radius: 999px;
162
+ border: 1px solid var(--border);
163
+ background: var(--bg-elev);
164
+ color: var(--fg-muted);
165
+ cursor: pointer;
166
+ transition: all .15s;
167
+ }
168
+ .filters button:hover { color: var(--fg); border-color: var(--border-strong); }
169
+ .filters button.active { background: var(--fg); color: var(--bg); border-color: var(--fg); }
170
+
171
+ /* ── Failure card ── */
172
+ .cards { display: flex; flex-direction: column; gap: 8px; }
173
+ .card {
174
+ background: var(--bg-elev);
175
+ border: 1px solid var(--border);
176
+ border-radius: 10px;
177
+ overflow: hidden;
178
+ transition: border-color .15s;
179
+ }
180
+ .card:hover { border-color: var(--border-strong); }
181
+ .card.hidden { display: none; }
182
+ .card > .head {
183
+ display: flex; align-items: center; gap: 10px;
184
+ padding: 10px 14px;
185
+ cursor: pointer;
186
+ user-select: none;
187
+ background: transparent;
188
+ border: 0;
189
+ width: 100%;
190
+ text-align: left;
191
+ font: inherit;
192
+ color: inherit;
193
+ }
194
+ .card > .head:hover { background: var(--bg-muted); }
195
+ .card .chev {
196
+ width: 14px; height: 14px;
197
+ transition: transform .2s;
198
+ flex-shrink: 0;
199
+ color: var(--fg-muted);
200
+ }
201
+ .card.open .chev { transform: rotate(90deg); }
202
+ .card .method {
203
+ font-family: ui-monospace, SFMono-Regular, monospace;
204
+ font-size: 11px;
205
+ font-weight: 700;
206
+ padding: 2px 6px;
207
+ border-radius: 4px;
208
+ background: var(--bg-muted);
209
+ color: var(--fg-muted);
210
+ flex-shrink: 0;
211
+ min-width: 50px;
212
+ text-align: center;
213
+ }
214
+ .card .method.GET { color: #10b981; }
215
+ .card .method.POST { color: #3b82f6; }
216
+ .card .method.PUT { color: #f59e0b; }
217
+ .card .method.PATCH { color: #f59e0b; }
218
+ .card .method.DELETE { color: #ef4444; }
219
+ .card .name {
220
+ flex: 1;
221
+ font-size: 13px;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ white-space: nowrap;
225
+ }
226
+ .card .badges { display: flex; gap: 6px; flex-shrink: 0; align-items: center; }
227
+ .card .body {
228
+ display: none;
229
+ border-top: 1px solid var(--border);
230
+ background: var(--bg-muted);
231
+ }
232
+ .card.open .body { display: block; }
233
+
234
+ .card .actions {
235
+ display: flex; gap: 6px;
236
+ padding: 10px 14px;
237
+ border-bottom: 1px solid var(--border);
238
+ flex-wrap: wrap;
239
+ }
240
+ .btn {
241
+ font: inherit;
242
+ font-size: 12px;
243
+ padding: 5px 10px;
244
+ border: 1px solid var(--border);
245
+ border-radius: 6px;
246
+ background: var(--bg-elev);
247
+ color: var(--fg);
248
+ cursor: pointer;
249
+ display: inline-flex;
250
+ align-items: center;
251
+ gap: 5px;
252
+ transition: all .15s;
253
+ }
254
+ .btn:hover { border-color: var(--border-strong); background: var(--bg); }
255
+ .btn.copied { background: var(--pass-bg); color: var(--pass); border-color: transparent; }
256
+
257
+ /* Tabs */
258
+ .tabs { display: flex; border-bottom: 1px solid var(--border); padding: 0 14px; }
259
+ .tabs button {
260
+ font: inherit;
261
+ font-size: 12px;
262
+ background: transparent;
263
+ border: 0;
264
+ border-bottom: 2px solid transparent;
265
+ padding: 10px 12px;
266
+ margin-bottom: -1px;
267
+ cursor: pointer;
268
+ color: var(--fg-muted);
269
+ transition: all .15s;
270
+ }
271
+ .tabs button:hover { color: var(--fg); }
272
+ .tabs button.active { color: var(--fg); border-bottom-color: var(--fg); font-weight: 600; }
273
+ .panel { padding: 14px; display: none; }
274
+ .panel.active { display: block; }
275
+
276
+ .kv { display: grid; grid-template-columns: 110px 1fr; gap: 8px 14px; font-size: 12px; }
277
+ .kv dt { color: var(--fg-muted); }
278
+ .kv dd { margin: 0; word-break: break-all; }
279
+
280
+ pre.code {
281
+ margin: 6px 0 0;
282
+ padding: 12px 14px;
283
+ background: var(--bg-code);
284
+ border: 1px solid var(--border);
285
+ border-radius: 6px;
286
+ font-family: ui-monospace, SFMono-Regular, monospace;
287
+ font-size: 12px;
288
+ line-height: 1.55;
289
+ overflow-x: auto;
290
+ max-height: 360px;
291
+ overflow-y: auto;
292
+ white-space: pre;
293
+ }
294
+ .code-label {
295
+ font-size: 10px;
296
+ text-transform: uppercase;
297
+ letter-spacing: .08em;
298
+ color: var(--fg-muted);
299
+ margin-top: 12px;
300
+ }
301
+ .code-label:first-child { margin-top: 0; }
302
+
303
+ /* JSON syntax highlighting (regex-driven) */
304
+ .j-key { color: #b45309; }
305
+ .j-str { color: #047857; }
306
+ .j-num { color: #1d4ed8; }
307
+ .j-bool { color: #7c3aed; }
308
+ .j-null { color: #6b7280; }
309
+ @media (prefers-color-scheme: dark) {
310
+ .j-key { color: #fbbf24; }
311
+ .j-str { color: #34d399; }
312
+ .j-num { color: #60a5fa; }
313
+ .j-bool { color: #c084fc; }
314
+ .j-null { color: #9aa3b2; }
315
+ }
316
+
317
+ /* Assertion list */
318
+ .asserts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
319
+ .asserts li {
320
+ border: 1px solid var(--border);
321
+ border-radius: 6px;
322
+ padding: 8px 10px;
323
+ background: var(--bg-elev);
324
+ font-size: 12px;
325
+ }
326
+ .asserts li.failed { border-color: var(--fail); background: var(--fail-bg); }
327
+ .asserts li.passed { border-color: var(--pass); background: var(--pass-bg); }
328
+ .asserts .a-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
329
+ .asserts .a-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 6px; font-family: ui-monospace, SFMono-Regular, monospace; font-size: 11px; }
330
+ .asserts .a-diff .lbl { color: var(--fg-muted); }
331
+
332
+ /* Coverage matrix */
333
+ .cov-grid {
334
+ display: grid;
335
+ gap: 1px;
336
+ background: var(--border);
337
+ border: 1px solid var(--border);
338
+ border-radius: 8px;
339
+ overflow: hidden;
340
+ font-size: 11px;
341
+ font-family: ui-monospace, SFMono-Regular, monospace;
342
+ }
343
+ .cov-row { display: grid; grid-template-columns: 1.5fr repeat(5, 60px); }
344
+ .cov-cell {
345
+ background: var(--bg-elev);
346
+ padding: 6px 8px;
347
+ text-align: center;
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ }
352
+ .cov-cell.head { font-weight: 600; color: var(--fg-muted); text-transform: uppercase; font-size: 10px; }
353
+ .cov-cell.path { justify-content: flex-start; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
354
+ .cov-cell.s2 { background: var(--pass-bg); color: var(--pass); font-weight: 600; }
355
+ .cov-cell.s4 { background: var(--warn-bg); color: var(--warn); font-weight: 600; }
356
+ .cov-cell.s5 { background: var(--fail-bg); color: var(--fail); font-weight: 600; }
357
+ .cov-cell.serr { background: var(--fail); color: white; font-weight: 600; }
358
+ .cov-cell.empty { color: var(--border-strong); }
359
+ .cov-row.reasons { grid-template-columns: 2fr repeat(3, 1fr); }
360
+ .cov-cell.reasons { flex-wrap: wrap; gap: 3px; padding: 6px; min-height: 32px; }
361
+ .cov-cell.s2.reasons { background: var(--pass-bg); }
362
+ .cov-cell.s4.reasons { background: var(--warn-bg); }
363
+ .cov-cell.su { background: var(--bg-muted); color: var(--fg-muted); }
364
+ .cov-cell .rchip { background: rgba(255,255,255,0.6); padding: 1px 5px; border-radius: 3px; font-size: 10px; font-weight: 500; color: var(--fg); }
365
+ .method-mini { font-weight: 700; color: var(--accent); margin-right: 4px; }
366
+ .cov-cell.path .badge { margin-left: 6px; vertical-align: middle; }
367
+
368
+ /* Footer */
369
+ footer {
370
+ margin-top: 48px;
371
+ padding-top: 20px;
372
+ border-top: 1px solid var(--border);
373
+ font-size: 11px;
374
+ color: var(--fg-muted);
375
+ display: flex;
376
+ justify-content: space-between;
377
+ flex-wrap: wrap;
378
+ gap: 8px;
379
+ }
380
+ footer a { color: var(--accent); text-decoration: none; }
381
+ footer a:hover { text-decoration: underline; }
382
+
383
+ /* Empty state */
384
+ .empty {
385
+ background: var(--bg-elev);
386
+ border: 1px dashed var(--border);
387
+ border-radius: 10px;
388
+ padding: 32px 24px;
389
+ text-align: center;
390
+ color: var(--fg-muted);
391
+ font-size: 13px;
392
+ }
393
+
394
+ /* Print */
395
+ @media print {
396
+ .hero, .kpi, .card, .empty { box-shadow: none !important; }
397
+ .card { break-inside: avoid; }
398
+ .actions, .filters, .tabs button:not(.active) { display: none !important; }
399
+ .panel { display: block !important; padding: 8px 14px; }
400
+ body { background: white; color: black; }
401
+ }
402
+
403
+ @media (max-width: 720px) {
404
+ .hero { grid-template-columns: 1fr; }
405
+ .ring { width: 110px; height: 110px; justify-self: center; }
406
+ .cov-row { grid-template-columns: 1.5fr repeat(5, 1fr); }
407
+ }
408
+ `;
@@ -1,6 +1,6 @@
1
1
  import type { EndpointInfo } from "./types.ts";
2
2
 
3
- export const CHUNK_THRESHOLD = 30;
3
+ const CHUNK_THRESHOLD = 30;
4
4
 
5
5
  export interface ChunkPlan {
6
6
  totalEndpoints: number;
@@ -8,23 +8,31 @@ export interface ChunkPlan {
8
8
  chunks: Array<{ tag: string; count: number }>;
9
9
  }
10
10
 
11
- /** Group endpoints by their first tag, or "untagged" if none */
11
+ /**
12
+ * Group endpoints by their first tag. TASK-36: untagged endpoints fall
13
+ * back to per-resource grouping (first path segment), so `/audiences` and
14
+ * `/audiences/{id}` land in the same `audiences` group instead of being
15
+ * piled into a single `untagged` bucket. Endpoints whose path has no
16
+ * usable first segment (e.g. `/`) keep the legacy `untagged` key.
17
+ */
12
18
  export function groupEndpointsByTag(endpoints: EndpointInfo[]): Map<string, EndpointInfo[]> {
13
- const groups = new Map<string, EndpointInfo[]>();
14
- for (const ep of endpoints) {
15
- const tag = ep.tags[0] ?? "untagged";
16
- const list = groups.get(tag);
17
- if (list) {
18
- list.push(ep);
19
- } else {
20
- groups.set(tag, [ep]);
21
- }
19
+ return Map.groupBy(endpoints, (ep) => ep.tags[0] ?? resourceKeyFromPath(ep.path));
20
+ }
21
+
22
+ /** Extract the first non-templated path segment for tagless fallback. */
23
+ function resourceKeyFromPath(path: string): string {
24
+ const segments = path.split("/").filter(Boolean);
25
+ for (const seg of segments) {
26
+ // Skip templated segments like {id} — they aren't resource names.
27
+ if (seg.startsWith("{") && seg.endsWith("}")) continue;
28
+ if (seg.length === 0) continue;
29
+ return seg;
22
30
  }
23
- return groups;
31
+ return "untagged";
24
32
  }
25
33
 
26
34
  /** Decide whether to chunk, and return the tag breakdown */
27
- export function planChunks(endpoints: EndpointInfo[]): ChunkPlan {
35
+ function planChunks(endpoints: EndpointInfo[]): ChunkPlan {
28
36
  const groups = groupEndpointsByTag(endpoints);
29
37
  const chunks = Array.from(groups.entries())
30
38
  .map(([tag, eps]) => ({ tag, count: eps.length }))
@@ -37,13 +45,24 @@ export function planChunks(endpoints: EndpointInfo[]): ChunkPlan {
37
45
  };
38
46
  }
39
47
 
40
- /** Filter endpoints that have the given tag (case-insensitive) */
48
+ /**
49
+ * Filter endpoints by tag (case-insensitive). Accepts a single tag or a
50
+ * comma-separated list (TASK-239) so callers can run one generate pass for
51
+ * multiple tags instead of looping in the shell — looping prints
52
+ * "Next steps" N times and drowns real warnings.
53
+ */
41
54
  export function filterByTag(endpoints: EndpointInfo[], tag: string): EndpointInfo[] {
42
- const lower = tag.trim().toLowerCase();
43
- if (lower === "untagged") {
44
- return endpoints.filter(ep => ep.tags.length === 0);
45
- }
46
- return endpoints.filter(ep => ep.tags.some(t => t.trim().toLowerCase() === lower));
55
+ const wanted = tag
56
+ .split(",")
57
+ .map(t => t.trim().toLowerCase())
58
+ .filter(t => t.length > 0);
59
+ if (wanted.length === 0) return [];
60
+ const includeUntagged = wanted.includes("untagged");
61
+ const explicit = wanted.filter(t => t !== "untagged");
62
+ return endpoints.filter(ep => {
63
+ if (includeUntagged && ep.tags.length === 0) return true;
64
+ return ep.tags.some(t => explicit.includes(t.trim().toLowerCase()));
65
+ });
47
66
  }
48
67
 
49
68
  /** Collect the unique set of tags across all endpoints (sorted, original casing). */