@kirrosh/zond 0.7.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 (102) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/LICENSE +21 -0
  3. package/README.md +130 -0
  4. package/package.json +53 -0
  5. package/src/bun-types.d.ts +5 -0
  6. package/src/cli/commands/add-api.ts +51 -0
  7. package/src/cli/commands/ai-generate.ts +106 -0
  8. package/src/cli/commands/chat.ts +43 -0
  9. package/src/cli/commands/ci-init.ts +163 -0
  10. package/src/cli/commands/collections.ts +41 -0
  11. package/src/cli/commands/compare.ts +129 -0
  12. package/src/cli/commands/coverage.ts +156 -0
  13. package/src/cli/commands/doctor.ts +127 -0
  14. package/src/cli/commands/init.ts +84 -0
  15. package/src/cli/commands/mcp.ts +16 -0
  16. package/src/cli/commands/run.ts +156 -0
  17. package/src/cli/commands/runs.ts +108 -0
  18. package/src/cli/commands/serve.ts +22 -0
  19. package/src/cli/commands/update.ts +142 -0
  20. package/src/cli/commands/validate.ts +18 -0
  21. package/src/cli/index.ts +529 -0
  22. package/src/cli/output.ts +24 -0
  23. package/src/cli/runtime.ts +7 -0
  24. package/src/core/agent/agent-loop.ts +116 -0
  25. package/src/core/agent/context-manager.ts +41 -0
  26. package/src/core/agent/system-prompt.ts +28 -0
  27. package/src/core/agent/tools/diagnose-failure.ts +51 -0
  28. package/src/core/agent/tools/explore-api.ts +40 -0
  29. package/src/core/agent/tools/index.ts +46 -0
  30. package/src/core/agent/tools/query-results.ts +40 -0
  31. package/src/core/agent/tools/run-tests.ts +38 -0
  32. package/src/core/agent/tools/send-request.ts +44 -0
  33. package/src/core/agent/tools/validate-tests.ts +23 -0
  34. package/src/core/agent/types.ts +22 -0
  35. package/src/core/diagnostics/failure-hints.ts +63 -0
  36. package/src/core/generator/ai/ai-generator.ts +61 -0
  37. package/src/core/generator/ai/llm-client.ts +159 -0
  38. package/src/core/generator/ai/output-parser.ts +307 -0
  39. package/src/core/generator/ai/prompt-builder.ts +153 -0
  40. package/src/core/generator/ai/types.ts +56 -0
  41. package/src/core/generator/chunker.ts +47 -0
  42. package/src/core/generator/coverage-scanner.ts +87 -0
  43. package/src/core/generator/data-factory.ts +115 -0
  44. package/src/core/generator/endpoint-warnings.ts +43 -0
  45. package/src/core/generator/index.ts +12 -0
  46. package/src/core/generator/openapi-reader.ts +143 -0
  47. package/src/core/generator/schema-utils.ts +52 -0
  48. package/src/core/generator/serializer.ts +189 -0
  49. package/src/core/generator/types.ts +48 -0
  50. package/src/core/parser/filter.ts +14 -0
  51. package/src/core/parser/index.ts +21 -0
  52. package/src/core/parser/schema.ts +175 -0
  53. package/src/core/parser/types.ts +52 -0
  54. package/src/core/parser/variables.ts +154 -0
  55. package/src/core/parser/yaml-parser.ts +85 -0
  56. package/src/core/reporter/console.ts +175 -0
  57. package/src/core/reporter/index.ts +23 -0
  58. package/src/core/reporter/json.ts +9 -0
  59. package/src/core/reporter/junit.ts +78 -0
  60. package/src/core/reporter/types.ts +12 -0
  61. package/src/core/runner/assertions.ts +173 -0
  62. package/src/core/runner/execute-run.ts +97 -0
  63. package/src/core/runner/executor.ts +183 -0
  64. package/src/core/runner/http-client.ts +69 -0
  65. package/src/core/runner/index.ts +12 -0
  66. package/src/core/runner/types.ts +48 -0
  67. package/src/core/setup-api.ts +113 -0
  68. package/src/core/utils.ts +9 -0
  69. package/src/db/queries.ts +774 -0
  70. package/src/db/schema.ts +159 -0
  71. package/src/mcp/descriptions.ts +88 -0
  72. package/src/mcp/server.ts +52 -0
  73. package/src/mcp/tools/ci-init.ts +54 -0
  74. package/src/mcp/tools/coverage-analysis.ts +141 -0
  75. package/src/mcp/tools/describe-endpoint.ts +241 -0
  76. package/src/mcp/tools/explore-api.ts +84 -0
  77. package/src/mcp/tools/generate-and-save.ts +129 -0
  78. package/src/mcp/tools/generate-missing-tests.ts +91 -0
  79. package/src/mcp/tools/generate-tests-guide.ts +391 -0
  80. package/src/mcp/tools/manage-server.ts +86 -0
  81. package/src/mcp/tools/query-db.ts +255 -0
  82. package/src/mcp/tools/run-tests.ts +71 -0
  83. package/src/mcp/tools/save-test-suite.ts +218 -0
  84. package/src/mcp/tools/send-request.ts +63 -0
  85. package/src/mcp/tools/set-work-dir.ts +35 -0
  86. package/src/mcp/tools/setup-api.ts +84 -0
  87. package/src/mcp/tools/validate-tests.ts +43 -0
  88. package/src/tui/chat-ui.ts +150 -0
  89. package/src/web/data/collection-state.ts +360 -0
  90. package/src/web/routes/api.ts +234 -0
  91. package/src/web/routes/dashboard.ts +313 -0
  92. package/src/web/routes/runs.ts +64 -0
  93. package/src/web/schemas.ts +121 -0
  94. package/src/web/server.ts +134 -0
  95. package/src/web/static/htmx.min.js +1 -0
  96. package/src/web/static/style.css +827 -0
  97. package/src/web/views/endpoints-tab.ts +170 -0
  98. package/src/web/views/health-strip.ts +92 -0
  99. package/src/web/views/layout.ts +48 -0
  100. package/src/web/views/results.ts +209 -0
  101. package/src/web/views/runs-tab.ts +126 -0
  102. package/src/web/views/suites-tab.ts +153 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Suites tab: all YAML files on disk with run status and step details.
3
+ */
4
+
5
+ import type { CollectionState, SuiteViewState, StepViewState } from "../data/collection-state.ts";
6
+ import { escapeHtml } from "./layout.ts";
7
+ import { basename } from "node:path";
8
+
9
+ export function renderSuitesTab(state: CollectionState): string {
10
+ if (state.suites.length === 0) {
11
+ return `<div class="tab-empty">No test suites found on disk. Generate tests with <code>generate_tests_guide</code> or <code>generate_and_save</code>.</div>`;
12
+ }
13
+
14
+ const rows = state.suites.map((s, i) => renderSuiteRow(s, i)).join("");
15
+ return `<div class="suite-list">${rows}</div>`;
16
+ }
17
+
18
+ function renderSuiteRow(suite: SuiteViewState, index: number): string {
19
+ const detailId = `suite-detail-${index}`;
20
+
21
+ if (suite.status === "parse_error") {
22
+ return `
23
+ <div class="suite-row suite-error-row">
24
+ <div class="suite-info">
25
+ <div class="suite-name">${escapeHtml(basename(suite.filePath || suite.name))}</div>
26
+ <div class="suite-desc" style="color:var(--fail);">${escapeHtml(suite.parseError ?? "Parse error")}</div>
27
+ </div>
28
+ <div class="suite-tags"></div>
29
+ <div class="suite-steps-count">-</div>
30
+ <div class="suite-result fail">error</div>
31
+ </div>`;
32
+ }
33
+
34
+ const tags = suite.tags.map(t => {
35
+ const tagClass = t === "smoke" ? "smoke" : t === "crud" ? "crud" : t === "auth" ? "auth" : t === "destructive" ? "destructive" : "";
36
+ return `<span class="tag-pill ${tagClass}">${escapeHtml(t)}</span>`;
37
+ }).join("");
38
+
39
+ const total = suite.runResult
40
+ ? suite.runResult.passed + suite.runResult.failed + suite.runResult.skipped
41
+ : 0;
42
+
43
+ let resultHtml: string;
44
+ if (suite.status === "passed") {
45
+ resultHtml = `<div class="suite-result pass">${suite.runResult!.passed}/${total} &#10003;</div>`;
46
+ } else if (suite.status === "failed") {
47
+ resultHtml = `<div class="suite-result fail">${suite.runResult!.passed}/${total} &#10007;</div>`;
48
+ } else {
49
+ resultHtml = `<div class="suite-result not-run">not run</div>`;
50
+ }
51
+
52
+ // Step detail rows
53
+ const stepsHtml = suite.steps.length > 0
54
+ ? suite.steps.map((step, si) => renderStepRow(step, index, si)).join("")
55
+ : `<div style="font-size:0.75rem;color:var(--text-dim);padding:0.5rem;">No run results yet</div>`;
56
+
57
+ return `
58
+ <div class="suite-row"
59
+ onclick="var d=document.getElementById('${detailId}');d.style.display=d.style.display==='none'?'block':'none'">
60
+ <div class="suite-info">
61
+ <div class="suite-name">${escapeHtml(suite.name)}</div>
62
+ ${suite.description ? `<div class="suite-desc">${escapeHtml(suite.description)}</div>` : ""}
63
+ </div>
64
+ <div class="suite-tags">${tags}</div>
65
+ <div class="suite-steps-count">${suite.stepCount} steps</div>
66
+ ${resultHtml}
67
+ </div>
68
+ <div class="suite-detail" id="${detailId}" style="display:none">
69
+ ${stepsHtml}
70
+ </div>`;
71
+ }
72
+
73
+ function renderStepRow(step: StepViewState, suiteIdx: number, stepIdx: number): string {
74
+ const icon = step.status === "pass"
75
+ ? '<span class="step-icon pass">&#10003;</span>'
76
+ : step.status === "fail" || step.status === "error"
77
+ ? '<span class="step-icon fail">&#10007;</span>'
78
+ : '<span class="step-icon skip">&#9675;</span>';
79
+
80
+ const labelStyle = step.status === "fail" || step.status === "error"
81
+ ? ' style="color:var(--fail);"'
82
+ : step.status === "skip"
83
+ ? ' style="color:var(--skip);"'
84
+ : "";
85
+
86
+ const duration = step.durationMs != null
87
+ ? `<span class="step-duration">${step.durationMs}ms</span>`
88
+ : `<span class="step-duration">-</span>`;
89
+
90
+ // Captures
91
+ const captureHtml = step.captures && Object.keys(step.captures).length > 0
92
+ ? `<span class="step-captures">${Object.entries(step.captures).map(([k, v]) =>
93
+ `<span class="capture-pill">${escapeHtml(k)} = ${escapeHtml(String(v))}</span>`
94
+ ).join("")}</span>`
95
+ : `<span class="step-captures"></span>`;
96
+
97
+ const detailId = `s-${suiteIdx}-step-${stepIdx}`;
98
+ const hasDetail = (step.status === "fail" || step.status === "error") &&
99
+ ((step.assertions && step.assertions.length > 0) || step.hint || step.responseBody);
100
+
101
+ const clickHandler = hasDetail
102
+ ? ` onclick="event.stopPropagation();var d=document.getElementById('${detailId}');d.style.display=d.style.display==='none'?'block':'none'"`
103
+ : "";
104
+
105
+ let detailPanel = "";
106
+ if (hasDetail) {
107
+ let detailContent = "";
108
+
109
+ // Request info
110
+ if (step.requestMethod && step.requestUrl) {
111
+ detailContent += `<div style="font-family:var(--font-mono);font-size:0.75rem;color:var(--text-dim);margin-bottom:0.4rem;">
112
+ ${escapeHtml(step.requestMethod)} ${escapeHtml(step.requestUrl)}</div>`;
113
+ }
114
+
115
+ // Assertions
116
+ if (step.assertions && step.assertions.length > 0) {
117
+ detailContent += step.assertions.map(a => {
118
+ const aIcon = a.passed
119
+ ? '<span class="assertion-icon pass">&#10003;</span>'
120
+ : '<span class="assertion-icon fail">&#10007;</span>';
121
+ const actual = !a.passed && a.actual !== undefined
122
+ ? ` <span class="assertion-actual">(got ${escapeHtml(JSON.stringify(a.actual))})</span>` : "";
123
+ return `<div class="assertion-row">${aIcon} <span class="assertion-field">${escapeHtml(a.field)}:</span> <span class="assertion-rule">${escapeHtml(a.rule)}</span>${actual}</div>`;
124
+ }).join("");
125
+ }
126
+
127
+ // Error message
128
+ if (step.errorMessage) {
129
+ detailContent += `<div style="font-family:var(--font-mono);font-size:0.75rem;color:var(--fail);margin-top:0.25rem;">${escapeHtml(step.errorMessage)}</div>`;
130
+ }
131
+
132
+ // Failure hint
133
+ if (step.hint) {
134
+ detailContent += `<div class="failure-hint"><span>&#9888;</span> ${escapeHtml(step.hint)}</div>`;
135
+ }
136
+
137
+ // Response body toggle
138
+ if (step.responseBody) {
139
+ const truncated = step.responseBody.length > 2000 ? step.responseBody.slice(0, 2000) + "..." : step.responseBody;
140
+ detailContent += `<div class="req-res-toggle" onclick="event.stopPropagation();var b=this.nextElementSibling;b.style.display=b.style.display==='none'?'block':'none'">&#9660; Response Body</div>
141
+ <div class="req-res-body" style="display:none;"><pre style="font-size:0.7rem;margin:0.25rem 0;">${escapeHtml(truncated)}</pre></div>`;
142
+ }
143
+
144
+ detailPanel = `<div class="step-detail-panel" id="${detailId}" style="display:none">${detailContent}</div>`;
145
+ }
146
+
147
+ return `<div class="step-row"${clickHandler}>
148
+ ${icon}
149
+ <span class="step-label"${labelStyle}>${escapeHtml(step.name)}</span>
150
+ ${captureHtml}
151
+ ${duration}
152
+ </div>${detailPanel}`;
153
+ }