sequant 1.18.0 → 1.20.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.
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Codebase conventions detector
3
+ *
4
+ * Deterministic detection of observable codebase patterns.
5
+ * No AI/ML — just file scanning and pattern matching.
6
+ */
7
+ import { readdir, stat } from "fs/promises";
8
+ import { join, extname } from "path";
9
+ import { fileExists, readFile, writeFile, ensureDir } from "./fs.js";
10
+ /** Path to conventions file */
11
+ export const CONVENTIONS_PATH = ".sequant/conventions.json";
12
+ /** Directories to skip during scanning */
13
+ const SKIP_DIRS = new Set([
14
+ "node_modules",
15
+ ".git",
16
+ "dist",
17
+ "build",
18
+ ".next",
19
+ ".nuxt",
20
+ ".output",
21
+ "__pycache__",
22
+ "target",
23
+ ".claude",
24
+ ".sequant",
25
+ "coverage",
26
+ ".turbo",
27
+ ".cache",
28
+ "vendor",
29
+ ]);
30
+ /**
31
+ * Collect source files up to a limit, skipping irrelevant directories
32
+ */
33
+ async function collectFiles(dir, extensions, maxFiles, depth = 0) {
34
+ if (depth > 5)
35
+ return [];
36
+ const results = [];
37
+ let entries;
38
+ try {
39
+ entries = await readdir(dir, { withFileTypes: true });
40
+ }
41
+ catch {
42
+ return results;
43
+ }
44
+ for (const entry of entries) {
45
+ if (results.length >= maxFiles)
46
+ break;
47
+ if (entry.isDirectory()) {
48
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith("."))
49
+ continue;
50
+ const sub = await collectFiles(join(dir, entry.name), extensions, maxFiles - results.length, depth + 1);
51
+ results.push(...sub);
52
+ }
53
+ else if (entry.isFile() && extensions.has(extname(entry.name))) {
54
+ results.push(join(dir, entry.name));
55
+ }
56
+ }
57
+ return results;
58
+ }
59
+ /**
60
+ * Count occurrences of a pattern in file contents
61
+ */
62
+ async function countPattern(files, pattern, maxFiles = 50) {
63
+ let count = 0;
64
+ for (const file of files.slice(0, maxFiles)) {
65
+ try {
66
+ const content = await readFile(file);
67
+ const matches = content.match(pattern);
68
+ if (matches)
69
+ count += matches.length;
70
+ }
71
+ catch {
72
+ // Skip unreadable files
73
+ }
74
+ }
75
+ return count;
76
+ }
77
+ /**
78
+ * Detect test file naming convention
79
+ */
80
+ async function detectTestPattern(root) {
81
+ const testFiles = await collectFiles(root, new Set([".ts", ".tsx", ".js", ".jsx"]), 500);
82
+ const dotTest = testFiles.filter((f) => /\.test\.[jt]sx?$/.test(f));
83
+ const dotSpec = testFiles.filter((f) => /\.spec\.[jt]sx?$/.test(f));
84
+ const underscoreTests = testFiles.filter((f) => f.includes("__tests__/"));
85
+ if (dotTest.length === 0 &&
86
+ dotSpec.length === 0 &&
87
+ underscoreTests.length === 0) {
88
+ return null;
89
+ }
90
+ let value;
91
+ let evidence;
92
+ if (dotTest.length >= dotSpec.length &&
93
+ dotTest.length >= underscoreTests.length) {
94
+ value = "*.test.ts";
95
+ evidence = `${dotTest.length} .test.* files found`;
96
+ }
97
+ else if (dotSpec.length >= underscoreTests.length) {
98
+ value = "*.spec.ts";
99
+ evidence = `${dotSpec.length} .spec.* files found`;
100
+ }
101
+ else {
102
+ value = "__tests__/";
103
+ evidence = `${underscoreTests.length} files in __tests__/ directories`;
104
+ }
105
+ return {
106
+ key: "testFilePattern",
107
+ label: "Test file pattern",
108
+ value,
109
+ source: "detected",
110
+ evidence,
111
+ };
112
+ }
113
+ /**
114
+ * Detect export style preference (named vs default)
115
+ */
116
+ async function detectExportStyle(root) {
117
+ const srcDir = join(root, "src");
118
+ const searchDir = (await fileExists(srcDir)) ? srcDir : root;
119
+ const files = await collectFiles(searchDir, new Set([".ts", ".tsx", ".js", ".jsx"]), 100);
120
+ if (files.length === 0)
121
+ return null;
122
+ const defaultExports = await countPattern(files, /export\s+default\b/g, 50);
123
+ const namedExports = await countPattern(files, /export\s+(?:async\s+)?(?:function|class|const|let|interface|type|enum)\b/g, 50);
124
+ if (defaultExports === 0 && namedExports === 0)
125
+ return null;
126
+ const total = defaultExports + namedExports;
127
+ const namedRatio = namedExports / total;
128
+ let value;
129
+ if (namedRatio > 0.7) {
130
+ value = "named";
131
+ }
132
+ else if (namedRatio < 0.3) {
133
+ value = "default";
134
+ }
135
+ else {
136
+ value = "mixed";
137
+ }
138
+ return {
139
+ key: "exportStyle",
140
+ label: "Export style",
141
+ value,
142
+ source: "detected",
143
+ evidence: `${namedExports} named, ${defaultExports} default exports`,
144
+ };
145
+ }
146
+ /**
147
+ * Detect async pattern preference
148
+ */
149
+ async function detectAsyncPattern(root) {
150
+ const srcDir = join(root, "src");
151
+ const searchDir = (await fileExists(srcDir)) ? srcDir : root;
152
+ const files = await collectFiles(searchDir, new Set([".ts", ".tsx", ".js", ".jsx"]), 100);
153
+ if (files.length === 0)
154
+ return null;
155
+ const awaitCount = await countPattern(files, /\bawait\b/g, 50);
156
+ const thenCount = await countPattern(files, /\.then\s*\(/g, 50);
157
+ if (awaitCount === 0 && thenCount === 0)
158
+ return null;
159
+ const total = awaitCount + thenCount;
160
+ const awaitRatio = awaitCount / total;
161
+ let value;
162
+ if (awaitRatio > 0.7) {
163
+ value = "async/await";
164
+ }
165
+ else if (awaitRatio < 0.3) {
166
+ value = "promise-chains";
167
+ }
168
+ else {
169
+ value = "mixed";
170
+ }
171
+ return {
172
+ key: "asyncPattern",
173
+ label: "Async pattern",
174
+ value,
175
+ source: "detected",
176
+ evidence: `${awaitCount} await, ${thenCount} .then() usages`,
177
+ };
178
+ }
179
+ /**
180
+ * Detect TypeScript strictness
181
+ */
182
+ async function detectTypeScriptConfig(root) {
183
+ const tsConfigPath = join(root, "tsconfig.json");
184
+ if (!(await fileExists(tsConfigPath)))
185
+ return null;
186
+ try {
187
+ const content = await readFile(tsConfigPath);
188
+ // Strip comments (single-line) for JSON parsing
189
+ const stripped = content.replace(/\/\/.*$/gm, "");
190
+ const config = JSON.parse(stripped);
191
+ const strict = config?.compilerOptions?.strict;
192
+ return {
193
+ key: "typescriptStrict",
194
+ label: "TypeScript strict mode",
195
+ value: strict ? "enabled" : "disabled",
196
+ source: "detected",
197
+ evidence: `tsconfig.json compilerOptions.strict = ${strict}`,
198
+ };
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ }
204
+ /**
205
+ * Detect source directory structure
206
+ */
207
+ async function detectSourceStructure(root) {
208
+ const candidates = [
209
+ { path: "src", label: "src/" },
210
+ { path: "lib", label: "lib/" },
211
+ { path: "app", label: "app/" },
212
+ { path: "pages", label: "pages/" },
213
+ ];
214
+ const found = [];
215
+ for (const c of candidates) {
216
+ const fullPath = join(root, c.path);
217
+ try {
218
+ const s = await stat(fullPath);
219
+ if (s.isDirectory())
220
+ found.push(c.label);
221
+ }
222
+ catch {
223
+ // doesn't exist
224
+ }
225
+ }
226
+ if (found.length === 0)
227
+ return null;
228
+ return {
229
+ key: "sourceStructure",
230
+ label: "Source directory structure",
231
+ value: found.join(", "),
232
+ source: "detected",
233
+ evidence: `Found directories: ${found.join(", ")}`,
234
+ };
235
+ }
236
+ /**
237
+ * Detect package manager from lockfiles
238
+ */
239
+ async function detectPackageManagerConvention(root) {
240
+ const lockfiles = [
241
+ { file: "bun.lockb", manager: "bun" },
242
+ { file: "bun.lock", manager: "bun" },
243
+ { file: "pnpm-lock.yaml", manager: "pnpm" },
244
+ { file: "yarn.lock", manager: "yarn" },
245
+ { file: "package-lock.json", manager: "npm" },
246
+ ];
247
+ for (const { file, manager } of lockfiles) {
248
+ if (await fileExists(join(root, file))) {
249
+ return {
250
+ key: "packageManager",
251
+ label: "Package manager",
252
+ value: manager,
253
+ source: "detected",
254
+ evidence: `Found ${file}`,
255
+ };
256
+ }
257
+ }
258
+ return null;
259
+ }
260
+ /**
261
+ * Detect indentation style from source files
262
+ */
263
+ async function detectIndentation(root) {
264
+ const srcDir = join(root, "src");
265
+ const searchDir = (await fileExists(srcDir)) ? srcDir : root;
266
+ const files = await collectFiles(searchDir, new Set([".ts", ".tsx", ".js", ".jsx"]), 30);
267
+ if (files.length === 0)
268
+ return null;
269
+ let twoSpace = 0;
270
+ let fourSpace = 0;
271
+ let tabs = 0;
272
+ for (const file of files.slice(0, 20)) {
273
+ try {
274
+ const content = await readFile(file);
275
+ const lines = content.split("\n").slice(0, 50);
276
+ for (const line of lines) {
277
+ if (/^\t/.test(line))
278
+ tabs++;
279
+ else if (/^ {2}[^ ]/.test(line))
280
+ twoSpace++;
281
+ else if (/^ {4}[^ ]/.test(line))
282
+ fourSpace++;
283
+ }
284
+ }
285
+ catch {
286
+ // skip
287
+ }
288
+ }
289
+ const total = twoSpace + fourSpace + tabs;
290
+ if (total === 0)
291
+ return null;
292
+ let value;
293
+ if (tabs > twoSpace && tabs > fourSpace) {
294
+ value = "tabs";
295
+ }
296
+ else if (twoSpace >= fourSpace) {
297
+ value = "2 spaces";
298
+ }
299
+ else {
300
+ value = "4 spaces";
301
+ }
302
+ return {
303
+ key: "indentation",
304
+ label: "Indentation",
305
+ value,
306
+ source: "detected",
307
+ evidence: `${twoSpace} two-space, ${fourSpace} four-space, ${tabs} tab-indented lines`,
308
+ };
309
+ }
310
+ /**
311
+ * Detect semicolon usage
312
+ */
313
+ async function detectSemicolons(root) {
314
+ const srcDir = join(root, "src");
315
+ const searchDir = (await fileExists(srcDir)) ? srcDir : root;
316
+ const files = await collectFiles(searchDir, new Set([".ts", ".tsx", ".js", ".jsx"]), 30);
317
+ if (files.length === 0)
318
+ return null;
319
+ let withSemicolon = 0;
320
+ let withoutSemicolon = 0;
321
+ for (const file of files.slice(0, 20)) {
322
+ try {
323
+ const content = await readFile(file);
324
+ const lines = content.split("\n");
325
+ for (const line of lines) {
326
+ const trimmed = line.trim();
327
+ // Skip empty lines, comments, opening/closing brackets
328
+ if (!trimmed ||
329
+ trimmed.startsWith("//") ||
330
+ trimmed.startsWith("/*") ||
331
+ trimmed.startsWith("*") ||
332
+ /^[{}()[\]]$/.test(trimmed) ||
333
+ /^import\s/.test(trimmed) ||
334
+ /^export\s/.test(trimmed))
335
+ continue;
336
+ if (trimmed.endsWith(";"))
337
+ withSemicolon++;
338
+ else if (trimmed.endsWith(")") ||
339
+ trimmed.endsWith('"') ||
340
+ trimmed.endsWith("'") ||
341
+ trimmed.endsWith("`") ||
342
+ /\w$/.test(trimmed))
343
+ withoutSemicolon++;
344
+ }
345
+ }
346
+ catch {
347
+ // skip
348
+ }
349
+ }
350
+ const total = withSemicolon + withoutSemicolon;
351
+ if (total === 0)
352
+ return null;
353
+ const semiRatio = withSemicolon / total;
354
+ const value = semiRatio > 0.6 ? "required" : semiRatio < 0.3 ? "omitted" : "mixed";
355
+ return {
356
+ key: "semicolons",
357
+ label: "Semicolons",
358
+ value,
359
+ source: "detected",
360
+ evidence: `${withSemicolon} with, ${withoutSemicolon} without semicolons`,
361
+ };
362
+ }
363
+ /**
364
+ * Detect component directory structure (for frontend projects)
365
+ */
366
+ async function detectComponentStructure(root) {
367
+ const candidates = [
368
+ "src/components",
369
+ "components",
370
+ "src/app",
371
+ "app",
372
+ "src/pages",
373
+ "pages",
374
+ ];
375
+ for (const candidate of candidates) {
376
+ const dirPath = join(root, candidate);
377
+ try {
378
+ const s = await stat(dirPath);
379
+ if (s.isDirectory()) {
380
+ return {
381
+ key: "componentDir",
382
+ label: "Component directory",
383
+ value: candidate + "/",
384
+ source: "detected",
385
+ evidence: `Directory exists: ${candidate}/`,
386
+ };
387
+ }
388
+ }
389
+ catch {
390
+ // doesn't exist
391
+ }
392
+ }
393
+ return null;
394
+ }
395
+ /**
396
+ * Run all convention detectors
397
+ */
398
+ export async function detectConventions(projectRoot) {
399
+ const detectors = [
400
+ detectTestPattern,
401
+ detectExportStyle,
402
+ detectAsyncPattern,
403
+ detectTypeScriptConfig,
404
+ detectSourceStructure,
405
+ detectPackageManagerConvention,
406
+ detectIndentation,
407
+ detectSemicolons,
408
+ detectComponentStructure,
409
+ ];
410
+ const results = [];
411
+ for (const detector of detectors) {
412
+ try {
413
+ const result = await detector(projectRoot);
414
+ if (result)
415
+ results.push(result);
416
+ }
417
+ catch {
418
+ // Skip failed detectors
419
+ }
420
+ }
421
+ return results;
422
+ }
423
+ /**
424
+ * Load existing conventions file
425
+ */
426
+ export async function loadConventions() {
427
+ if (!(await fileExists(CONVENTIONS_PATH)))
428
+ return null;
429
+ try {
430
+ const content = await readFile(CONVENTIONS_PATH);
431
+ return JSON.parse(content);
432
+ }
433
+ catch {
434
+ return null;
435
+ }
436
+ }
437
+ /**
438
+ * Save conventions, preserving manual entries
439
+ */
440
+ export async function saveConventions(detected) {
441
+ // Load existing to preserve manual entries
442
+ const existing = await loadConventions();
443
+ const manual = existing?.manual ?? {};
444
+ const detectedMap = {};
445
+ for (const c of detected) {
446
+ detectedMap[c.key] = c.value;
447
+ }
448
+ const conventions = {
449
+ detected: detectedMap,
450
+ manual,
451
+ detectedAt: new Date().toISOString(),
452
+ };
453
+ await ensureDir(".sequant");
454
+ await writeFile(CONVENTIONS_PATH, JSON.stringify(conventions, null, 2));
455
+ return conventions;
456
+ }
457
+ /**
458
+ * Get merged conventions (manual overrides detected)
459
+ */
460
+ export function getMergedConventions(file) {
461
+ return { ...file.detected, ...file.manual };
462
+ }
463
+ /**
464
+ * Format conventions for display
465
+ */
466
+ export function formatConventions(file) {
467
+ const lines = [];
468
+ lines.push("Detected conventions:");
469
+ const detected = Object.entries(file.detected);
470
+ if (detected.length === 0) {
471
+ lines.push(" (none)");
472
+ }
473
+ else {
474
+ for (const [key, value] of detected) {
475
+ lines.push(` ${key}: ${value}`);
476
+ }
477
+ }
478
+ const manual = Object.entries(file.manual);
479
+ if (manual.length > 0) {
480
+ lines.push("");
481
+ lines.push("Manual overrides:");
482
+ for (const [key, value] of manual) {
483
+ lines.push(` ${key}: ${value}`);
484
+ }
485
+ }
486
+ lines.push("");
487
+ lines.push(`Last detected: ${file.detectedAt}`);
488
+ return lines.join("\n");
489
+ }
490
+ /**
491
+ * Detect and save conventions in one call
492
+ */
493
+ export async function detectAndSaveConventions(projectRoot) {
494
+ const conventions = await detectConventions(projectRoot);
495
+ return saveConventions(conventions);
496
+ }
497
+ /**
498
+ * Format conventions as context for AI skills
499
+ */
500
+ export function formatConventionsForContext(file) {
501
+ const merged = getMergedConventions(file);
502
+ const entries = Object.entries(merged);
503
+ if (entries.length === 0)
504
+ return "";
505
+ const lines = ["## Codebase Conventions", ""];
506
+ for (const [key, value] of entries) {
507
+ lines.push(`- **${key}**: ${value}`);
508
+ }
509
+ return lines.join("\n");
510
+ }
@@ -6,7 +6,7 @@ export declare function isExecutable(path: string): Promise<boolean>;
6
6
  export declare function ensureDir(path: string): Promise<void>;
7
7
  export declare function readFile(path: string): Promise<string>;
8
8
  export declare function writeFile(path: string, content: string): Promise<void>;
9
- export declare function getFileStats(path: string): Promise<import("fs").Stats>;
9
+ export declare function getFileStats(path: string): Promise<import("node:fs").Stats>;
10
10
  /**
11
11
  * Check if a path is a symbolic link
12
12
  */
@@ -87,6 +87,14 @@ export interface RunSettings {
87
87
  * Default: true
88
88
  */
89
89
  retry: boolean;
90
+ /**
91
+ * Threshold for stale branch detection in pre-flight checks.
92
+ * If feature branch is more than this many commits behind main,
93
+ * QA/test skills block execution and recommend rebase.
94
+ * exec skill warns but doesn't block.
95
+ * Default: 5
96
+ */
97
+ staleBranchThreshold: number;
90
98
  }
91
99
  /**
92
100
  * Scope assessment threshold configuration
@@ -85,6 +85,7 @@ export const DEFAULT_SETTINGS = {
85
85
  rotation: DEFAULT_ROTATION_SETTINGS,
86
86
  mcp: true, // Enable MCP servers by default in headless mode
87
87
  retry: true, // Enable automatic retry with MCP fallback by default
88
+ staleBranchThreshold: 5, // Block QA/test if feature is >5 commits behind main
88
89
  },
89
90
  agents: DEFAULT_AGENT_SETTINGS,
90
91
  scopeAssessment: DEFAULT_SCOPE_ASSESSMENT_SETTINGS,
@@ -28,7 +28,7 @@ export interface StackConfig_Persisted {
28
28
  /**
29
29
  * Supported package managers
30
30
  */
31
- export type PackageManager = "npm" | "bun" | "yarn" | "pnpm";
31
+ export type PackageManager = "npm" | "bun" | "yarn" | "pnpm" | "pip" | "poetry" | "uv";
32
32
  /**
33
33
  * Package manager command configuration
34
34
  */
@@ -44,8 +44,10 @@ export interface PackageManagerConfig {
44
44
  export declare const PM_CONFIG: Record<PackageManager, PackageManagerConfig>;
45
45
  /**
46
46
  * Detect package manager from lockfiles
47
- * Priority: bun > yarn > pnpm > npm
47
+ * Priority: bun > yarn > pnpm > npm (for JS)
48
+ * Priority: uv > poetry > pip (for Python)
48
49
  * Falls back to npm if no lockfile found but package.json exists
50
+ * Falls back to pip if no lockfile found but pyproject.toml/requirements.txt exists
49
51
  */
50
52
  export declare function detectPackageManager(): Promise<PackageManager | null>;
51
53
  /**
@@ -48,6 +48,25 @@ export const PM_CONFIG = {
48
48
  install: "pnpm install",
49
49
  installSilent: "pnpm install --silent",
50
50
  },
51
+ // Python package managers
52
+ pip: {
53
+ run: "python -m",
54
+ exec: "python -m",
55
+ install: "pip install",
56
+ installSilent: "pip install -q",
57
+ },
58
+ poetry: {
59
+ run: "poetry run",
60
+ exec: "poetry run",
61
+ install: "poetry install",
62
+ installSilent: "poetry install -q",
63
+ },
64
+ uv: {
65
+ run: "uv run",
66
+ exec: "uvx",
67
+ install: "uv pip install",
68
+ installSilent: "uv pip install -q",
69
+ },
51
70
  };
52
71
  /**
53
72
  * Lockfile to package manager mapping (priority order: bun > yarn > pnpm > npm)
@@ -59,13 +78,23 @@ const LOCKFILE_PRIORITY = [
59
78
  { file: "pnpm-lock.yaml", pm: "pnpm" },
60
79
  { file: "package-lock.json", pm: "npm" },
61
80
  ];
81
+ /**
82
+ * Python lockfile to package manager mapping (priority order: uv > poetry > pip)
83
+ */
84
+ const PYTHON_LOCKFILE_PRIORITY = [
85
+ { file: "uv.lock", pm: "uv" },
86
+ { file: "poetry.lock", pm: "poetry" },
87
+ // requirements.txt is a fallback when no lockfile is found
88
+ ];
62
89
  /**
63
90
  * Detect package manager from lockfiles
64
- * Priority: bun > yarn > pnpm > npm
91
+ * Priority: bun > yarn > pnpm > npm (for JS)
92
+ * Priority: uv > poetry > pip (for Python)
65
93
  * Falls back to npm if no lockfile found but package.json exists
94
+ * Falls back to pip if no lockfile found but pyproject.toml/requirements.txt exists
66
95
  */
67
96
  export async function detectPackageManager() {
68
- // Check lockfiles in priority order
97
+ // Check JS lockfiles in priority order
69
98
  for (const { file, pm } of LOCKFILE_PRIORITY) {
70
99
  if (await fileExists(file)) {
71
100
  return pm;
@@ -75,7 +104,18 @@ export async function detectPackageManager() {
75
104
  if (await fileExists("package.json")) {
76
105
  return "npm";
77
106
  }
78
- // Not a Node.js project
107
+ // Check Python lockfiles in priority order
108
+ for (const { file, pm } of PYTHON_LOCKFILE_PRIORITY) {
109
+ if (await fileExists(file)) {
110
+ return pm;
111
+ }
112
+ }
113
+ // Fallback to pip if pyproject.toml or requirements.txt exists
114
+ if ((await fileExists("pyproject.toml")) ||
115
+ (await fileExists("requirements.txt"))) {
116
+ return "pip";
117
+ }
118
+ // Not a recognized project type
79
119
  return null;
80
120
  }
81
121
  /**
@@ -137,10 +137,10 @@ export type ACStatus = z.infer<typeof ACStatusSchema>;
137
137
  * Acceptance criteria verification method
138
138
  */
139
139
  export declare const ACVerificationMethodSchema: z.ZodEnum<{
140
+ manual: "manual";
140
141
  unit_test: "unit_test";
141
142
  integration_test: "integration_test";
142
143
  browser_test: "browser_test";
143
- manual: "manual";
144
144
  }>;
145
145
  export type ACVerificationMethod = z.infer<typeof ACVerificationMethodSchema>;
146
146
  /**
@@ -150,10 +150,10 @@ export declare const AcceptanceCriterionSchema: z.ZodObject<{
150
150
  id: z.ZodString;
151
151
  description: z.ZodString;
152
152
  verificationMethod: z.ZodEnum<{
153
+ manual: "manual";
153
154
  unit_test: "unit_test";
154
155
  integration_test: "integration_test";
155
156
  browser_test: "browser_test";
156
- manual: "manual";
157
157
  }>;
158
158
  status: z.ZodEnum<{
159
159
  pending: "pending";
@@ -173,10 +173,10 @@ export declare const AcceptanceCriteriaSchema: z.ZodObject<{
173
173
  id: z.ZodString;
174
174
  description: z.ZodString;
175
175
  verificationMethod: z.ZodEnum<{
176
+ manual: "manual";
176
177
  unit_test: "unit_test";
177
178
  integration_test: "integration_test";
178
179
  browser_test: "browser_test";
179
- manual: "manual";
180
180
  }>;
181
181
  status: z.ZodEnum<{
182
182
  pending: "pending";
@@ -252,10 +252,10 @@ export declare const IssueStateSchema: z.ZodObject<{
252
252
  id: z.ZodString;
253
253
  description: z.ZodString;
254
254
  verificationMethod: z.ZodEnum<{
255
+ manual: "manual";
255
256
  unit_test: "unit_test";
256
257
  integration_test: "integration_test";
257
258
  browser_test: "browser_test";
258
- manual: "manual";
259
259
  }>;
260
260
  status: z.ZodEnum<{
261
261
  pending: "pending";
@@ -377,10 +377,10 @@ export declare const WorkflowStateSchema: z.ZodObject<{
377
377
  id: z.ZodString;
378
378
  description: z.ZodString;
379
379
  verificationMethod: z.ZodEnum<{
380
+ manual: "manual";
380
381
  unit_test: "unit_test";
381
382
  integration_test: "integration_test";
382
383
  browser_test: "browser_test";
383
- manual: "manual";
384
384
  }>;
385
385
  status: z.ZodEnum<{
386
386
  pending: "pending";