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.
- package/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- 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
|
+
}
|