@vibedx/vibekit 0.1.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/LICENSE +21 -0
- package/README.md +368 -0
- package/assets/config.yml +35 -0
- package/assets/default.md +47 -0
- package/assets/instructions/README.md +46 -0
- package/assets/instructions/claude.md +83 -0
- package/assets/instructions/codex.md +19 -0
- package/index.js +106 -0
- package/package.json +90 -0
- package/src/commands/close/index.js +66 -0
- package/src/commands/close/index.test.js +235 -0
- package/src/commands/get-started/index.js +138 -0
- package/src/commands/get-started/index.test.js +246 -0
- package/src/commands/init/index.js +51 -0
- package/src/commands/init/index.test.js +159 -0
- package/src/commands/link/index.js +395 -0
- package/src/commands/link/index.test.js +28 -0
- package/src/commands/lint/index.js +657 -0
- package/src/commands/lint/index.test.js +569 -0
- package/src/commands/list/index.js +131 -0
- package/src/commands/list/index.test.js +153 -0
- package/src/commands/new/index.js +305 -0
- package/src/commands/new/index.test.js +256 -0
- package/src/commands/refine/index.js +741 -0
- package/src/commands/refine/index.test.js +28 -0
- package/src/commands/review/index.js +957 -0
- package/src/commands/review/index.test.js +193 -0
- package/src/commands/start/index.js +180 -0
- package/src/commands/start/index.test.js +88 -0
- package/src/commands/unlink/index.js +123 -0
- package/src/commands/unlink/index.test.js +22 -0
- package/src/utils/arrow-select.js +233 -0
- package/src/utils/cli.js +489 -0
- package/src/utils/cli.test.js +9 -0
- package/src/utils/git.js +146 -0
- package/src/utils/git.test.js +330 -0
- package/src/utils/index.js +193 -0
- package/src/utils/index.test.js +375 -0
- package/src/utils/prompts.js +47 -0
- package/src/utils/prompts.test.js +165 -0
- package/src/utils/test-helpers.js +492 -0
- package/src/utils/ticket.js +423 -0
- package/src/utils/ticket.test.js +190 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a temporary test directory
|
|
11
|
+
* @param {string} testName - Name for the test directory
|
|
12
|
+
* @returns {string} Path to the temporary directory
|
|
13
|
+
*/
|
|
14
|
+
export function createTempDir(testName = 'test') {
|
|
15
|
+
const tempDir = path.join(__dirname, '../../__temp__', testName, Date.now().toString());
|
|
16
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
17
|
+
return tempDir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clean up temporary test directory
|
|
22
|
+
* @param {string} tempDir - Path to the temporary directory to remove
|
|
23
|
+
*/
|
|
24
|
+
export function cleanupTempDir(tempDir) {
|
|
25
|
+
if (fs.existsSync(tempDir)) {
|
|
26
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
27
|
+
|
|
28
|
+
// Also clean up empty parent directories
|
|
29
|
+
let parentDir = path.dirname(tempDir);
|
|
30
|
+
const tempRoot = path.join(__dirname, '../../__temp__');
|
|
31
|
+
|
|
32
|
+
// Keep going up the directory tree until we reach __temp__ root or find a non-empty directory
|
|
33
|
+
while (parentDir !== tempRoot && parentDir !== path.dirname(parentDir)) {
|
|
34
|
+
try {
|
|
35
|
+
if (fs.existsSync(parentDir)) {
|
|
36
|
+
const entries = fs.readdirSync(parentDir);
|
|
37
|
+
if (entries.length === 0) {
|
|
38
|
+
fs.rmdirSync(parentDir);
|
|
39
|
+
parentDir = path.dirname(parentDir);
|
|
40
|
+
} else {
|
|
41
|
+
break; // Directory not empty, stop cleaning
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
break; // Directory doesn't exist, stop
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
break; // Permission error or other issue, stop cleaning
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Clean up all temporary test directories
|
|
55
|
+
*/
|
|
56
|
+
export function cleanupAllTempDirs() {
|
|
57
|
+
const tempRoot = path.join(__dirname, '../../__temp__');
|
|
58
|
+
if (fs.existsSync(tempRoot)) {
|
|
59
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a mock .vibe project structure
|
|
65
|
+
* @param {string} baseDir - Base directory for the mock project
|
|
66
|
+
* @param {Object} options - Configuration options
|
|
67
|
+
* @returns {Object} Paths to created files and directories
|
|
68
|
+
*/
|
|
69
|
+
export function createMockVibeProject(baseDir, options = {}) {
|
|
70
|
+
const {
|
|
71
|
+
withConfig = true,
|
|
72
|
+
withTemplate = true,
|
|
73
|
+
withTickets = [],
|
|
74
|
+
configData = null,
|
|
75
|
+
templateData = null
|
|
76
|
+
} = options;
|
|
77
|
+
|
|
78
|
+
const vibeDir = path.join(baseDir, '.vibe');
|
|
79
|
+
const ticketsDir = path.join(vibeDir, 'tickets');
|
|
80
|
+
const templatesDir = path.join(vibeDir, '.templates');
|
|
81
|
+
|
|
82
|
+
// Create directories
|
|
83
|
+
fs.mkdirSync(vibeDir, { recursive: true });
|
|
84
|
+
fs.mkdirSync(ticketsDir, { recursive: true });
|
|
85
|
+
fs.mkdirSync(templatesDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
const paths = {
|
|
88
|
+
vibeDir,
|
|
89
|
+
ticketsDir,
|
|
90
|
+
templatesDir
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Create config.yml
|
|
94
|
+
if (withConfig) {
|
|
95
|
+
const defaultConfig = {
|
|
96
|
+
project: {
|
|
97
|
+
name: 'Test Project',
|
|
98
|
+
version: '1.0.0'
|
|
99
|
+
},
|
|
100
|
+
tickets: {
|
|
101
|
+
path: '.vibe/tickets',
|
|
102
|
+
priority_options: ['low', 'medium', 'high', 'urgent'],
|
|
103
|
+
status_options: ['open', 'in_progress', 'review', 'done'],
|
|
104
|
+
slug: {
|
|
105
|
+
max_length: 30,
|
|
106
|
+
word_limit: 5
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
git: {
|
|
110
|
+
branch_prefix: 'feature/'
|
|
111
|
+
},
|
|
112
|
+
ai: {
|
|
113
|
+
enabled: false,
|
|
114
|
+
provider: 'none'
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const configPath = path.join(vibeDir, 'config.yml');
|
|
119
|
+
const configContent = configData || `project:
|
|
120
|
+
name: Test Project
|
|
121
|
+
version: 1.0.0
|
|
122
|
+
|
|
123
|
+
tickets:
|
|
124
|
+
path: .vibe/tickets
|
|
125
|
+
priority_options:
|
|
126
|
+
- low
|
|
127
|
+
- medium
|
|
128
|
+
- high
|
|
129
|
+
- urgent
|
|
130
|
+
status_options:
|
|
131
|
+
- open
|
|
132
|
+
- in_progress
|
|
133
|
+
- review
|
|
134
|
+
- done
|
|
135
|
+
slug:
|
|
136
|
+
max_length: 30
|
|
137
|
+
word_limit: 5
|
|
138
|
+
|
|
139
|
+
git:
|
|
140
|
+
branch_prefix: feature/
|
|
141
|
+
|
|
142
|
+
ai:
|
|
143
|
+
enabled: false
|
|
144
|
+
provider: none
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
148
|
+
paths.configPath = configPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Create default.md template
|
|
152
|
+
if (withTemplate) {
|
|
153
|
+
const defaultTemplate = templateData || `---
|
|
154
|
+
id: TKT-{id}
|
|
155
|
+
title: {title}
|
|
156
|
+
slug: {slug}
|
|
157
|
+
status: open
|
|
158
|
+
priority: medium
|
|
159
|
+
created_at: {date}
|
|
160
|
+
updated_at: {date}
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Description
|
|
164
|
+
|
|
165
|
+
Brief description of the task or issue.
|
|
166
|
+
|
|
167
|
+
## Acceptance Criteria
|
|
168
|
+
|
|
169
|
+
- [ ] Define what needs to be done
|
|
170
|
+
- [ ] Add specific requirements
|
|
171
|
+
- [ ] Include testing criteria
|
|
172
|
+
|
|
173
|
+
## Code Quality
|
|
174
|
+
|
|
175
|
+
- Follow existing code patterns
|
|
176
|
+
- Write clear, readable code
|
|
177
|
+
- Include appropriate error handling
|
|
178
|
+
- Add JSDoc comments where needed
|
|
179
|
+
|
|
180
|
+
## Implementation Notes
|
|
181
|
+
|
|
182
|
+
Add any technical details, considerations, or constraints.
|
|
183
|
+
|
|
184
|
+
## Design / UX Considerations
|
|
185
|
+
|
|
186
|
+
Any design or user experience requirements.
|
|
187
|
+
|
|
188
|
+
## Testing & Test Cases
|
|
189
|
+
|
|
190
|
+
Outline the testing approach and specific test cases.
|
|
191
|
+
|
|
192
|
+
## AI Prompt
|
|
193
|
+
|
|
194
|
+
Brief description for AI assistance.
|
|
195
|
+
|
|
196
|
+
## Expected AI Output
|
|
197
|
+
|
|
198
|
+
What the AI should deliver or accomplish.
|
|
199
|
+
|
|
200
|
+
## AI Workflow [Claude Code, Codex]
|
|
201
|
+
|
|
202
|
+
<!-- NOTE (Do not remove) -->
|
|
203
|
+
Always use \`vibe start\` to start working on this ticket and \`vibe close\` to close this ticket when done. Read .vibe/.context/aiworkflow directory for following vibekit cli workflow and follow the instructions to work on the tickets.
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
const templatePath = path.join(templatesDir, 'default.md');
|
|
207
|
+
fs.writeFileSync(templatePath, defaultTemplate, 'utf-8');
|
|
208
|
+
paths.templatePath = templatePath;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Create mock tickets
|
|
212
|
+
withTickets.forEach((ticket, index) => {
|
|
213
|
+
const ticketId = ticket.id || `TKT-${String(index + 1).padStart(3, '0')}`;
|
|
214
|
+
const filename = `${ticketId}-${ticket.slug || ticket.title?.toLowerCase().replace(/[^a-z0-9]+/g, '-') || 'test-ticket'}.md`;
|
|
215
|
+
const ticketPath = path.join(ticketsDir, filename);
|
|
216
|
+
|
|
217
|
+
// Build frontmatter with only provided fields
|
|
218
|
+
const frontmatterLines = [];
|
|
219
|
+
if (ticket.id !== undefined) frontmatterLines.push(`id: ${ticket.id}`);
|
|
220
|
+
if (ticket.title !== undefined) frontmatterLines.push(`title: ${ticket.title}`);
|
|
221
|
+
if (ticket.slug !== undefined) frontmatterLines.push(`slug: ${ticket.slug}`);
|
|
222
|
+
if (ticket.status !== undefined) frontmatterLines.push(`status: ${ticket.status}`);
|
|
223
|
+
if (ticket.priority !== undefined) frontmatterLines.push(`priority: ${ticket.priority}`);
|
|
224
|
+
if (ticket.created_at !== undefined) frontmatterLines.push(`created_at: ${ticket.created_at}`);
|
|
225
|
+
if (ticket.updated_at !== undefined) frontmatterLines.push(`updated_at: ${ticket.updated_at}`);
|
|
226
|
+
|
|
227
|
+
const frontmatter = frontmatterLines.length > 0 ? frontmatterLines.join('\n') : '';
|
|
228
|
+
|
|
229
|
+
const ticketContent = `---
|
|
230
|
+
${frontmatter}
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
${ticket.description || 'Test ticket description'}
|
|
234
|
+
`;
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(ticketPath, ticketContent, 'utf-8');
|
|
237
|
+
if (!paths.ticketPaths) paths.ticketPaths = [];
|
|
238
|
+
paths.ticketPaths.push(ticketPath);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return paths;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create a mock git repository structure
|
|
246
|
+
* @param {string} baseDir - Base directory for the mock repository
|
|
247
|
+
* @returns {Object} Information about the created git repository
|
|
248
|
+
*/
|
|
249
|
+
export function createMockGitRepo(baseDir) {
|
|
250
|
+
const gitDir = path.join(baseDir, '.git');
|
|
251
|
+
const headFile = path.join(gitDir, 'HEAD');
|
|
252
|
+
const configFile = path.join(gitDir, 'config');
|
|
253
|
+
|
|
254
|
+
fs.mkdirSync(gitDir, { recursive: true });
|
|
255
|
+
fs.writeFileSync(headFile, 'ref: refs/heads/main\n', 'utf-8');
|
|
256
|
+
fs.writeFileSync(configFile, `[core]
|
|
257
|
+
\trepositoryformatversion = 0
|
|
258
|
+
\tfilemode = true
|
|
259
|
+
\tbare = false
|
|
260
|
+
\tlogallrefupdates = true
|
|
261
|
+
[branch "main"]
|
|
262
|
+
\tremote = origin
|
|
263
|
+
\tmerge = refs/heads/main
|
|
264
|
+
`, 'utf-8');
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
gitDir,
|
|
268
|
+
headFile,
|
|
269
|
+
configFile,
|
|
270
|
+
currentBranch: 'main'
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Mock console methods for testing
|
|
276
|
+
* @returns {Object} Mock console methods and restore function
|
|
277
|
+
*/
|
|
278
|
+
export function mockConsole() {
|
|
279
|
+
const originalConsole = {
|
|
280
|
+
log: console.log,
|
|
281
|
+
error: console.error,
|
|
282
|
+
warn: console.warn,
|
|
283
|
+
info: console.info
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const logs = {
|
|
287
|
+
log: [],
|
|
288
|
+
error: [],
|
|
289
|
+
warn: [],
|
|
290
|
+
info: []
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
console.log = (...args) => logs.log.push(args.join(' '));
|
|
294
|
+
console.error = (...args) => logs.error.push(args.join(' '));
|
|
295
|
+
console.warn = (...args) => logs.warn.push(args.join(' '));
|
|
296
|
+
console.info = (...args) => logs.info.push(args.join(' '));
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
logs,
|
|
300
|
+
restore: () => {
|
|
301
|
+
console.log = originalConsole.log;
|
|
302
|
+
console.error = originalConsole.error;
|
|
303
|
+
console.warn = originalConsole.warn;
|
|
304
|
+
console.info = originalConsole.info;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Mock process.cwd() to return a specific directory
|
|
311
|
+
* @param {string} mockDir - Directory to return as current working directory
|
|
312
|
+
* @returns {Function} Restore function to reset process.cwd
|
|
313
|
+
*/
|
|
314
|
+
export function mockProcessCwd(mockDir) {
|
|
315
|
+
const originalCwd = process.cwd;
|
|
316
|
+
process.cwd = () => mockDir;
|
|
317
|
+
|
|
318
|
+
return () => {
|
|
319
|
+
process.cwd = originalCwd;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Mock process.exit() to prevent actual exit during tests
|
|
325
|
+
* @returns {Object} Mock exit function and restore function
|
|
326
|
+
*/
|
|
327
|
+
export function mockProcessExit() {
|
|
328
|
+
const originalExit = process.exit;
|
|
329
|
+
const exitCalls = [];
|
|
330
|
+
|
|
331
|
+
process.exit = (code) => {
|
|
332
|
+
exitCalls.push(code);
|
|
333
|
+
throw new Error(`process.exit(${code})`);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
exitCalls,
|
|
338
|
+
restore: () => {
|
|
339
|
+
process.exit = originalExit;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Create a test fixture for file system operations
|
|
346
|
+
* @param {Object} fileStructure - Object representing file structure
|
|
347
|
+
* @param {string} baseDir - Base directory for the fixture
|
|
348
|
+
*/
|
|
349
|
+
export function createFileFixture(fileStructure, baseDir) {
|
|
350
|
+
Object.entries(fileStructure).forEach(([filePath, content]) => {
|
|
351
|
+
const fullPath = path.join(baseDir, filePath);
|
|
352
|
+
const dir = path.dirname(fullPath);
|
|
353
|
+
|
|
354
|
+
if (!fs.existsSync(dir)) {
|
|
355
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (typeof content === 'string') {
|
|
359
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
360
|
+
} else if (content === null) {
|
|
361
|
+
// Create directory
|
|
362
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Assert that a file exists and has expected content
|
|
369
|
+
* @param {string} filePath - Path to the file
|
|
370
|
+
* @param {string|RegExp} expectedContent - Expected content or pattern
|
|
371
|
+
*/
|
|
372
|
+
export function assertFileContent(filePath, expectedContent) {
|
|
373
|
+
if (!fs.existsSync(filePath)) {
|
|
374
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
378
|
+
|
|
379
|
+
if (typeof expectedContent === 'string') {
|
|
380
|
+
if (!content.includes(expectedContent)) {
|
|
381
|
+
throw new Error(`File content does not include expected text. Got: ${content}`);
|
|
382
|
+
}
|
|
383
|
+
} else if (expectedContent instanceof RegExp) {
|
|
384
|
+
if (!expectedContent.test(content)) {
|
|
385
|
+
throw new Error(`File content does not match expected pattern. Got: ${content}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Wait for a specified amount of time (for async operations)
|
|
392
|
+
* @param {number} ms - Milliseconds to wait
|
|
393
|
+
* @returns {Promise} Promise that resolves after the wait
|
|
394
|
+
*/
|
|
395
|
+
export function wait(ms) {
|
|
396
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Create a spy function for testing
|
|
401
|
+
* @param {Function} originalFn - Original function to spy on
|
|
402
|
+
* @returns {Object} Spy function with call tracking
|
|
403
|
+
*/
|
|
404
|
+
export function createSpy(originalFn = () => {}) {
|
|
405
|
+
const calls = [];
|
|
406
|
+
|
|
407
|
+
const spy = (...args) => {
|
|
408
|
+
calls.push(args);
|
|
409
|
+
return originalFn(...args);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
spy.calls = calls;
|
|
413
|
+
spy.calledWith = (...expectedArgs) => {
|
|
414
|
+
return calls.some(callArgs =>
|
|
415
|
+
callArgs.length === expectedArgs.length &&
|
|
416
|
+
callArgs.every((arg, index) => arg === expectedArgs[index])
|
|
417
|
+
);
|
|
418
|
+
};
|
|
419
|
+
spy.callCount = () => calls.length;
|
|
420
|
+
spy.lastCall = () => calls[calls.length - 1];
|
|
421
|
+
|
|
422
|
+
return spy;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Setup mock assets directory structure for init command testing
|
|
427
|
+
* @param {string} tempDir - Base temporary directory
|
|
428
|
+
* @returns {string} Path to the mock assets directory
|
|
429
|
+
*/
|
|
430
|
+
export function setupMockAssets(tempDir) {
|
|
431
|
+
// Create the expected assets path structure that init command will look for
|
|
432
|
+
const mockAssetsPath = path.join(tempDir, 'src', 'commands', 'init', '..', '..', '..', 'assets');
|
|
433
|
+
const assetsDir = path.resolve(mockAssetsPath);
|
|
434
|
+
|
|
435
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
436
|
+
|
|
437
|
+
// Create mock config.yml
|
|
438
|
+
const configContent = `project:
|
|
439
|
+
name: Test Project
|
|
440
|
+
version: 1.0.0
|
|
441
|
+
|
|
442
|
+
tickets:
|
|
443
|
+
path: .vibe/tickets
|
|
444
|
+
priority_options:
|
|
445
|
+
- low
|
|
446
|
+
- medium
|
|
447
|
+
- high
|
|
448
|
+
- urgent
|
|
449
|
+
status_options:
|
|
450
|
+
- open
|
|
451
|
+
- in_progress
|
|
452
|
+
- review
|
|
453
|
+
- done
|
|
454
|
+
slug:
|
|
455
|
+
max_length: 30
|
|
456
|
+
word_limit: 5
|
|
457
|
+
|
|
458
|
+
git:
|
|
459
|
+
branch_prefix: feature/
|
|
460
|
+
|
|
461
|
+
ai:
|
|
462
|
+
enabled: false
|
|
463
|
+
provider: none
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
// Create mock default.md template
|
|
467
|
+
const templateContent = `---
|
|
468
|
+
id: TKT-{id}
|
|
469
|
+
title: {title}
|
|
470
|
+
slug: {slug}
|
|
471
|
+
status: open
|
|
472
|
+
priority: medium
|
|
473
|
+
created_at: {date}
|
|
474
|
+
updated_at: {date}
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Description
|
|
478
|
+
|
|
479
|
+
Brief description of the task or issue.
|
|
480
|
+
|
|
481
|
+
## Acceptance Criteria
|
|
482
|
+
|
|
483
|
+
- [ ] Define what needs to be done
|
|
484
|
+
- [ ] Add specific requirements
|
|
485
|
+
- [ ] Include testing criteria
|
|
486
|
+
`;
|
|
487
|
+
|
|
488
|
+
fs.writeFileSync(path.join(assetsDir, 'config.yml'), configContent, 'utf-8');
|
|
489
|
+
fs.writeFileSync(path.join(assetsDir, 'default.md'), templateContent, 'utf-8');
|
|
490
|
+
|
|
491
|
+
return assetsDir;
|
|
492
|
+
}
|