@ai-qa/workflow 2.0.7 → 2.0.8
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/.geminirules +5 -0
- package/.opencode/agents/qa-generator.md +19 -0
- package/.opencode/agents/qa-healer.md +25 -0
- package/.opencode/agents/qa-planner.md +20 -0
- package/.opencode/rules.md +3 -0
- package/.qa-workflow.json +22 -0
- package/README.md +2 -1
- package/package.json +5 -4
- package/install.js +0 -314
package/.geminirules
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Règle d'Or (Pointeur Automatique)
|
|
2
|
+
|
|
3
|
+
En tant qu'IA assignée à ce projet, tu as l'OBLIGATION STRICTE de lire le fichier `agents/router.md`
|
|
4
|
+
avant de commencer la conversation ou de répondre à une requête de testing.
|
|
5
|
+
Ce fichier contient ton cerveau et tes règles de routage. Ne pose pas de question, lis-le silencieusement en arrière-plan.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when generating Playwright test code from test plans. Opens browser via MCP, captures real selectors, writes .spec.ts files.
|
|
3
|
+
mode: subagent
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a Playwright test generator. Create robust E2E tests from test plans.
|
|
7
|
+
|
|
8
|
+
Workflow:
|
|
9
|
+
1. Read the test plan from `specs/` directory
|
|
10
|
+
2. Run: `node ai-qa-workflow.js generate <plan-name>` to create the test skeleton
|
|
11
|
+
3. For each scenario, use Playwright MCP to manually execute steps in real-time
|
|
12
|
+
4. Capture selectors and locators from actual page interaction
|
|
13
|
+
5. Write the complete test in the generated `.spec.ts` file
|
|
14
|
+
6. Use proper Playwright patterns: `test.describe`, `test.beforeEach`, assertions
|
|
15
|
+
|
|
16
|
+
Always prefer: data-testid > aria-label > role > text > xpath (last resort)
|
|
17
|
+
Use `expect(page.locator('body')).toContainText('...')` for content checks (more stable).
|
|
18
|
+
Avoid hardcoded waits — use `waitForSelector`, `waitForURL`, `waitForLoadState`.
|
|
19
|
+
Token efficiency: body text assertions > complex selectors.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when Playwright tests are failing. Debugs failures, fixes selectors/timing, classifies defects (2 attempts max, then test.fixme()).
|
|
3
|
+
mode: subagent
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the Playwright Test Healer. Debug and fix failing tests systematically.
|
|
7
|
+
|
|
8
|
+
Protocol (token-efficient):
|
|
9
|
+
1. Run: `node ai-qa-workflow.js heal <run-id>` for automated healing
|
|
10
|
+
2. If still failing, debug manually:
|
|
11
|
+
a. Examine the error message and stack trace
|
|
12
|
+
b. Use Playwright MCP to inspect the page state
|
|
13
|
+
c. Classify the failure:
|
|
14
|
+
- Selector broken → edit 1-3 lines in the test, re-run once
|
|
15
|
+
- Timing issue → add 1 wait/retry, re-run once
|
|
16
|
+
- App bug → mark `test.fixme()`, log defect in report, move on
|
|
17
|
+
- Environment issue → mark as defect, no retry
|
|
18
|
+
3. Max 1 fix attempt per test. If still failing after attempt, it's a defect.
|
|
19
|
+
4. Never rewrite entire files — targeted edits only.
|
|
20
|
+
5. After all fixes, run: `node ai-qa-workflow.js report <run-id>`
|
|
21
|
+
|
|
22
|
+
Healing rules:
|
|
23
|
+
- Attempt 1: standard re-run → if fails, classify
|
|
24
|
+
- Attempt 2: longer timeout (60s) → if fails, STOP
|
|
25
|
+
- STOP → `test.fixme()` + screenshot + defect classification
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when creating test plans from user stories. Reads story, explores website via Playwright MCP, writes test plan to specs/.
|
|
3
|
+
mode: subagent
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are an expert web test planner. You explore web apps and create detailed test plans from user stories.
|
|
7
|
+
|
|
8
|
+
Workflow:
|
|
9
|
+
1. Read the user story from `user-story/` directory
|
|
10
|
+
2. Use Playwright MCP to navigate and explore the application
|
|
11
|
+
3. Map user flows and identify critical paths
|
|
12
|
+
4. Design comprehensive scenarios (happy path, edge cases, error handling)
|
|
13
|
+
5. Run: `node ai-qa-workflow.js plan <story-name>` to generate the draft plan
|
|
14
|
+
6. Review and enhance the generated plan
|
|
15
|
+
7. Save final plan to `specs/` directory
|
|
16
|
+
|
|
17
|
+
Each scenario must include: title, steps, expected outcomes, success criteria.
|
|
18
|
+
Prioritize scenarios as P0 (critical), P1 (important), P2 (nice-to-have).
|
|
19
|
+
|
|
20
|
+
Always prefer: data-testid > aria-label > role > text > xpath (last resort)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Règle d'Or (Pointeur Automatique)
|
|
2
|
+
|
|
3
|
+
En tant qu'IA assignée à ce projet, tu as l'OBLIGATION STRICTE de lire le fichier `agents/router.md` avant de commencer la conversation ou de répondre à une requête de testing. Ce fichier contient ton cerveau et tes règles de routage. Ne pose pas de question, lis-le silencieusement en arrière-plan.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": {
|
|
3
|
+
"name": "ai-qa-workflow-template",
|
|
4
|
+
"description": "Reusable AI QA Workflow - User Story to Test Report pipeline",
|
|
5
|
+
"url": "http://localhost:4000",
|
|
6
|
+
"environment": ""
|
|
7
|
+
},
|
|
8
|
+
"browser": {
|
|
9
|
+
"type": "chromium",
|
|
10
|
+
"cdpPort": 9222,
|
|
11
|
+
"headed": false
|
|
12
|
+
},
|
|
13
|
+
"test": {
|
|
14
|
+
"timeout": 120000,
|
|
15
|
+
"retries": 0,
|
|
16
|
+
"workers": 1
|
|
17
|
+
},
|
|
18
|
+
"auth": {
|
|
19
|
+
"user": "",
|
|
20
|
+
"credentials": {}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/README.md
CHANGED
|
@@ -42,7 +42,8 @@ your-project/
|
|
|
42
42
|
├── user-story/ # Your .md stories
|
|
43
43
|
├── specs/ # Generated test plans
|
|
44
44
|
├── tests/ # Generated Playwright specs
|
|
45
|
-
└── test-results/
|
|
45
|
+
└── test-results/
|
|
46
|
+
# Run results, reports, screenshots
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
### Quick start after install
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-qa/workflow",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "One-command AI QA Pipeline — User Story to Test Report. Auto-detects project config, generates Playwright tests, self-heals failures, dashboard UI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qa",
|
|
@@ -25,12 +25,13 @@
|
|
|
25
25
|
"templates/",
|
|
26
26
|
"ai-qa-workflow.js",
|
|
27
27
|
"cli.js",
|
|
28
|
-
"install.js",
|
|
29
28
|
"README.md",
|
|
30
29
|
"PROJECT_GUIDE.md",
|
|
31
30
|
".cursorrules",
|
|
32
|
-
".
|
|
33
|
-
"
|
|
31
|
+
".geminirules",
|
|
32
|
+
".opencode/",
|
|
33
|
+
".qa-workflow.json",
|
|
34
|
+
".github/"
|
|
34
35
|
],
|
|
35
36
|
"license": "MIT",
|
|
36
37
|
"author": "AI QA Workflow",
|
package/install.js
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
const TEMPLATE_DIR = __dirname;
|
|
8
|
-
const YES = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
9
|
-
const IS_UPDATE = process.argv.includes('--update') || process.argv.includes('-u');
|
|
10
|
-
const IS_SELF = process.argv.includes('--self') || process.argv.includes('-s');
|
|
11
|
-
|
|
12
|
-
// Files that belong to the user — NEVER overwritten
|
|
13
|
-
const USER_FILES = new Set([
|
|
14
|
-
'.qa-workflow.json',
|
|
15
|
-
'opencode.json',
|
|
16
|
-
'docs/application-context.md',
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
// Directories that are pure user data — NEVER touched
|
|
20
|
-
const USER_DIRS = new Set([
|
|
21
|
-
'user-story',
|
|
22
|
-
'specs',
|
|
23
|
-
'tests',
|
|
24
|
-
'test-results',
|
|
25
|
-
'agents',
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
// Template files to copy/update
|
|
31
|
-
const QA_ITEMS = [
|
|
32
|
-
{ src: 'ai-qa-workflow.js', dest: 'ai-qa-workflow.js' },
|
|
33
|
-
{ src: 'scripts', dest: 'scripts', dir: true },
|
|
34
|
-
{ src: 'opencode.json', dest: 'opencode.json' },
|
|
35
|
-
{ src: '.qa-workflow.json', dest: '.qa-workflow.json' },
|
|
36
|
-
{ src: 'prompts', dest: 'prompts', dir: true },
|
|
37
|
-
{ src: '.github/agents', dest: '.github/agents', dir: true },
|
|
38
|
-
{ src: '.opencode', dest: '.opencode', dir: true },
|
|
39
|
-
{ src: 'README.md', dest: 'README.md' },
|
|
40
|
-
{ src: 'PROJECT_GUIDE.md', dest: 'PROJECT_GUIDE.md' },
|
|
41
|
-
{ src: 'cursorrules.md', dest: 'cursorrules.md' },
|
|
42
|
-
{ src: 'geminirules.md', dest: 'geminirules.md' },
|
|
43
|
-
{ src: '.github/copilot-instructions.md', dest: '.github/copilot-instructions.md' },
|
|
44
|
-
{ src: 'agents/router.md', dest: 'agents/router.md' },
|
|
45
|
-
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
// Files to copy even during update (excludes user config files)
|
|
49
|
-
const UPDATE_ITEMS = QA_ITEMS.filter(item => {
|
|
50
|
-
if (item.dir) return !USER_DIRS.has(item.dest);
|
|
51
|
-
return !USER_FILES.has(item.dest);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const DIRS_TO_CREATE = ['user-story', 'specs', 'tests', 'test-results'];
|
|
55
|
-
|
|
56
|
-
const NPM_SCRIPTS = {
|
|
57
|
-
'qa': 'node ai-qa-workflow.js',
|
|
58
|
-
'qa:init': 'node ai-qa-workflow.js init',
|
|
59
|
-
'qa:plan': 'node ai-qa-workflow.js plan',
|
|
60
|
-
'qa:generate': 'node ai-qa-workflow.js generate',
|
|
61
|
-
'qa:execute': 'node ai-qa-workflow.js execute',
|
|
62
|
-
'qa:heal': 'node ai-qa-workflow.js heal',
|
|
63
|
-
'qa:report': 'node ai-qa-workflow.js report',
|
|
64
|
-
'qa:report:allure': 'node ai-qa-workflow.js report:allure',
|
|
65
|
-
'qa:run': 'node ai-qa-workflow.js run',
|
|
66
|
-
'qa:status': 'node ai-qa-workflow.js status',
|
|
67
|
-
'qa:list': 'node ai-qa-workflow.js list',
|
|
68
|
-
'dashboard': 'cd qa-dashboard && npm start',
|
|
69
|
-
'dashboard:dev': 'cd qa-dashboard && npx nodemon app.js',
|
|
70
|
-
'dashboard:stop': 'npx kill-port 4000',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const BANNER = `
|
|
74
|
-
╔══════════════════════════════════════════╗
|
|
75
|
-
║ AI QA Pipeline Installer v2.0 ║
|
|
76
|
-
╚══════════════════════════════════════════╝
|
|
77
|
-
`;
|
|
78
|
-
|
|
79
|
-
function copyRecursive(src, dest, skipDirs) {
|
|
80
|
-
skipDirs = skipDirs || new Set();
|
|
81
|
-
const stat = fs.statSync(src);
|
|
82
|
-
if (stat.isDirectory()) {
|
|
83
|
-
const base = path.basename(src);
|
|
84
|
-
if (skipDirs.has(base)) return;
|
|
85
|
-
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
86
|
-
fs.readdirSync(src).forEach(item => copyRecursive(path.join(src, item), path.join(dest, item), skipDirs));
|
|
87
|
-
} else {
|
|
88
|
-
if (!fs.existsSync(dest) || fs.readFileSync(src).length !== fs.readFileSync(dest).length) {
|
|
89
|
-
fs.copyFileSync(src, dest);
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function countFiles(dir) {
|
|
97
|
-
let count = 0;
|
|
98
|
-
if (!fs.existsSync(dir)) return 0;
|
|
99
|
-
fs.readdirSync(dir).forEach(item => {
|
|
100
|
-
try {
|
|
101
|
-
const full = path.join(dir, item);
|
|
102
|
-
if (fs.statSync(full).isDirectory()) count += countFiles(full);
|
|
103
|
-
else count++;
|
|
104
|
-
} catch (e) {}
|
|
105
|
-
});
|
|
106
|
-
return count;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function addNpmScripts(pkgPath, overwrite) {
|
|
110
|
-
if (!fs.existsSync(pkgPath)) return;
|
|
111
|
-
let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
112
|
-
if (!pkg.scripts) pkg.scripts = {};
|
|
113
|
-
let changed = 0;
|
|
114
|
-
for (const [key, val] of Object.entries(NPM_SCRIPTS)) {
|
|
115
|
-
if (!pkg.scripts[key] || overwrite) {
|
|
116
|
-
pkg.scripts[key] = val;
|
|
117
|
-
changed++;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
121
|
-
return changed;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function ask(query) {
|
|
125
|
-
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
126
|
-
return new Promise(resolve => rl.question(query, a => { rl.close(); resolve(a.toLowerCase()); }));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function install(targetPath, mode) {
|
|
130
|
-
const isUpdate = mode === 'update';
|
|
131
|
-
|
|
132
|
-
console.log(BANNER);
|
|
133
|
-
console.log(` Mode: ${isUpdate ? 'UPDATE (preserves config)' : 'FRESH INSTALL'}`);
|
|
134
|
-
console.log(` Target: ${targetPath}\n`);
|
|
135
|
-
|
|
136
|
-
// 1. Copy template files
|
|
137
|
-
const items = isUpdate ? UPDATE_ITEMS : QA_ITEMS;
|
|
138
|
-
console.log(` ── Step 1: QA Pipeline Files ──`);
|
|
139
|
-
for (const item of items) {
|
|
140
|
-
const srcPath = path.join(TEMPLATE_DIR, item.src);
|
|
141
|
-
const destPath = path.join(targetPath, item.dest);
|
|
142
|
-
if (!fs.existsSync(srcPath)) { console.log(` ⚠ ${item.src} not found, skipping`); continue; }
|
|
143
|
-
if (item.dir) {
|
|
144
|
-
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
|
|
145
|
-
const skipDirs = isUpdate ? new Set(['node_modules', 'data']) : new Set();
|
|
146
|
-
copyRecursive(srcPath, destPath, skipDirs);
|
|
147
|
-
const count = countFiles(srcPath);
|
|
148
|
-
console.log(` ✓ ${item.dest}/ (${count} files)`);
|
|
149
|
-
} else {
|
|
150
|
-
fs.copyFileSync(srcPath, destPath);
|
|
151
|
-
console.log(` ✓ ${item.dest}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 2. Create directories (fresh install only)
|
|
156
|
-
if (!isUpdate) {
|
|
157
|
-
console.log(`\n ── Step 2: Project Directories ──`);
|
|
158
|
-
for (const dir of DIRS_TO_CREATE) {
|
|
159
|
-
const dirPath = path.join(targetPath, dir);
|
|
160
|
-
if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); console.log(` ✓ ${dir}/`); }
|
|
161
|
-
else console.log(` • ${dir}/ (exists)`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 3. Add/update npm scripts
|
|
166
|
-
console.log(`\n ── Step 3: NPM Scripts ──`);
|
|
167
|
-
const pkgPath = path.join(targetPath, 'package.json');
|
|
168
|
-
const changed = addNpmScripts(pkgPath, isUpdate);
|
|
169
|
-
if (changed > 0) console.log(` ✓ ${isUpdate ? 'Updated' : 'Added'} ${changed} npm scripts (qa:*, dashboard)`);
|
|
170
|
-
else console.log(` • Scripts already configured`);
|
|
171
|
-
|
|
172
|
-
// 4. Dashboard
|
|
173
|
-
const dashboardSrc = path.join(TEMPLATE_DIR, 'qa-dashboard');
|
|
174
|
-
const dashboardDest = path.join(targetPath, 'qa-dashboard');
|
|
175
|
-
if (fs.existsSync(dashboardSrc)) {
|
|
176
|
-
console.log(`\n ── Step 4: QA Dashboard ──`);
|
|
177
|
-
if (!fs.existsSync(dashboardDest)) {
|
|
178
|
-
fs.mkdirSync(dashboardDest, { recursive: true });
|
|
179
|
-
copyRecursive(dashboardSrc, dashboardDest);
|
|
180
|
-
console.log(` ✓ Dashboard copied to qa-dashboard/`);
|
|
181
|
-
console.log(` → Installing dashboard dependencies...`);
|
|
182
|
-
try { execSync('npm install', { cwd: dashboardDest, stdio: 'pipe', timeout: 120000 }); console.log(` ✓ Dashboard deps installed`); } catch (e) { console.log(` ⚠ cd qa-dashboard && npm install`); }
|
|
183
|
-
} else {
|
|
184
|
-
const skipDirs = new Set(['node_modules', 'data']);
|
|
185
|
-
copyRecursive(dashboardSrc, dashboardDest, skipDirs);
|
|
186
|
-
console.log(` ✓ Dashboard updated (preserved data/, node_modules/)`);
|
|
187
|
-
if (!isUpdate) {
|
|
188
|
-
console.log(` → Installing dashboard dependencies...`);
|
|
189
|
-
try { execSync('npm install', { cwd: dashboardDest, stdio: 'pipe', timeout: 120000 }); console.log(` ✓ Dashboard deps installed`); } catch (e) { console.log(` ⚠ cd qa-dashboard && npm install`); }
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 5. Register project in dashboard (fresh install only)
|
|
195
|
-
if (!isUpdate && fs.existsSync(dashboardDest)) {
|
|
196
|
-
console.log(`\n ── Step 5: Link Dashboard → Project ──`);
|
|
197
|
-
const dashDataDir = path.join(dashboardDest, 'data');
|
|
198
|
-
const dashProjectsFile = path.join(dashDataDir, 'projects.json');
|
|
199
|
-
if (fs.existsSync(dashDataDir)) {
|
|
200
|
-
let projects = [];
|
|
201
|
-
if (fs.existsSync(dashProjectsFile)) {
|
|
202
|
-
try { projects = JSON.parse(fs.readFileSync(dashProjectsFile, 'utf-8')); } catch(e) {}
|
|
203
|
-
}
|
|
204
|
-
const projectName = path.basename(targetPath);
|
|
205
|
-
const projectId = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
206
|
-
if (!projects.find(p => p.id === projectId)) {
|
|
207
|
-
projects.push({ id: projectId, name: projectName, path: targetPath, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() });
|
|
208
|
-
fs.writeFileSync(dashProjectsFile, JSON.stringify(projects, null, 2));
|
|
209
|
-
console.log(` ✓ Project "${projectName}" registered in dashboard`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 6. Install Playwright (fresh only)
|
|
215
|
-
if (!isUpdate) {
|
|
216
|
-
console.log(`\n ── Step 6: Dependencies ──`);
|
|
217
|
-
if (!fs.existsSync(path.join(targetPath, 'node_modules', '@playwright'))) {
|
|
218
|
-
console.log(` → Installing @playwright/test...`);
|
|
219
|
-
try { execSync('npm install @playwright/test', { cwd: targetPath, stdio: 'pipe', timeout: 120000 }); console.log(` ✓ Playwright installed`); } catch (e) { console.log(` ⚠ npm install failed: npm install @playwright/test`); }
|
|
220
|
-
} else console.log(` • Playwright already installed`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Summary
|
|
224
|
-
const totalFiles = countFiles(path.join(targetPath, 'scripts'));
|
|
225
|
-
const dashboardCount = countFiles(path.join(targetPath, 'qa-dashboard'));
|
|
226
|
-
|
|
227
|
-
console.log(`\n ╔══════════════════════════════════════════╗`);
|
|
228
|
-
console.log(` ║ ✓ ${isUpdate ? 'UPDATE COMPLETE' : 'INSTALLATION COMPLETE'}${' '.repeat(isUpdate ? 13 : 7)}║`);
|
|
229
|
-
console.log(` ╚══════════════════════════════════════════╝\n`);
|
|
230
|
-
|
|
231
|
-
if (isUpdate) {
|
|
232
|
-
console.log(` Pipeline files updated. Your config and data are preserved.`);
|
|
233
|
-
console.log(` Restart the dashboard if it was running.\n`);
|
|
234
|
-
} else {
|
|
235
|
-
console.log(` Files: ~${totalFiles} scripts + ${dashboardCount} dashboard files`);
|
|
236
|
-
console.log(` Next:\n`);
|
|
237
|
-
console.log(` npm run qa:init Initialize pipeline (auto-detect config)`);
|
|
238
|
-
console.log(` npm run qa:run Run a user story through full pipeline`);
|
|
239
|
-
console.log(` npm run qa:status Check pipeline state`);
|
|
240
|
-
console.log(` npm run dashboard Start dashboard (port 4000)\n`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ---- CLI entry ----
|
|
245
|
-
const args = process.argv.slice(2);
|
|
246
|
-
const isHelp = !args.length || args.includes('--help') || args.includes('-h');
|
|
247
|
-
|
|
248
|
-
if (isHelp) {
|
|
249
|
-
console.log(`
|
|
250
|
-
AI QA Pipeline Installer
|
|
251
|
-
|
|
252
|
-
USAGE:
|
|
253
|
-
|
|
254
|
-
npx ai-qa-workflow init [--yes] Install into current directory
|
|
255
|
-
npx ai-qa-workflow update [--yes] Update pipeline files (preserves config)
|
|
256
|
-
|
|
257
|
-
Or from the template directory:
|
|
258
|
-
|
|
259
|
-
node install.js <target> [--yes] Fresh install into target project
|
|
260
|
-
node install.js <target> --update Update existing installation
|
|
261
|
-
node install.js . --yes Install into current directory
|
|
262
|
-
|
|
263
|
-
FLAGS:
|
|
264
|
-
--yes, -y Skip all prompts
|
|
265
|
-
--update,-u Update mode (preserves config, stories, results)
|
|
266
|
-
|
|
267
|
-
EXAMPLES:
|
|
268
|
-
npx ai-qa-workflow init --yes
|
|
269
|
-
node install.js ../my-project --yes
|
|
270
|
-
node install.js ../my-project --update
|
|
271
|
-
`);
|
|
272
|
-
process.exit(0);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function main() {
|
|
276
|
-
let targetPath;
|
|
277
|
-
let mode = 'install';
|
|
278
|
-
|
|
279
|
-
// Parse flags and target
|
|
280
|
-
const nonFlagArgs = args.filter(a => !a.startsWith('-'));
|
|
281
|
-
|
|
282
|
-
if (IS_SELF || args.includes('init')) {
|
|
283
|
-
targetPath = process.cwd();
|
|
284
|
-
mode = 'install';
|
|
285
|
-
} else if (args.includes('update')) {
|
|
286
|
-
targetPath = process.cwd();
|
|
287
|
-
mode = 'update';
|
|
288
|
-
} else if (IS_UPDATE) {
|
|
289
|
-
targetPath = path.resolve(nonFlagArgs[0] || process.cwd());
|
|
290
|
-
mode = 'update';
|
|
291
|
-
} else if (nonFlagArgs.length > 0) {
|
|
292
|
-
targetPath = path.resolve(nonFlagArgs[0]);
|
|
293
|
-
mode = 'install';
|
|
294
|
-
} else {
|
|
295
|
-
targetPath = process.cwd();
|
|
296
|
-
mode = 'install';
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (!fs.existsSync(targetPath) || !fs.statSync(targetPath).isDirectory()) {
|
|
300
|
-
console.error(` ✗ Target not found: ${targetPath}`);
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Confirm unless --yes
|
|
305
|
-
if (!YES) {
|
|
306
|
-
const modeLabel = mode === 'update' ? 'UPDATE pipeline files' : 'INSTALL pipeline';
|
|
307
|
-
const answer = await ask(` ${modeLabel} in "${path.basename(targetPath)}"? (Y/n) `);
|
|
308
|
-
if (answer.startsWith('n')) { console.log(' Aborted.'); process.exit(0); }
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
await install(targetPath, mode);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
main().catch(e => { console.error(e); process.exit(1); });
|