claude-cli-advanced-starter-pack 1.0.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/OVERVIEW.md +597 -0
  3. package/README.md +439 -0
  4. package/bin/gtask.js +282 -0
  5. package/bin/postinstall.js +53 -0
  6. package/package.json +69 -0
  7. package/src/agents/phase-dev-templates.js +1011 -0
  8. package/src/agents/templates.js +668 -0
  9. package/src/analysis/checklist-parser.js +414 -0
  10. package/src/analysis/codebase.js +481 -0
  11. package/src/cli/menu.js +958 -0
  12. package/src/commands/claude-audit.js +1482 -0
  13. package/src/commands/claude-settings.js +2243 -0
  14. package/src/commands/create-agent.js +681 -0
  15. package/src/commands/create-command.js +337 -0
  16. package/src/commands/create-hook.js +262 -0
  17. package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
  18. package/src/commands/create-phase-dev/documentation-generator.js +352 -0
  19. package/src/commands/create-phase-dev/post-completion.js +404 -0
  20. package/src/commands/create-phase-dev/scale-calculator.js +344 -0
  21. package/src/commands/create-phase-dev/wizard.js +492 -0
  22. package/src/commands/create-phase-dev.js +481 -0
  23. package/src/commands/create-skill.js +313 -0
  24. package/src/commands/create.js +446 -0
  25. package/src/commands/decompose.js +392 -0
  26. package/src/commands/detect-tech-stack.js +768 -0
  27. package/src/commands/explore-mcp/claude-md-updater.js +252 -0
  28. package/src/commands/explore-mcp/mcp-installer.js +346 -0
  29. package/src/commands/explore-mcp/mcp-registry.js +438 -0
  30. package/src/commands/explore-mcp.js +638 -0
  31. package/src/commands/gtask-init.js +641 -0
  32. package/src/commands/help.js +128 -0
  33. package/src/commands/init.js +1890 -0
  34. package/src/commands/install.js +250 -0
  35. package/src/commands/list.js +116 -0
  36. package/src/commands/roadmap.js +750 -0
  37. package/src/commands/setup-wizard.js +482 -0
  38. package/src/commands/setup.js +351 -0
  39. package/src/commands/sync.js +534 -0
  40. package/src/commands/test-run.js +456 -0
  41. package/src/commands/test-setup.js +456 -0
  42. package/src/commands/validate.js +67 -0
  43. package/src/config/tech-stack.defaults.json +182 -0
  44. package/src/config/tech-stack.schema.json +502 -0
  45. package/src/github/client.js +359 -0
  46. package/src/index.js +84 -0
  47. package/src/templates/claude-command.js +244 -0
  48. package/src/templates/issue-body.js +284 -0
  49. package/src/testing/config.js +411 -0
  50. package/src/utils/template-engine.js +398 -0
  51. package/src/utils/validate-templates.js +223 -0
  52. package/src/utils.js +396 -0
  53. package/templates/commands/ccasp-setup.template.md +113 -0
  54. package/templates/commands/context-audit.template.md +97 -0
  55. package/templates/commands/create-task-list.template.md +382 -0
  56. package/templates/commands/deploy-full.template.md +261 -0
  57. package/templates/commands/github-task-start.template.md +99 -0
  58. package/templates/commands/github-update.template.md +69 -0
  59. package/templates/commands/happy-start.template.md +117 -0
  60. package/templates/commands/phase-track.template.md +142 -0
  61. package/templates/commands/tunnel-start.template.md +127 -0
  62. package/templates/commands/tunnel-stop.template.md +106 -0
  63. package/templates/hooks/context-guardian.template.js +173 -0
  64. package/templates/hooks/deployment-orchestrator.template.js +219 -0
  65. package/templates/hooks/github-progress-hook.template.js +197 -0
  66. package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
  67. package/templates/hooks/phase-dev-enforcer.template.js +183 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Issue Body Templates
3
+ *
4
+ * Generates comprehensive GitHub issue bodies with codebase analysis
5
+ */
6
+
7
+ /**
8
+ * Generate a comprehensive issue body
9
+ */
10
+ export function generateIssueBody(data) {
11
+ const {
12
+ description,
13
+ expectedBehavior,
14
+ actualBehavior,
15
+ acceptanceCriteria = [],
16
+ codeAnalysis = {},
17
+ apiStatus = {},
18
+ references = [],
19
+ todoList = [],
20
+ testScenarios = [],
21
+ priority,
22
+ labels = [],
23
+ } = data;
24
+
25
+ const sections = [];
26
+
27
+ // Problem Statement
28
+ sections.push(`## Problem Statement\n\n${description}`);
29
+
30
+ // Expected vs Actual (for bugs)
31
+ if (expectedBehavior || actualBehavior) {
32
+ sections.push(`## Expected vs Actual Behavior
33
+
34
+ **Expected**: ${expectedBehavior || 'N/A'}
35
+ **Actual**: ${actualBehavior || 'N/A'}`);
36
+ }
37
+
38
+ // Acceptance Criteria
39
+ if (acceptanceCriteria.length > 0) {
40
+ const criteria = acceptanceCriteria
41
+ .map((c) => `- [ ] ${c}`)
42
+ .join('\n');
43
+ sections.push(`## Acceptance Criteria\n\n${criteria}`);
44
+ }
45
+
46
+ // Code Analysis
47
+ if (codeAnalysis.relevantFiles?.length > 0 || codeAnalysis.keyFunctions?.length > 0) {
48
+ sections.push(generateCodeAnalysisSection(codeAnalysis));
49
+ }
50
+
51
+ // API Status (for full-stack features)
52
+ if (apiStatus.layers?.length > 0) {
53
+ sections.push(generateApiStatusSection(apiStatus));
54
+ }
55
+
56
+ // Reference Documents
57
+ if (references.length > 0) {
58
+ const refList = references.map((r) => `- ${r}`).join('\n');
59
+ sections.push(`## Reference Documents\n\n${refList}`);
60
+ }
61
+
62
+ // Recommended Approach / Todo List
63
+ if (todoList.length > 0) {
64
+ sections.push(generateTodoSection(todoList));
65
+ }
66
+
67
+ // Testing
68
+ if (testScenarios.length > 0) {
69
+ sections.push(generateTestingSection(testScenarios));
70
+ }
71
+
72
+ // Metadata footer
73
+ sections.push(`---
74
+ *Created by GitHub Task Kit*`);
75
+
76
+ return sections.join('\n\n---\n\n');
77
+ }
78
+
79
+ /**
80
+ * Generate code analysis section
81
+ */
82
+ function generateCodeAnalysisSection(analysis) {
83
+ const parts = ['## Code Analysis'];
84
+
85
+ // Relevant Files & Functions table
86
+ if (analysis.relevantFiles?.length > 0) {
87
+ parts.push('### Relevant Files & Functions\n');
88
+ parts.push('| File | Line | Function/Component | Purpose |');
89
+ parts.push('|------|------|-------------------|---------|');
90
+
91
+ for (const fileInfo of analysis.relevantFiles) {
92
+ for (const def of fileInfo.definitions || []) {
93
+ parts.push(
94
+ `| \`${fileInfo.file}\` | ${def.line} | \`${def.name}()\` | ${def.type} |`
95
+ );
96
+ }
97
+ }
98
+ }
99
+
100
+ // Key Functions
101
+ if (analysis.keyFunctions?.length > 0) {
102
+ parts.push('\n### Key Functions to Modify\n');
103
+ for (const func of analysis.keyFunctions.slice(0, 5)) {
104
+ parts.push(`- \`${func.file}:${func.line}\` - \`${func.name}()\``);
105
+ }
106
+ }
107
+
108
+ // Patterns to Follow
109
+ if (analysis.patterns?.length > 0) {
110
+ parts.push('\n### Patterns to Follow\n');
111
+ for (const pattern of analysis.patterns.slice(0, 3)) {
112
+ parts.push(`- **${pattern.keyword}**: Found in \`${pattern.file}\``);
113
+ }
114
+ }
115
+
116
+ // Code Snippets
117
+ if (analysis.codeSnippets?.length > 0) {
118
+ parts.push('\n### Code Snippets\n');
119
+ for (const snippet of analysis.codeSnippets.slice(0, 2)) {
120
+ const ext = snippet.file.split('.').pop();
121
+ const lang = getLanguageForExt(ext);
122
+ parts.push(`\`\`\`${lang}
123
+ // From: ${snippet.file}:${snippet.startLine}-${snippet.endLine}
124
+ ${snippet.content}
125
+ \`\`\``);
126
+ }
127
+ }
128
+
129
+ return parts.join('\n');
130
+ }
131
+
132
+ /**
133
+ * Generate API status section for full-stack features
134
+ */
135
+ function generateApiStatusSection(apiStatus) {
136
+ const parts = ['## Full-Stack API Status'];
137
+
138
+ parts.push('### Stack Verification Matrix\n');
139
+ parts.push('| Layer | Component | Status | File Location |');
140
+ parts.push('|-------|-----------|--------|---------------|');
141
+
142
+ for (const layer of apiStatus.layers || []) {
143
+ const status = layer.exists ? '✅' : '❌';
144
+ const location = layer.file ? `\`${layer.file}:${layer.line || ''}\`` : '-';
145
+ parts.push(`| ${layer.layer} | \`${layer.component}\` | ${status} | ${location} |`);
146
+ }
147
+
148
+ // Missing layers
149
+ const missing = (apiStatus.layers || []).filter((l) => !l.exists);
150
+ if (missing.length > 0) {
151
+ parts.push('\n### Missing Layers (MUST IMPLEMENT)\n');
152
+ for (const layer of missing) {
153
+ parts.push(`- [ ] **${layer.layer}**: Create \`${layer.component}\``);
154
+ }
155
+ }
156
+
157
+ return parts.join('\n');
158
+ }
159
+
160
+ /**
161
+ * Generate todo section
162
+ */
163
+ function generateTodoSection(todoList) {
164
+ const parts = ['## Recommended Approach'];
165
+
166
+ parts.push('### Todo List\n');
167
+ for (let i = 0; i < todoList.length; i++) {
168
+ const item = todoList[i];
169
+ parts.push(`- [ ] **Step ${i + 1}**: ${item.task}`);
170
+ if (item.details) {
171
+ parts.push(` - ${item.details}`);
172
+ }
173
+ if (item.file) {
174
+ parts.push(` - File: \`${item.file}\``);
175
+ }
176
+ }
177
+
178
+ // Dependencies
179
+ const deps = todoList.filter((t) => t.dependsOn);
180
+ if (deps.length > 0) {
181
+ parts.push('\n### Dependencies\n');
182
+ for (const dep of deps) {
183
+ parts.push(`- Step ${todoList.indexOf(dep) + 1} depends on Step ${dep.dependsOn}`);
184
+ }
185
+ }
186
+
187
+ return parts.join('\n');
188
+ }
189
+
190
+ /**
191
+ * Generate testing section
192
+ */
193
+ function generateTestingSection(scenarios) {
194
+ const parts = ['## Testing'];
195
+
196
+ parts.push('### Test Scenarios\n');
197
+ for (let i = 0; i < scenarios.length; i++) {
198
+ const scenario = scenarios[i];
199
+ parts.push(`${i + 1}. [ ] **${scenario.name}**`);
200
+ if (scenario.steps) {
201
+ for (const step of scenario.steps) {
202
+ parts.push(` - ${step}`);
203
+ }
204
+ }
205
+ if (scenario.expected) {
206
+ parts.push(` - Expected: ${scenario.expected}`);
207
+ }
208
+ }
209
+
210
+ return parts.join('\n');
211
+ }
212
+
213
+ /**
214
+ * Get language name for syntax highlighting
215
+ */
216
+ function getLanguageForExt(ext) {
217
+ const map = {
218
+ js: 'javascript',
219
+ jsx: 'jsx',
220
+ ts: 'typescript',
221
+ tsx: 'tsx',
222
+ py: 'python',
223
+ rs: 'rust',
224
+ go: 'go',
225
+ java: 'java',
226
+ cs: 'csharp',
227
+ rb: 'ruby',
228
+ php: 'php',
229
+ swift: 'swift',
230
+ kt: 'kotlin',
231
+ };
232
+ return map[ext] || ext;
233
+ }
234
+
235
+ /**
236
+ * Generate a simple issue body (minimal template)
237
+ */
238
+ export function generateSimpleIssueBody(data) {
239
+ const { description, acceptanceCriteria = [] } = data;
240
+
241
+ let body = `## Description\n\n${description}`;
242
+
243
+ if (acceptanceCriteria.length > 0) {
244
+ body += '\n\n## Acceptance Criteria\n\n';
245
+ body += acceptanceCriteria.map((c) => `- [ ] ${c}`).join('\n');
246
+ }
247
+
248
+ body += '\n\n---\n*Created by GitHub Task Kit*';
249
+
250
+ return body;
251
+ }
252
+
253
+ /**
254
+ * Default acceptance criteria suggestions based on issue type
255
+ */
256
+ export function suggestAcceptanceCriteria(issueType, keywords = []) {
257
+ const suggestions = {
258
+ bug: [
259
+ 'Bug is reproducible and root cause identified',
260
+ 'Fix implemented and tested locally',
261
+ 'No regression in related functionality',
262
+ 'Error handling covers edge cases',
263
+ ],
264
+ feature: [
265
+ 'Feature implemented according to requirements',
266
+ 'UI matches design specifications (if applicable)',
267
+ 'Unit tests added for new functionality',
268
+ 'Documentation updated',
269
+ ],
270
+ refactor: [
271
+ 'Code refactored without changing behavior',
272
+ 'All existing tests still pass',
273
+ 'Code follows project style guidelines',
274
+ 'No new warnings or errors introduced',
275
+ ],
276
+ documentation: [
277
+ 'Documentation is accurate and complete',
278
+ 'Examples are working and tested',
279
+ 'Links are valid and accessible',
280
+ ],
281
+ };
282
+
283
+ return suggestions[issueType] || suggestions.feature;
284
+ }
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Testing Configuration Module
3
+ *
4
+ * Manages testing modes, credentials, and environment configuration
5
+ */
6
+
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ // Default paths
11
+ const CONFIG_DIR = join(process.cwd(), '.gtask');
12
+ const TESTING_RULES_FILE = 'TESTING_RULES.md';
13
+
14
+ /**
15
+ * Testing mode definitions
16
+ */
17
+ export const TESTING_MODES = {
18
+ ralph: {
19
+ name: 'Ralph Loop',
20
+ description: 'Continuous test-fix cycle until all tests pass',
21
+ recommended: true,
22
+ defaults: {
23
+ maxIterations: 10,
24
+ completionPromise: 'all tasks complete and tests passing',
25
+ },
26
+ },
27
+ manual: {
28
+ name: 'Manual Testing',
29
+ description: 'You control when tests run',
30
+ recommended: false,
31
+ },
32
+ minimal: {
33
+ name: 'Minimal Testing',
34
+ description: 'Only test at the end of all tasks',
35
+ recommended: false,
36
+ },
37
+ };
38
+
39
+ /**
40
+ * Environment definitions
41
+ */
42
+ export const ENVIRONMENTS = {
43
+ localhost: {
44
+ name: 'Localhost',
45
+ description: 'Local development server',
46
+ urlTemplate: 'http://localhost:{port}',
47
+ defaultPort: 5173,
48
+ requiresSetup: ['npm run dev'],
49
+ recommended: true,
50
+ },
51
+ ngrok: {
52
+ name: 'ngrok Tunnel',
53
+ description: 'Expose local server via ngrok',
54
+ urlTemplate: 'https://{subdomain}.ngrok.dev',
55
+ requiresSetup: ['npm run dev', 'ngrok http {port}'],
56
+ recommended: false,
57
+ },
58
+ custom: {
59
+ name: 'Custom URL',
60
+ description: 'Custom deployment URL',
61
+ urlTemplate: '{url}',
62
+ requiresSetup: [],
63
+ recommended: false,
64
+ },
65
+ };
66
+
67
+ /**
68
+ * Credential source options
69
+ */
70
+ export const CREDENTIAL_SOURCES = {
71
+ env: {
72
+ name: 'Environment Variables',
73
+ description: 'Read from .env file or system environment',
74
+ envVars: {
75
+ username: 'TEST_USER_USERNAME',
76
+ password: 'TEST_USER_PASSWORD',
77
+ },
78
+ recommended: true,
79
+ secure: true,
80
+ },
81
+ config: {
82
+ name: 'Config File',
83
+ description: 'Store in .gtask/testing.json (gitignored)',
84
+ recommended: false,
85
+ secure: false,
86
+ },
87
+ prompt: {
88
+ name: 'Prompt Each Time',
89
+ description: 'Ask for credentials when tests run',
90
+ recommended: false,
91
+ secure: true,
92
+ },
93
+ none: {
94
+ name: 'No Authentication',
95
+ description: 'Tests do not require login',
96
+ recommended: false,
97
+ secure: true,
98
+ },
99
+ };
100
+
101
+ /**
102
+ * Testing configuration structure
103
+ */
104
+ export function createTestingConfig(options = {}) {
105
+ return {
106
+ version: '1.0.0',
107
+ createdAt: new Date().toISOString(),
108
+ updatedAt: new Date().toISOString(),
109
+
110
+ // Testing mode
111
+ mode: options.mode || 'manual',
112
+ ralphConfig: options.mode === 'ralph' ? {
113
+ maxIterations: options.maxIterations || 10,
114
+ completionPromise: options.completionPromise || 'all tasks complete and tests passing',
115
+ autoRetry: true,
116
+ } : null,
117
+
118
+ // Environment
119
+ environment: {
120
+ type: options.envType || 'localhost',
121
+ baseUrl: options.baseUrl || 'http://localhost:5173',
122
+ port: options.port || 5173,
123
+ requiresSetup: options.requiresSetup || [],
124
+ },
125
+
126
+ // Credentials
127
+ credentials: {
128
+ source: options.credentialSource || 'env',
129
+ envVars: options.envVars || {
130
+ username: 'TEST_USER_USERNAME',
131
+ password: 'TEST_USER_PASSWORD',
132
+ },
133
+ // Only stored if source is 'config'
134
+ username: options.credentialSource === 'config' ? options.username : undefined,
135
+ password: options.credentialSource === 'config' ? options.password : undefined,
136
+ },
137
+
138
+ // Playwright configuration
139
+ playwright: {
140
+ enabled: options.playwrightEnabled ?? true,
141
+ configPath: options.playwrightConfig || 'playwright.config.ts',
142
+ browser: options.browser || 'chromium',
143
+ headless: options.headless ?? true,
144
+ timeout: options.timeout || 30000,
145
+ },
146
+
147
+ // Test selectors (for login flow)
148
+ selectors: {
149
+ usernameInput: options.usernameSelector || '[data-testid="username-input"]',
150
+ passwordInput: options.passwordSelector || '[data-testid="password-input"]',
151
+ loginButton: options.loginButtonSelector || '[data-testid="login-submit"]',
152
+ loginSuccessIndicator: options.loginSuccessSelector || '[data-testid="dashboard"]',
153
+ },
154
+
155
+ // Persistent rules file path
156
+ rulesFile: options.rulesFile || null,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Save testing configuration
162
+ */
163
+ export function saveTestingConfig(config) {
164
+ if (!existsSync(CONFIG_DIR)) {
165
+ mkdirSync(CONFIG_DIR, { recursive: true });
166
+ }
167
+
168
+ const configPath = join(CONFIG_DIR, 'testing.json');
169
+ config.updatedAt = new Date().toISOString();
170
+
171
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
172
+
173
+ // Add to .gitignore if credentials are stored
174
+ if (config.credentials.source === 'config') {
175
+ ensureGitignore('.gtask/testing.json');
176
+ }
177
+
178
+ return configPath;
179
+ }
180
+
181
+ /**
182
+ * Load testing configuration
183
+ */
184
+ export function loadTestingConfig() {
185
+ const configPath = join(CONFIG_DIR, 'testing.json');
186
+
187
+ if (!existsSync(configPath)) {
188
+ return null;
189
+ }
190
+
191
+ try {
192
+ return JSON.parse(readFileSync(configPath, 'utf8'));
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Check if testing is configured
200
+ */
201
+ export function hasTestingConfig() {
202
+ return existsSync(join(CONFIG_DIR, 'testing.json'));
203
+ }
204
+
205
+ /**
206
+ * Generate TESTING_RULES.md template
207
+ */
208
+ export function generateTestingRules(config) {
209
+ const envSection = config.environment.type === 'ngrok'
210
+ ? `
211
+ ## Environment
212
+ - **Testing URL**: ${config.environment.baseUrl}
213
+ - **Environment Type**: ngrok tunnel
214
+ - **Requires**: npm run dev + ngrok running
215
+ - **Backend**: Railway (not local Vite proxy)
216
+ `
217
+ : config.environment.type === 'localhost'
218
+ ? `
219
+ ## Environment
220
+ - **Testing URL**: ${config.environment.baseUrl}
221
+ - **Environment Type**: Local development
222
+ - **Requires**: npm run dev
223
+ - **Port**: ${config.environment.port}
224
+ `
225
+ : `
226
+ ## Environment
227
+ - **Testing URL**: ${config.environment.baseUrl}
228
+ - **Environment Type**: Custom/Production
229
+ `;
230
+
231
+ const credentialSection = config.credentials.source === 'env'
232
+ ? `
233
+ ## Credentials (Environment Variables)
234
+ - **Username env var**: \`${config.credentials.envVars.username}\`
235
+ - **Password env var**: \`${config.credentials.envVars.password}\`
236
+
237
+ Set these in your .env file or system environment before running tests.
238
+ `
239
+ : config.credentials.source === 'config'
240
+ ? `
241
+ ## Credentials (Stored in config)
242
+ Credentials are stored in .gtask/testing.json (gitignored).
243
+ `
244
+ : config.credentials.source === 'prompt'
245
+ ? `
246
+ ## Credentials (Prompt)
247
+ You will be prompted for credentials each time tests run.
248
+ `
249
+ : `
250
+ ## Credentials
251
+ No authentication required for these tests.
252
+ `;
253
+
254
+ const selectorSection = `
255
+ ## Login Selectors
256
+ - **Username input**: \`${config.selectors.usernameInput}\`
257
+ - **Password input**: \`${config.selectors.passwordInput}\`
258
+ - **Login button**: \`${config.selectors.loginButton}\`
259
+ - **Success indicator**: \`${config.selectors.loginSuccessIndicator}\`
260
+ `;
261
+
262
+ const modeSection = config.mode === 'ralph'
263
+ ? `
264
+ ## Testing Mode: Ralph Loop
265
+ - **Max iterations**: ${config.ralphConfig.maxIterations}
266
+ - **Completion promise**: "${config.ralphConfig.completionPromise}"
267
+ - **Behavior**: Automatically retry tests after fixes until all pass
268
+ `
269
+ : config.mode === 'manual'
270
+ ? `
271
+ ## Testing Mode: Manual
272
+ Run tests manually when ready: \`gtask test\`
273
+ `
274
+ : `
275
+ ## Testing Mode: Minimal
276
+ Tests will only run after all tasks are complete.
277
+ `;
278
+
279
+ return `# Testing Rules
280
+
281
+ This file contains persistent testing rules for this project.
282
+ Generated by GitHub Task Kit.
283
+
284
+ ---
285
+ ${envSection}
286
+ ---
287
+ ${credentialSection}
288
+ ---
289
+ ${selectorSection}
290
+ ---
291
+ ${modeSection}
292
+ ---
293
+
294
+ ## Playwright Commands
295
+
296
+ \`\`\`bash
297
+ # Run all tests
298
+ npx playwright test
299
+
300
+ # Run specific test file
301
+ npx playwright test auth-login.spec.ts
302
+
303
+ # Run in headed mode (see browser)
304
+ npx playwright test --headed
305
+
306
+ # Run with UI mode
307
+ npx playwright test --ui
308
+ \`\`\`
309
+
310
+ ## Important Notes
311
+
312
+ 1. **Never commit credentials** to git
313
+ 2. **Always check the testing URL** before running tests
314
+ 3. **Ralph Loop will auto-retry** up to ${config.ralphConfig?.maxIterations || 10} times
315
+ 4. **Stop tests with Ctrl+C** if stuck in a loop
316
+
317
+ ---
318
+ *Generated by GitHub Task Kit - ${new Date().toISOString()}*
319
+ `;
320
+ }
321
+
322
+ /**
323
+ * Save testing rules markdown file
324
+ */
325
+ export function saveTestingRules(config, filename = TESTING_RULES_FILE) {
326
+ if (!existsSync(CONFIG_DIR)) {
327
+ mkdirSync(CONFIG_DIR, { recursive: true });
328
+ }
329
+
330
+ const rulesPath = join(CONFIG_DIR, filename);
331
+ const content = generateTestingRules(config);
332
+
333
+ writeFileSync(rulesPath, content, 'utf8');
334
+
335
+ // Update config with rules file path
336
+ config.rulesFile = rulesPath;
337
+
338
+ return rulesPath;
339
+ }
340
+
341
+ /**
342
+ * Ensure .gitignore includes sensitive files
343
+ */
344
+ function ensureGitignore(pattern) {
345
+ const gitignorePath = join(process.cwd(), '.gitignore');
346
+
347
+ if (!existsSync(gitignorePath)) {
348
+ writeFileSync(gitignorePath, `# gtask sensitive files\n${pattern}\n`, 'utf8');
349
+ return;
350
+ }
351
+
352
+ const content = readFileSync(gitignorePath, 'utf8');
353
+ if (!content.includes(pattern)) {
354
+ writeFileSync(gitignorePath, content + `\n# gtask sensitive files\n${pattern}\n`, 'utf8');
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Get credentials based on config
360
+ */
361
+ export function getCredentials(config) {
362
+ if (!config || config.credentials.source === 'none') {
363
+ return null;
364
+ }
365
+
366
+ if (config.credentials.source === 'env') {
367
+ return {
368
+ username: process.env[config.credentials.envVars.username] || null,
369
+ password: process.env[config.credentials.envVars.password] || null,
370
+ };
371
+ }
372
+
373
+ if (config.credentials.source === 'config') {
374
+ return {
375
+ username: config.credentials.username,
376
+ password: config.credentials.password,
377
+ };
378
+ }
379
+
380
+ // 'prompt' returns null - caller should prompt user
381
+ return null;
382
+ }
383
+
384
+ /**
385
+ * Validate testing configuration
386
+ */
387
+ export function validateConfig(config) {
388
+ const errors = [];
389
+
390
+ if (!config.environment?.baseUrl) {
391
+ errors.push('Base URL is required');
392
+ }
393
+
394
+ if (config.credentials?.source === 'env') {
395
+ const creds = getCredentials(config);
396
+ if (!creds?.username || !creds?.password) {
397
+ errors.push(
398
+ `Environment variables not set: ${config.credentials.envVars.username}, ${config.credentials.envVars.password}`
399
+ );
400
+ }
401
+ }
402
+
403
+ if (config.mode === 'ralph' && !config.ralphConfig) {
404
+ errors.push('Ralph configuration missing');
405
+ }
406
+
407
+ return {
408
+ valid: errors.length === 0,
409
+ errors,
410
+ };
411
+ }