@vibecheckai/cli 3.0.2 → 3.0.3

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 (68) hide show
  1. package/package.json +9 -1
  2. package/bin/cli-hygiene.js +0 -241
  3. package/bin/guardrail.js +0 -834
  4. package/bin/runners/cli-utils.js +0 -1070
  5. package/bin/runners/context/ai-task-decomposer.js +0 -337
  6. package/bin/runners/context/analyzer.js +0 -462
  7. package/bin/runners/context/api-contracts.js +0 -427
  8. package/bin/runners/context/context-diff.js +0 -342
  9. package/bin/runners/context/context-pruner.js +0 -291
  10. package/bin/runners/context/dependency-graph.js +0 -414
  11. package/bin/runners/context/generators/claude.js +0 -107
  12. package/bin/runners/context/generators/codex.js +0 -108
  13. package/bin/runners/context/generators/copilot.js +0 -119
  14. package/bin/runners/context/generators/cursor.js +0 -514
  15. package/bin/runners/context/generators/mcp.js +0 -151
  16. package/bin/runners/context/generators/windsurf.js +0 -180
  17. package/bin/runners/context/git-context.js +0 -302
  18. package/bin/runners/context/index.js +0 -1042
  19. package/bin/runners/context/insights.js +0 -173
  20. package/bin/runners/context/mcp-server/generate-rules.js +0 -337
  21. package/bin/runners/context/mcp-server/index.js +0 -1176
  22. package/bin/runners/context/mcp-server/package.json +0 -24
  23. package/bin/runners/context/memory.js +0 -200
  24. package/bin/runners/context/monorepo.js +0 -215
  25. package/bin/runners/context/multi-repo-federation.js +0 -404
  26. package/bin/runners/context/patterns.js +0 -253
  27. package/bin/runners/context/proof-context.js +0 -972
  28. package/bin/runners/context/security-scanner.js +0 -303
  29. package/bin/runners/context/semantic-search.js +0 -350
  30. package/bin/runners/context/shared.js +0 -264
  31. package/bin/runners/context/team-conventions.js +0 -310
  32. package/bin/runners/lib/ai-bridge.js +0 -416
  33. package/bin/runners/lib/analysis-core.js +0 -271
  34. package/bin/runners/lib/analyzers.js +0 -541
  35. package/bin/runners/lib/audit-bridge.js +0 -391
  36. package/bin/runners/lib/auth-truth.js +0 -193
  37. package/bin/runners/lib/auth.js +0 -215
  38. package/bin/runners/lib/backup.js +0 -62
  39. package/bin/runners/lib/billing.js +0 -107
  40. package/bin/runners/lib/claims.js +0 -118
  41. package/bin/runners/lib/cli-ui.js +0 -540
  42. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  43. package/bin/runners/lib/compliance-bridge.js +0 -165
  44. package/bin/runners/lib/contracts/auth-contract.js +0 -194
  45. package/bin/runners/lib/contracts/env-contract.js +0 -178
  46. package/bin/runners/lib/contracts/external-contract.js +0 -198
  47. package/bin/runners/lib/contracts/guard.js +0 -168
  48. package/bin/runners/lib/contracts/index.js +0 -89
  49. package/bin/runners/lib/contracts/plan-validator.js +0 -311
  50. package/bin/runners/lib/contracts/route-contract.js +0 -192
  51. package/bin/runners/lib/detect.js +0 -89
  52. package/bin/runners/lib/doctor/autofix.js +0 -254
  53. package/bin/runners/lib/doctor/index.js +0 -37
  54. package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
  55. package/bin/runners/lib/doctor/modules/index.js +0 -46
  56. package/bin/runners/lib/doctor/modules/network.js +0 -250
  57. package/bin/runners/lib/doctor/modules/project.js +0 -312
  58. package/bin/runners/lib/doctor/modules/runtime.js +0 -224
  59. package/bin/runners/lib/doctor/modules/security.js +0 -348
  60. package/bin/runners/lib/doctor/modules/system.js +0 -213
  61. package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
  62. package/bin/runners/lib/doctor/reporter.js +0 -262
  63. package/bin/runners/lib/doctor/service.js +0 -262
  64. package/bin/runners/lib/doctor/types.js +0 -113
  65. package/bin/runners/lib/doctor/ui.js +0 -263
  66. package/bin/runners/lib/doctor-enhanced.js +0 -233
  67. package/bin/runners/lib/doctor-v2.js +0 -608
  68. package/bin/runners/lib/enforcement.js +0 -72
@@ -1,342 +0,0 @@
1
- /**
2
- * Context Diff Module
3
- * Tracks changes in context between generations
4
- */
5
-
6
- const fs = require("fs");
7
- const path = require("path");
8
- const crypto = require("crypto");
9
-
10
- /**
11
- * Generate hash for context snapshot
12
- */
13
- function generateContextHash(context) {
14
- const content = JSON.stringify(context, null, 2);
15
- return crypto.createHash("sha256").update(content).digest("hex");
16
- }
17
-
18
- /**
19
- * Save context snapshot
20
- */
21
- function saveSnapshot(projectPath, context) {
22
- const vibecheckDir = path.join(projectPath, ".vibecheck");
23
- if (!fs.existsSync(vibecheckDir)) {
24
- fs.mkdirSync(vibecheckDir, { recursive: true });
25
- }
26
-
27
- const snapshot = {
28
- timestamp: new Date().toISOString(),
29
- hash: generateContextHash(context),
30
- context: {
31
- version: context.version,
32
- project: context.project,
33
- techStack: context.techStack,
34
- structure: {
35
- directories: context.structure?.directories || [],
36
- componentsCount: context.structure?.components?.length || 0,
37
- apiRoutesCount: context.structure?.apiRoutes?.length || 0,
38
- },
39
- patterns: {
40
- hooks: context.patterns?.hooks || [],
41
- stateManagement: context.patterns?.stateManagement,
42
- validation: context.patterns?.validation,
43
- },
44
- monorepo: context.monorepo,
45
- },
46
- };
47
-
48
- const snapshotFile = path.join(vibecheckDir, "context-snapshot.json");
49
- fs.writeFileSync(snapshotFile, JSON.stringify(snapshot, null, 2));
50
-
51
- return snapshot;
52
- }
53
-
54
- /**
55
- * Load previous snapshot
56
- */
57
- function loadSnapshot(projectPath) {
58
- const snapshotFile = path.join(projectPath, ".vibecheck", "context-snapshot.json");
59
-
60
- if (!fs.existsSync(snapshotFile)) {
61
- return null;
62
- }
63
-
64
- try {
65
- return JSON.parse(fs.readFileSync(snapshotFile, "utf-8"));
66
- } catch {
67
- return null;
68
- }
69
- }
70
-
71
- /**
72
- * Generate diff between contexts
73
- */
74
- function generateContextDiff(previous, current) {
75
- if (!previous) {
76
- return {
77
- isFirstRun: true,
78
- changes: {
79
- added: [],
80
- removed: [],
81
- modified: [],
82
- },
83
- };
84
- }
85
-
86
- const diff = {
87
- timestamp: new Date().toISOString(),
88
- previousTimestamp: previous.timestamp,
89
- changes: {
90
- added: [],
91
- removed: [],
92
- modified: [],
93
- },
94
- summary: {
95
- totalChanges: 0,
96
- breakingChanges: 0,
97
- additions: 0,
98
- removals: 0,
99
- },
100
- };
101
-
102
- // Compare tech stack
103
- if (previous.context.techStack && current.techStack) {
104
- for (const [key, value] of Object.entries(current.techStack)) {
105
- if (previous.context.techStack[key] !== value) {
106
- diff.changes.modified.push({
107
- type: "techStack",
108
- field: key,
109
- from: previous.context.techStack[key],
110
- to: value,
111
- });
112
- diff.summary.totalChanges++;
113
- }
114
- }
115
- }
116
-
117
- // Compare directories
118
- const prevDirs = new Set(previous.context.structure?.directories || []);
119
- const currDirs = new Set(current.structure?.directories || []);
120
-
121
- for (const dir of currDirs) {
122
- if (!prevDirs.has(dir)) {
123
- diff.changes.added.push({
124
- type: "directory",
125
- name: dir,
126
- });
127
- diff.summary.additions++;
128
- diff.summary.totalChanges++;
129
- }
130
- }
131
-
132
- for (const dir of prevDirs) {
133
- if (!currDirs.has(dir)) {
134
- diff.changes.removed.push({
135
- type: "directory",
136
- name: dir,
137
- });
138
- diff.summary.removals++;
139
- diff.summary.totalChanges++;
140
- }
141
- }
142
-
143
- // Compare components
144
- const prevComponents = new Set(previous.context.structure?.components || []);
145
- const currComponents = new Set(current.structure?.components || []);
146
-
147
- for (const comp of currComponents) {
148
- if (!prevComponents.has(comp)) {
149
- diff.changes.added.push({
150
- type: "component",
151
- name: comp,
152
- });
153
- diff.summary.additions++;
154
- diff.summary.totalChanges++;
155
- }
156
- }
157
-
158
- for (const comp of prevComponents) {
159
- if (!currComponents.has(comp)) {
160
- diff.changes.removed.push({
161
- type: "component",
162
- name: comp,
163
- });
164
- diff.summary.removals++;
165
- diff.summary.totalChanges++;
166
- }
167
- }
168
-
169
- // Compare API routes
170
- const prevRoutes = new Set(previous.context.structure?.apiRoutes || []);
171
- const currRoutes = new Set(current.structure?.apiRoutes || []);
172
-
173
- for (const route of currRoutes) {
174
- if (!prevRoutes.has(route)) {
175
- diff.changes.added.push({
176
- type: "apiRoute",
177
- name: route,
178
- });
179
- diff.summary.additions++;
180
- diff.summary.totalChanges++;
181
- }
182
- }
183
-
184
- for (const route of prevRoutes) {
185
- if (!currRoutes.has(route)) {
186
- diff.changes.removed.push({
187
- type: "apiRoute",
188
- name: route,
189
- });
190
- diff.summary.removals++;
191
- diff.summary.totalChanges++;
192
- }
193
- }
194
-
195
- // Compare patterns
196
- const prevHooks = new Set(previous.context.patterns?.hooks || []);
197
- const currHooks = new Set(current.patterns?.hooks || []);
198
-
199
- for (const hook of currHooks) {
200
- if (!prevHooks.has(hook)) {
201
- diff.changes.added.push({
202
- type: "hook",
203
- name: hook,
204
- });
205
- diff.summary.additions++;
206
- diff.summary.totalChanges++;
207
- }
208
- }
209
-
210
- // Check for breaking changes
211
- if (previous.context.patterns?.stateManagement !== current.patterns?.stateManagement) {
212
- diff.changes.modified.push({
213
- type: "breaking",
214
- field: "stateManagement",
215
- from: previous.context.patterns?.stateManagement,
216
- to: current.patterns?.stateManagement,
217
- });
218
- diff.summary.breakingChanges++;
219
- diff.summary.totalChanges++;
220
- }
221
-
222
- if (previous.context.patterns?.validation !== current.patterns?.validation) {
223
- diff.changes.modified.push({
224
- type: "breaking",
225
- field: "validation",
226
- from: previous.context.patterns?.validation,
227
- to: current.patterns?.validation,
228
- });
229
- diff.summary.breakingChanges++;
230
- diff.summary.totalChanges++;
231
- }
232
-
233
- return diff;
234
- }
235
-
236
- /**
237
- * Generate diff report
238
- */
239
- function generateDiffReport(diff) {
240
- if (diff.isFirstRun) {
241
- return `
242
- # First Context Generation
243
-
244
- This is the first time context has been generated for this project.
245
- `;
246
- }
247
-
248
- let report = `# Context Changes Report
249
- Generated: ${new Date(diff.timestamp).toLocaleString()}
250
- Since: ${new Date(diff.previousTimestamp).toLocaleString()}
251
-
252
- ## Summary
253
- - Total Changes: ${diff.summary.totalChanges}
254
- - Additions: ${diff.summary.additions}
255
- - Removals: ${diff.summary.removals}
256
- - Breaking Changes: ${diff.summary.breakingChanges}
257
-
258
- `;
259
-
260
- if (diff.changes.added.length > 0) {
261
- report += "## Added\n\n";
262
- for (const change of diff.changes.added) {
263
- const icon = {
264
- directory: "📁",
265
- component: "🧩",
266
- apiRoute: "🔌",
267
- hook: "🪝",
268
- }[change.type] || "➕";
269
-
270
- report += `- ${icon} **${change.type}**: ${change.name}\n`;
271
- }
272
- report += "\n";
273
- }
274
-
275
- if (diff.changes.removed.length > 0) {
276
- report += "## Removed\n\n";
277
- for (const change of diff.changes.removed) {
278
- const icon = {
279
- directory: "📁",
280
- component: "🧩",
281
- apiRoute: "🔌",
282
- hook: "🪝",
283
- }[change.type] || "➖";
284
-
285
- report += `- ${icon} **${change.type}**: ${change.name}\n`;
286
- }
287
- report += "\n";
288
- }
289
-
290
- if (diff.changes.modified.length > 0) {
291
- report += "## Modified\n\n";
292
- for (const change of diff.changes.modified) {
293
- const icon = change.type === "breaking" ? "⚠️" : "🔄";
294
- report += `- ${icon} **${change.field}**: \`${change.from}\` → \`${change.to}\`\n`;
295
- }
296
- report += "\n";
297
- }
298
-
299
- if (diff.summary.breakingChanges > 0) {
300
- report += "## ⚠️ Breaking Changes\n\n";
301
- report += "Breaking changes detected. Review AI rules files to ensure compatibility.\n\n";
302
- }
303
-
304
- return report;
305
- }
306
-
307
- /**
308
- * Save diff report
309
- */
310
- function saveDiffReport(projectPath, diff) {
311
- const vibecheckDir = path.join(projectPath, ".vibecheck");
312
- if (!fs.existsSync(vibecheckDir)) {
313
- fs.mkdirSync(vibecheckDir, { recursive: true });
314
- }
315
-
316
- const report = generateDiffReport(diff);
317
- const reportFile = path.join(vibecheckDir, "context-diff.md");
318
-
319
- fs.writeFileSync(reportFile, report);
320
-
321
- return reportFile;
322
- }
323
-
324
- /**
325
- * Check if context needs regeneration
326
- */
327
- function needsRegeneration(projectPath, currentContext) {
328
- const previous = loadSnapshot(projectPath);
329
- if (!previous) return true;
330
-
331
- const currentHash = generateContextHash(currentContext);
332
- return previous.hash !== currentHash;
333
- }
334
-
335
- module.exports = {
336
- saveSnapshot,
337
- loadSnapshot,
338
- generateContextDiff,
339
- generateDiffReport,
340
- saveDiffReport,
341
- needsRegeneration,
342
- };
@@ -1,291 +0,0 @@
1
- /**
2
- * Smart Context Pruning Module
3
- * Reduces context to most relevant subset based on current file/task
4
- */
5
-
6
- const fs = require("fs");
7
- const path = require("path");
8
-
9
- /**
10
- * Find files recursively (local helper)
11
- */
12
- function findFiles(dir, extensions, maxDepth = 5, currentDepth = 0) {
13
- if (currentDepth >= maxDepth || !fs.existsSync(dir)) return [];
14
-
15
- const files = [];
16
- try {
17
- const entries = fs.readdirSync(dir, { withFileTypes: true });
18
- for (const entry of entries) {
19
- const fullPath = path.join(dir, entry.name);
20
- if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
21
- files.push(...findFiles(fullPath, extensions, maxDepth, currentDepth + 1));
22
- } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
23
- files.push(fullPath);
24
- }
25
- }
26
- } catch {}
27
- return files;
28
- }
29
-
30
- /**
31
- * Calculate relevance score for a file based on context
32
- */
33
- function calculateRelevanceScore(filePath, context, analysis) {
34
- let score = 0;
35
- const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
36
-
37
- // Base score for all files
38
- score += 1;
39
-
40
- // Boost for same directory
41
- if (context.currentFile) {
42
- const currentDir = path.dirname(context.currentFile);
43
- const fileDir = path.dirname(relativePath);
44
- if (currentDir === fileDir) score += 5;
45
- }
46
-
47
- // Boost for shared directory levels
48
- if (context.currentFile) {
49
- const currentParts = context.currentFile.split("/");
50
- const fileParts = relativePath.split("/");
51
- const sharedLevels = Math.min(
52
- currentParts.length - 1,
53
- fileParts.length - 1
54
- );
55
- for (let i = 0; i < sharedLevels; i++) {
56
- if (currentParts[i] === fileParts[i]) {
57
- score += 2;
58
- } else {
59
- break;
60
- }
61
- }
62
- }
63
-
64
- // Boost for commonly imported files
65
- if (analysis.imports?.internalAliases) {
66
- for (const alias of analysis.imports.internalAliases) {
67
- if (relativePath.startsWith(alias.replace("@", "src/"))) {
68
- score += 3;
69
- }
70
- }
71
- }
72
-
73
- // Boost based on file type
74
- const ext = path.extname(relativePath);
75
- const baseName = path.basename(relativePath, ext);
76
-
77
- // Components are highly relevant
78
- if (relativePath.includes("/components/") && /^[A-Z]/.test(baseName)) {
79
- score += 4;
80
- }
81
-
82
- // Hooks are moderately relevant
83
- if (relativePath.includes("/hooks/") || baseName.startsWith("use")) {
84
- score += 3;
85
- }
86
-
87
- // API routes for API-related tasks
88
- if (context.task === "api" && relativePath.includes("/api/")) {
89
- score += 5;
90
- }
91
-
92
- // Utils for utility tasks
93
- if (context.task === "utility" && (relativePath.includes("/utils/") || relativePath.includes("/lib/"))) {
94
- score += 4;
95
- }
96
-
97
- // Boost for files with many imports (likely core files)
98
- const fileStats = analysis.stats?.byExtension || {};
99
- if (fileStats[ext]) {
100
- score += Math.min(2, fileStats[ext] / 10);
101
- }
102
-
103
- // Boost for recently modified files (if git info available)
104
- if (context.recentFiles && context.recentFiles.includes(relativePath)) {
105
- score += 3;
106
- }
107
-
108
- // Boost for files mentioned in custom hooks
109
- if (analysis.patterns?.hooks?.includes(baseName)) {
110
- score += 2;
111
- }
112
-
113
- return score;
114
- }
115
-
116
- /**
117
- * Prune context to most relevant files
118
- */
119
- function pruneContext(analysis, context = {}) {
120
- const {
121
- maxTokens = 8000,
122
- currentFile = "",
123
- task = "general",
124
- includeTypes = ["components", "hooks", "utils", "api"],
125
- excludePatterns = ["*.test.*", "*.spec.*", "node_modules"],
126
- } = context;
127
-
128
- // Get all relevant files
129
- const allFiles = findFiles(process.cwd(), [".ts", ".tsx", ".js", ".jsx"], 5);
130
-
131
- // Filter by type and exclude patterns
132
- const filteredFiles = allFiles.filter(file => {
133
- const relativePath = path.relative(process.cwd(), file).replace(/\\/g, "/");
134
-
135
- // Check exclude patterns
136
- for (const pattern of excludePatterns) {
137
- if (relativePath.includes(pattern.replace("*", ""))) {
138
- return false;
139
- }
140
- }
141
-
142
- // Check include types
143
- const isInComponents = relativePath.includes("/components/");
144
- const isInHooks = relativePath.includes("/hooks/") || path.basename(file).startsWith("use");
145
- const isInUtils = relativePath.includes("/utils/") || relativePath.includes("/lib/");
146
- const isInApi = relativePath.includes("/api/") || relativePath.includes("/routes/");
147
-
148
- return (
149
- (includeTypes.includes("components") && isInComponents) ||
150
- (includeTypes.includes("hooks") && isInHooks) ||
151
- (includeTypes.includes("utils") && isInUtils) ||
152
- (includeTypes.includes("api") && isInApi) ||
153
- !includeTypes.length
154
- );
155
- });
156
-
157
- // Score and rank files
158
- const scoredFiles = filteredFiles.map(file => ({
159
- path: file,
160
- relativePath: path.relative(process.cwd(), file).replace(/\\/g, "/"),
161
- score: calculateRelevanceScore(file, context, analysis),
162
- }));
163
-
164
- // Sort by score (descending)
165
- scoredFiles.sort((a, b) => b.score - a.score);
166
-
167
- // Estimate token usage (rough estimate: ~4 tokens per character)
168
- let totalTokens = 0;
169
- const selectedFiles = [];
170
-
171
- for (const file of scoredFiles) {
172
- try {
173
- const content = fs.readFileSync(file.path, "utf-8");
174
- const fileTokens = content.length / 4;
175
-
176
- if (totalTokens + fileTokens <= maxTokens) {
177
- selectedFiles.push({
178
- ...file,
179
- content,
180
- tokens: Math.round(fileTokens),
181
- });
182
- totalTokens += fileTokens;
183
- } else {
184
- // Try to include partial content for important files
185
- if (file.score > 5) {
186
- const remainingTokens = maxTokens - totalTokens;
187
- const charsToFit = remainingTokens * 4;
188
- const partialContent = content.slice(0, charsToFit) + "\n... [truncated]";
189
-
190
- selectedFiles.push({
191
- ...file,
192
- content: partialContent,
193
- tokens: Math.round(partialContent.length / 4),
194
- truncated: true,
195
- });
196
- totalTokens += partialContent.length / 4;
197
- }
198
- break;
199
- }
200
- } catch {}
201
- }
202
-
203
- return {
204
- files: selectedFiles,
205
- totalTokens: Math.round(totalTokens),
206
- maxTokens,
207
- includedCount: selectedFiles.length,
208
- totalCount: filteredFiles.length,
209
- pruned: filteredFiles.length - selectedFiles.length,
210
- };
211
- }
212
-
213
- /**
214
- * Generate pruned context JSON for MCP
215
- */
216
- function generatePrunedContext(analysis, context = {}) {
217
- const pruned = pruneContext(analysis, context);
218
-
219
- return {
220
- version: "3.0.0-pruned",
221
- generatedAt: new Date().toISOString(),
222
- context: {
223
- currentFile: context.currentFile || "",
224
- task: context.task || "general",
225
- maxTokens: context.maxTokens || 8000,
226
- },
227
- summary: {
228
- totalFiles: pruned.totalCount,
229
- includedFiles: pruned.includedCount,
230
- prunedFiles: pruned.pruned,
231
- estimatedTokens: pruned.totalTokens,
232
- },
233
- files: pruned.files.map(f => ({
234
- path: f.relativePath,
235
- score: f.score,
236
- tokens: f.tokens,
237
- truncated: f.truncated || false,
238
- content: f.content,
239
- })),
240
- };
241
- }
242
-
243
- /**
244
- * Get context for specific file
245
- */
246
- function getContextForFile(filePath, analysis) {
247
- const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
248
-
249
- // Extract imports from current file
250
- let imports = [];
251
- try {
252
- const content = fs.readFileSync(filePath, "utf-8");
253
- const importMatches = content.match(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g) || [];
254
-
255
- for (const match of importMatches) {
256
- const source = match.match(/from\s+['"]([^'"]+)['"]/)?.[1];
257
- if (source && source.startsWith(".")) {
258
- // Resolve relative import
259
- const fromDir = path.dirname(relativePath);
260
- const resolvedPath = path.resolve(process.cwd(), fromDir, source);
261
- imports.push(path.relative(process.cwd(), resolvedPath).replace(/\\/g, "/"));
262
- }
263
- }
264
- } catch {}
265
-
266
- // Prune with focus on imported files
267
- const context = generatePrunedContext(analysis, {
268
- currentFile: relativePath,
269
- maxTokens: 6000,
270
- task: "file-specific",
271
- });
272
-
273
- // Boost imported files in the results
274
- for (const file of context.files) {
275
- if (imports.includes(file.path)) {
276
- file.score += 10;
277
- }
278
- }
279
-
280
- // Re-sort
281
- context.files.sort((a, b) => b.score - a.score);
282
-
283
- return context;
284
- }
285
-
286
- module.exports = {
287
- pruneContext,
288
- generatePrunedContext,
289
- getContextForFile,
290
- calculateRelevanceScore,
291
- };