qa-flowkit 0.4.0-alpha.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/.qa-ai/adapters/aider/.aider/README.md +25 -0
- package/.qa-ai/adapters/aider/.aider.conf.yml +6 -0
- package/.qa-ai/adapters/claude/agents/qa-workflow-orchestrator.md +18 -0
- package/.qa-ai/adapters/claude/commands/qa-add-tests.md +42 -0
- package/.qa-ai/adapters/claude/commands/qa-automation-plan.md +43 -0
- package/.qa-ai/adapters/claude/commands/qa-clean.md +42 -0
- package/.qa-ai/adapters/claude/commands/qa-config.md +51 -0
- package/.qa-ai/adapters/claude/commands/qa-coverage.md +46 -0
- package/.qa-ai/adapters/claude/commands/qa-doctor.md +11 -0
- package/.qa-ai/adapters/claude/commands/qa-full-flow.md +59 -0
- package/.qa-ai/adapters/claude/commands/qa-gate.md +36 -0
- package/.qa-ai/adapters/claude/commands/qa-help.md +30 -0
- package/.qa-ai/adapters/claude/commands/qa-init.md +70 -0
- package/.qa-ai/adapters/claude/commands/qa-status.md +56 -0
- package/.qa-ai/adapters/claude/commands/qa-update-tests.md +47 -0
- package/.qa-ai/adapters/claude/commands/qa-validate-features.md +36 -0
- package/.qa-ai/adapters/cline/.cline/README.md +25 -0
- package/.qa-ai/adapters/cline/.clinerules +9 -0
- package/.qa-ai/adapters/codex/README.md +44 -0
- package/.qa-ai/adapters/codex/prompts/implement-project.md +15 -0
- package/.qa-ai/adapters/continue/README.md +26 -0
- package/.qa-ai/adapters/continue/checks/qa-feature-conventions.md +15 -0
- package/.qa-ai/adapters/gemini/GEMINI.md +40 -0
- package/.qa-ai/adapters/generic/AGENTS.md +100 -0
- package/.qa-ai/adapters/goose/recipes/qa-flowkit.yaml +20 -0
- package/.qa-ai/adapters/opencode/README.md +57 -0
- package/.qa-ai/adapters/opencode/agents/qa-workflow.md +18 -0
- package/.qa-ai/adapters/opencode/commands/qa-add-tests.md +42 -0
- package/.qa-ai/adapters/opencode/commands/qa-automation-plan.md +43 -0
- package/.qa-ai/adapters/opencode/commands/qa-clean.md +42 -0
- package/.qa-ai/adapters/opencode/commands/qa-config.md +51 -0
- package/.qa-ai/adapters/opencode/commands/qa-coverage.md +46 -0
- package/.qa-ai/adapters/opencode/commands/qa-doctor.md +13 -0
- package/.qa-ai/adapters/opencode/commands/qa-full-flow.md +59 -0
- package/.qa-ai/adapters/opencode/commands/qa-gate.md +36 -0
- package/.qa-ai/adapters/opencode/commands/qa-help.md +30 -0
- package/.qa-ai/adapters/opencode/commands/qa-init.md +70 -0
- package/.qa-ai/adapters/opencode/commands/qa-status.md +56 -0
- package/.qa-ai/adapters/opencode/commands/qa-update-tests.md +47 -0
- package/.qa-ai/adapters/opencode/commands/qa-validate-features.md +36 -0
- package/.qa-ai/agents/README.md +39 -0
- package/.qa-ai/agents/api-testing-agent.md +73 -0
- package/.qa-ai/agents/automation-feasibility-agent.md +128 -0
- package/.qa-ai/agents/gherkin-test-design-agent.md +110 -0
- package/.qa-ai/agents/jira-task-agent.md +92 -0
- package/.qa-ai/agents/pr-agent.md +101 -0
- package/.qa-ai/agents/qa-context-intake-agent.md +75 -0
- package/.qa-ai/agents/qa-workflow-orchestrator.md +113 -0
- package/.qa-ai/agents/release-gate-agent.md +50 -0
- package/.qa-ai/agents/requirements-intake-agent.md +79 -0
- package/.qa-ai/agents/requirements-normalization-agent.md +80 -0
- package/.qa-ai/agents/specialists/available/appium.md +59 -0
- package/.qa-ai/agents/specialists/available/cypress.md +68 -0
- package/.qa-ai/agents/specialists/available/generic-test-design.md +117 -0
- package/.qa-ai/agents/specialists/available/jira.md +108 -0
- package/.qa-ai/agents/specialists/available/karate.md +97 -0
- package/.qa-ai/agents/specialists/available/playwright-api.md +87 -0
- package/.qa-ai/agents/specialists/available/playwright-ui.md +87 -0
- package/.qa-ai/agents/specialists/available/postman.md +108 -0
- package/.qa-ai/agents/specialists/available/rest-assured.md +103 -0
- package/.qa-ai/agents/specialists/available/selenium.md +91 -0
- package/.qa-ai/agents/specialists/available/testrail.md +85 -0
- package/.qa-ai/agents/specialists/available/webdriverio.md +81 -0
- package/.qa-ai/agents/test-design-system-agent.md +33 -0
- package/.qa-ai/agents/testrail-coverage-agent.md +84 -0
- package/.qa-ai/agents/testrail-sync-agent.md +96 -0
- package/.qa-ai/agents/webdriverio-implementation-agent.md +84 -0
- package/.qa-ai/presets/manual-only.yaml +65 -0
- package/.qa-ai/presets/selenium-jest-browserstack.yaml +72 -0
- package/.qa-ai/presets/webdriverio-playwright-api.yaml +85 -0
- package/.qa-ai/rules/api-testing.rules.md +7 -0
- package/.qa-ai/rules/approval.rules.md +8 -0
- package/.qa-ai/rules/automation.rules.md +7 -0
- package/.qa-ai/rules/gherkin.rules.md +12 -0
- package/.qa-ai/rules/testrail.rules.md +10 -0
- package/.qa-ai/rules/webdriverio.rules.md +9 -0
- package/.qa-ai/scripts/bootstrap-agent-adapters.mjs +127 -0
- package/.qa-ai/scripts/clean.mjs +243 -0
- package/.qa-ai/scripts/config.mjs +202 -0
- package/.qa-ai/scripts/doctor.mjs +383 -0
- package/.qa-ai/scripts/init.mjs +447 -0
- package/.qa-ai/scripts/lib/markdown-table.mjs +76 -0
- package/.qa-ai/scripts/lib/project-config.mjs +184 -0
- package/.qa-ai/scripts/lib/qa-next-steps.mjs +578 -0
- package/.qa-ai/scripts/lib/release-gate.mjs +66 -0
- package/.qa-ai/scripts/lib/test-design.mjs +92 -0
- package/.qa-ai/scripts/lib/test-management-mapping.mjs +73 -0
- package/.qa-ai/scripts/lib/utils.mjs +331 -0
- package/.qa-ai/scripts/qa-help.mjs +44 -0
- package/.qa-ai/scripts/smoke-npm-pack.mjs +187 -0
- package/.qa-ai/scripts/smoke-test.mjs +465 -0
- package/.qa-ai/scripts/sync-agent-adapters.mjs +121 -0
- package/.qa-ai/scripts/test-validators.mjs +334 -0
- package/.qa-ai/scripts/validate-active-specialists.mjs +106 -0
- package/.qa-ai/scripts/validate-features.mjs +277 -0
- package/.qa-ai/scripts/validate-release-gate.mjs +105 -0
- package/.qa-ai/scripts/validate-sync-plan.mjs +186 -0
- package/.qa-ai/scripts/validate-target.mjs +104 -0
- package/.qa-ai/scripts/validate-test-design.mjs +117 -0
- package/.qa-ai/scripts/validate-traceability.mjs +183 -0
- package/.qa-ai/templates/automation-feasibility-report.template.md +21 -0
- package/.qa-ai/templates/automation-implementation-plan.template.md +23 -0
- package/.qa-ai/templates/feature.template +13 -0
- package/.qa-ai/templates/jira-automation-task.template.md +25 -0
- package/.qa-ai/templates/pr-template.md +60 -0
- package/.qa-ai/templates/release-gate.template.yaml +16 -0
- package/.qa-ai/templates/requirement-analysis.template.md +17 -0
- package/.qa-ai/templates/test-design-proposal.template.md +26 -0
- package/.qa-ai/templates/test-design-system.template.md +15 -0
- package/.qa-ai/templates/test-management-mapping.template.json +18 -0
- package/.qa-ai/templates/testrail-coverage-analysis.template.md +17 -0
- package/.qa-ai/templates/testrail-sync-plan.template.md +22 -0
- package/.qa-ai/templates/traceability-matrix.template.md +4 -0
- package/.qa-ai/workflows/automation-analysis.md +23 -0
- package/.qa-ai/workflows/cleanup.md +52 -0
- package/.qa-ai/workflows/context-intake.md +66 -0
- package/.qa-ai/workflows/full-flow.md +55 -0
- package/.qa-ai/workflows/implementation.md +24 -0
- package/.qa-ai/workflows/intake.md +3 -0
- package/.qa-ai/workflows/pr.md +3 -0
- package/.qa-ai/workflows/release-gate.md +22 -0
- package/.qa-ai/workflows/test-design-system.md +33 -0
- package/.qa-ai/workflows/test-design.md +23 -0
- package/.qa-ai/workflows/testrail-sync.md +23 -0
- package/CHANGELOG.md +108 -0
- package/CODE_OF_CONDUCT.md +11 -0
- package/CONTRIBUTING.md +39 -0
- package/LICENSE +21 -0
- package/README.es.md +602 -0
- package/README.md +633 -0
- package/ROADMAP.md +107 -0
- package/SECURITY.md +18 -0
- package/bin/qa-flowkit.mjs +214 -0
- package/docs/qa-ai/agent-compatibility.md +100 -0
- package/docs/qa-ai/architecture.md +130 -0
- package/docs/qa-ai/backlog.md +393 -0
- package/docs/qa-ai/cleanup.md +104 -0
- package/docs/qa-ai/customizing-agents.md +148 -0
- package/docs/qa-ai/getting-started.md +385 -0
- package/docs/qa-ai/implementation-guide-for-codex.md +210 -0
- package/docs/qa-ai/npm-migration-plan.md +50 -0
- package/docs/qa-ai/open-source-release-checklist.md +17 -0
- package/docs/qa-ai/qa-help.md +76 -0
- package/docs/qa-ai/release-gate.md +60 -0
- package/docs/qa-ai/terminal-transcripts.md +316 -0
- package/docs/qa-ai/test-design-dual-mode.md +75 -0
- package/docs/qa-ai/troubleshooting.md +740 -0
- package/docs/qa-ai/workflow.md +147 -0
- package/package.json +72 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const sourceRoot = process.cwd();
|
|
7
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
8
|
+
const node = process.execPath;
|
|
9
|
+
const npmExecPath = process.env.npm_execpath || '';
|
|
10
|
+
|
|
11
|
+
function run(command, args, { cwd, env = {}, expectFailure = false, shell = false } = {}) {
|
|
12
|
+
const childEnv = {
|
|
13
|
+
...process.env,
|
|
14
|
+
npm_config_update_notifier: 'false',
|
|
15
|
+
npm_config_fund: 'false',
|
|
16
|
+
npm_config_audit: 'false'
|
|
17
|
+
};
|
|
18
|
+
if (env.npm_config_cache) childEnv.npm_config_cache = env.npm_config_cache;
|
|
19
|
+
|
|
20
|
+
const result = spawnSync(command, args, {
|
|
21
|
+
cwd,
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
shell,
|
|
24
|
+
env: childEnv
|
|
25
|
+
});
|
|
26
|
+
const failed = result.status !== 0;
|
|
27
|
+
if (expectFailure ? !failed : failed) {
|
|
28
|
+
throw new Error([
|
|
29
|
+
`Command ${expectFailure ? 'succeeded unexpectedly' : 'failed'} (${result.status}): ${command} ${args.join(' ')}`,
|
|
30
|
+
result.error?.message,
|
|
31
|
+
result.stdout,
|
|
32
|
+
result.stderr
|
|
33
|
+
].filter(Boolean).join('\n'));
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runNpm(args, options = {}) {
|
|
39
|
+
if (npmExecPath) return run(node, [npmExecPath, ...args], options);
|
|
40
|
+
return run(npmCommand, args, {
|
|
41
|
+
...options,
|
|
42
|
+
shell: process.platform === 'win32'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function assertExists(filePath, label = filePath) {
|
|
47
|
+
try {
|
|
48
|
+
await fs.access(filePath);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new Error(`Expected ${label} to exist.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function assertMissing(filePath, label = filePath) {
|
|
55
|
+
try {
|
|
56
|
+
await fs.access(filePath);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.code === 'ENOENT') return;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`Expected ${label} to be absent.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parsePackOutput(stdout) {
|
|
65
|
+
const parsed = JSON.parse(stdout);
|
|
66
|
+
const item = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
67
|
+
if (!item?.filename || !Array.isArray(item.files)) {
|
|
68
|
+
throw new Error('Unexpected npm pack --json output.');
|
|
69
|
+
}
|
|
70
|
+
return item;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validatePackFileList(files) {
|
|
74
|
+
const names = files.map((file) => file.path).sort();
|
|
75
|
+
const required = [
|
|
76
|
+
'bin/qa-flowkit.mjs',
|
|
77
|
+
'.qa-ai/scripts/init.mjs',
|
|
78
|
+
'.qa-ai/scripts/doctor.mjs',
|
|
79
|
+
'.qa-ai/adapters/opencode/commands/qa-init.md',
|
|
80
|
+
'README.md',
|
|
81
|
+
'README.es.md',
|
|
82
|
+
'LICENSE',
|
|
83
|
+
'package.json'
|
|
84
|
+
];
|
|
85
|
+
const forbiddenPrefixes = [
|
|
86
|
+
'.github/',
|
|
87
|
+
'.claude/',
|
|
88
|
+
'.opencode/',
|
|
89
|
+
'.npm-cache/',
|
|
90
|
+
'qa-ai-output/',
|
|
91
|
+
'features/',
|
|
92
|
+
'tests/'
|
|
93
|
+
];
|
|
94
|
+
const forbiddenExact = [
|
|
95
|
+
'qa-ai.config.yaml',
|
|
96
|
+
'.qa-ai/state/init-manifest.json',
|
|
97
|
+
'.qa-ai/agents/specialists/active.md'
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const relPath of required) {
|
|
101
|
+
if (!names.includes(relPath)) throw new Error(`Pack file list is missing required path: ${relPath}`);
|
|
102
|
+
}
|
|
103
|
+
for (const relPath of names) {
|
|
104
|
+
if (forbiddenExact.includes(relPath) || forbiddenPrefixes.some((prefix) => relPath.startsWith(prefix))) {
|
|
105
|
+
throw new Error(`Pack file list includes forbidden path: ${relPath}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function cliPath(targetRoot) {
|
|
111
|
+
return path.join(targetRoot, 'node_modules', 'qa-flowkit', 'bin', 'qa-flowkit.mjs');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function runCli(targetRoot, args, options = {}) {
|
|
115
|
+
return run(node, [cliPath(targetRoot), ...args], {
|
|
116
|
+
cwd: targetRoot,
|
|
117
|
+
env: options.env,
|
|
118
|
+
expectFailure: options.expectFailure
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function installPackedCli(targetRoot, tarball, npmCache) {
|
|
123
|
+
runNpm(['install', '--prefix', targetRoot, '--ignore-scripts', '--package-lock=false', '--no-save', tarball], {
|
|
124
|
+
cwd: sourceRoot,
|
|
125
|
+
env: { npm_config_cache: npmCache }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function main() {
|
|
130
|
+
const tempRoot = await fs.mkdtemp(path.join(sourceRoot, '.qa-flowkit-npm-smoke-'));
|
|
131
|
+
const packDir = path.join(tempRoot, 'pack');
|
|
132
|
+
const npmCache = path.join(tempRoot, 'npm-cache');
|
|
133
|
+
let initTarget = null;
|
|
134
|
+
let updateTarget = null;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await fs.mkdir(packDir, { recursive: true });
|
|
138
|
+
await fs.mkdir(npmCache, { recursive: true });
|
|
139
|
+
|
|
140
|
+
const packResult = runNpm(['pack', '--pack-destination', packDir, '--json'], {
|
|
141
|
+
cwd: sourceRoot,
|
|
142
|
+
env: { npm_config_cache: npmCache }
|
|
143
|
+
});
|
|
144
|
+
const packInfo = parsePackOutput(packResult.stdout);
|
|
145
|
+
validatePackFileList(packInfo.files);
|
|
146
|
+
const tarball = path.join(packDir, packInfo.filename);
|
|
147
|
+
await assertExists(tarball, 'packed tarball');
|
|
148
|
+
|
|
149
|
+
initTarget = path.join(tempRoot, 'init-target');
|
|
150
|
+
await fs.mkdir(initTarget, { recursive: true });
|
|
151
|
+
await installPackedCli(initTarget, tarball, npmCache);
|
|
152
|
+
runCli(initTarget, ['init', '--skip-doctor']);
|
|
153
|
+
await assertExists(path.join(initTarget, '.qa-ai', 'scripts', 'init.mjs'), '.qa-ai framework');
|
|
154
|
+
await assertExists(path.join(initTarget, 'qa-ai.config.yaml'), 'generated config');
|
|
155
|
+
await assertExists(path.join(initTarget, 'features'), 'features directory');
|
|
156
|
+
await assertExists(path.join(initTarget, 'qa-ai-output'), 'qa-ai-output directory');
|
|
157
|
+
await assertExists(path.join(initTarget, '.opencode', 'commands', 'qa-init.md'), 'default OpenCode adapter');
|
|
158
|
+
runCli(initTarget, ['init', '--skip-doctor'], { expectFailure: true });
|
|
159
|
+
runCli(initTarget, ['doctor']);
|
|
160
|
+
runCli(initTarget, ['validate-target', '--allow-empty', '--allow-missing', '--no-strict-doctor']);
|
|
161
|
+
|
|
162
|
+
updateTarget = path.join(tempRoot, 'update-target');
|
|
163
|
+
await fs.mkdir(updateTarget, { recursive: true });
|
|
164
|
+
await installPackedCli(updateTarget, tarball, npmCache);
|
|
165
|
+
runCli(updateTarget, ['init', '--skip-doctor']);
|
|
166
|
+
await fs.mkdir(path.join(updateTarget, '.qa-ai', 'state'), { recursive: true });
|
|
167
|
+
await fs.mkdir(path.join(updateTarget, '.qa-ai', 'config-profiles'), { recursive: true });
|
|
168
|
+
await fs.writeFile(path.join(updateTarget, '.qa-ai', 'state', 'keep.json'), '{}\n', 'utf8');
|
|
169
|
+
await fs.writeFile(path.join(updateTarget, '.qa-ai', 'config-profiles', 'team.yaml'), 'version: 1\n', 'utf8');
|
|
170
|
+
await fs.writeFile(path.join(updateTarget, '.qa-ai', 'obsolete.txt'), 'old\n', 'utf8');
|
|
171
|
+
await fs.writeFile(path.join(updateTarget, 'qa-ai-output', 'user.md'), 'USER\n', 'utf8');
|
|
172
|
+
runCli(updateTarget, ['update', '--skip-doctor']);
|
|
173
|
+
await assertExists(path.join(updateTarget, '.qa-ai', 'state', 'keep.json'), 'preserved state');
|
|
174
|
+
await assertExists(path.join(updateTarget, '.qa-ai', 'config-profiles', 'team.yaml'), 'preserved config profile');
|
|
175
|
+
await assertMissing(path.join(updateTarget, '.qa-ai', 'obsolete.txt'), 'obsolete framework file');
|
|
176
|
+
await assertExists(path.join(updateTarget, 'qa-ai-output', 'user.md'), 'target output artifact');
|
|
177
|
+
|
|
178
|
+
console.log('npm pack smoke tests passed.');
|
|
179
|
+
} finally {
|
|
180
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main().catch((error) => {
|
|
185
|
+
console.error(error);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { parseSimpleYaml, readText } from './lib/utils.mjs';
|
|
7
|
+
|
|
8
|
+
const sourceRoot = process.cwd();
|
|
9
|
+
const node = process.execPath;
|
|
10
|
+
|
|
11
|
+
function run(cwd, args, { expectFailure = false } = {}) {
|
|
12
|
+
const result = spawnSync(node, args, {
|
|
13
|
+
cwd,
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
shell: false
|
|
16
|
+
});
|
|
17
|
+
const failed = result.status !== 0;
|
|
18
|
+
if (expectFailure ? !failed : failed) {
|
|
19
|
+
throw new Error([
|
|
20
|
+
`Command ${expectFailure ? 'succeeded unexpectedly' : 'failed'}: node ${args.join(' ')}`,
|
|
21
|
+
result.stdout,
|
|
22
|
+
result.stderr
|
|
23
|
+
].filter(Boolean).join('\n'));
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function copyFramework(targetRoot) {
|
|
29
|
+
await fs.cp(path.join(sourceRoot, '.qa-ai'), path.join(targetRoot, '.qa-ai'), {
|
|
30
|
+
recursive: true,
|
|
31
|
+
force: false
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-smoke-'));
|
|
37
|
+
let unsafeRoot = null;
|
|
38
|
+
let defaultTarget = null;
|
|
39
|
+
let geminiTarget = null;
|
|
40
|
+
let qaContextTarget = null;
|
|
41
|
+
let optionalDocsTarget = null;
|
|
42
|
+
let strictTarget = null;
|
|
43
|
+
let quickStrictTarget = null;
|
|
44
|
+
let importProfileTarget = null;
|
|
45
|
+
let validatorTarget = null;
|
|
46
|
+
try {
|
|
47
|
+
await copyFramework(tempRoot);
|
|
48
|
+
|
|
49
|
+
run(tempRoot, [
|
|
50
|
+
'.qa-ai/scripts/init.mjs',
|
|
51
|
+
'--preset', 'webdriverio-playwright-api',
|
|
52
|
+
'--interface-language', 'en',
|
|
53
|
+
'--gherkin-language', 'en',
|
|
54
|
+
'--ui-framework', 'webdriverio',
|
|
55
|
+
'--api-framework', 'playwright-api',
|
|
56
|
+
'--adapters', 'generic'
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const config = parseSimpleYaml(await readText(path.join(tempRoot, 'qa-ai.config.yaml')));
|
|
60
|
+
if (config.automation.ui.specsPath !== 'tests/wdio/specs') {
|
|
61
|
+
throw new Error(`Expected preset UI path tests/wdio/specs, got ${config.automation.ui.specsPath}`);
|
|
62
|
+
}
|
|
63
|
+
if (config.automation.api.specsPath !== 'tests/api/specs') {
|
|
64
|
+
throw new Error(`Expected preset API path tests/api/specs, got ${config.automation.api.specsPath}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
run(tempRoot, [
|
|
68
|
+
'.qa-ai/scripts/config.mjs',
|
|
69
|
+
'--export', '.qa-ai/config-profiles/team.yaml'
|
|
70
|
+
]);
|
|
71
|
+
importProfileTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-import-'));
|
|
72
|
+
await copyFramework(importProfileTarget);
|
|
73
|
+
await fs.mkdir(path.join(importProfileTarget, '.qa-ai/config-profiles'), { recursive: true });
|
|
74
|
+
await fs.copyFile(
|
|
75
|
+
path.join(tempRoot, '.qa-ai/config-profiles/team.yaml'),
|
|
76
|
+
path.join(importProfileTarget, '.qa-ai/config-profiles/team.yaml')
|
|
77
|
+
);
|
|
78
|
+
run(importProfileTarget, [
|
|
79
|
+
'.qa-ai/scripts/config.mjs',
|
|
80
|
+
'--import', '.qa-ai/config-profiles/team.yaml'
|
|
81
|
+
]);
|
|
82
|
+
const importedConfig = parseSimpleYaml(await readText(path.join(importProfileTarget, 'qa-ai.config.yaml')));
|
|
83
|
+
if (importedConfig.automation.ui.specsPath !== 'tests/wdio/specs') {
|
|
84
|
+
throw new Error(`Imported config did not preserve UI specs path, got ${importedConfig.automation.ui.specsPath}`);
|
|
85
|
+
}
|
|
86
|
+
const expectedImportPaths = [
|
|
87
|
+
'.qa-ai/agents/specialists/active.md',
|
|
88
|
+
'features',
|
|
89
|
+
'qa-ai-output',
|
|
90
|
+
'tests/wdio/specs',
|
|
91
|
+
'tests/api/specs'
|
|
92
|
+
];
|
|
93
|
+
for (const relPath of expectedImportPaths) {
|
|
94
|
+
try {
|
|
95
|
+
await fs.access(path.join(importProfileTarget, relPath));
|
|
96
|
+
} catch {
|
|
97
|
+
throw new Error(`Config import did not create expected path: ${relPath}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
defaultTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-default-'));
|
|
102
|
+
await copyFramework(defaultTarget);
|
|
103
|
+
run(defaultTarget, ['.qa-ai/scripts/init.mjs']);
|
|
104
|
+
const expectedDefaultPaths = [
|
|
105
|
+
'.opencode/README.md',
|
|
106
|
+
'.opencode/commands/qa-init.md',
|
|
107
|
+
'qa-ai.config.yaml',
|
|
108
|
+
'qa-ai-output',
|
|
109
|
+
'features'
|
|
110
|
+
];
|
|
111
|
+
for (const relPath of expectedDefaultPaths) {
|
|
112
|
+
try {
|
|
113
|
+
await fs.access(path.join(defaultTarget, relPath));
|
|
114
|
+
} catch {
|
|
115
|
+
throw new Error(`Default init did not create expected path: ${relPath}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const unexpectedDefaultPaths = [
|
|
119
|
+
'.claude',
|
|
120
|
+
'.codex',
|
|
121
|
+
'qa-ai-output/requirement-analysis.md',
|
|
122
|
+
'qa-ai-output/test-management-mapping.json'
|
|
123
|
+
];
|
|
124
|
+
for (const relPath of unexpectedDefaultPaths) {
|
|
125
|
+
try {
|
|
126
|
+
await fs.access(path.join(defaultTarget, relPath));
|
|
127
|
+
throw new Error(`Default init created unexpected path: ${relPath}`);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error.code !== 'ENOENT') throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
run(defaultTarget, ['.qa-ai/scripts/doctor.mjs']);
|
|
133
|
+
|
|
134
|
+
geminiTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-gemini-'));
|
|
135
|
+
await copyFramework(geminiTarget);
|
|
136
|
+
run(geminiTarget, [
|
|
137
|
+
'.qa-ai/scripts/init.mjs',
|
|
138
|
+
'--adapters', 'gemini'
|
|
139
|
+
]);
|
|
140
|
+
const expectedGeminiPaths = [
|
|
141
|
+
'AGENTS.md',
|
|
142
|
+
'GEMINI.md'
|
|
143
|
+
];
|
|
144
|
+
for (const relPath of expectedGeminiPaths) {
|
|
145
|
+
try {
|
|
146
|
+
await fs.access(path.join(geminiTarget, relPath));
|
|
147
|
+
} catch {
|
|
148
|
+
throw new Error(`Gemini adapter init did not create expected path: ${relPath}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
qaContextTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-context-'));
|
|
153
|
+
await copyFramework(qaContextTarget);
|
|
154
|
+
await fs.mkdir(path.join(qaContextTarget, 'qa-ai-knowledge'), { recursive: true });
|
|
155
|
+
await fs.writeFile(
|
|
156
|
+
path.join(qaContextTarget, 'qa-ai-knowledge', 'qa-process.md'),
|
|
157
|
+
'# QA Process\n\nUse Jira, TestRail and English Gherkin.\n',
|
|
158
|
+
'utf8'
|
|
159
|
+
);
|
|
160
|
+
run(qaContextTarget, [
|
|
161
|
+
'.qa-ai/scripts/init.mjs',
|
|
162
|
+
'--qa-context', 'qa-ai-knowledge',
|
|
163
|
+
'--no-adapters'
|
|
164
|
+
]);
|
|
165
|
+
const qaContextConfig = parseSimpleYaml(await readText(path.join(qaContextTarget, 'qa-ai.config.yaml')));
|
|
166
|
+
if (qaContextConfig.knowledge.enabled !== true) {
|
|
167
|
+
throw new Error('QA context init did not enable knowledge config.');
|
|
168
|
+
}
|
|
169
|
+
if (qaContextConfig.knowledge.sourcePath !== 'qa-ai-knowledge') {
|
|
170
|
+
throw new Error(`QA context init did not preserve sourcePath, got ${qaContextConfig.knowledge.sourcePath}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
optionalDocsTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-docs-'));
|
|
174
|
+
await copyFramework(optionalDocsTarget);
|
|
175
|
+
run(optionalDocsTarget, [
|
|
176
|
+
'.qa-ai/scripts/init.mjs',
|
|
177
|
+
'--with-doc-templates',
|
|
178
|
+
'--with-test-management-mapping',
|
|
179
|
+
'--no-adapters'
|
|
180
|
+
]);
|
|
181
|
+
const expectedOptionalDocPaths = [
|
|
182
|
+
'qa-ai-output/requirement-analysis.md',
|
|
183
|
+
'qa-ai-output/test-design-system.md',
|
|
184
|
+
'qa-ai-output/test-design-proposal.md',
|
|
185
|
+
'qa-ai-output/traceability-matrix.md',
|
|
186
|
+
'qa-ai-output/test-management-mapping.json'
|
|
187
|
+
];
|
|
188
|
+
for (const relPath of expectedOptionalDocPaths) {
|
|
189
|
+
try {
|
|
190
|
+
await fs.access(path.join(optionalDocsTarget, relPath));
|
|
191
|
+
} catch {
|
|
192
|
+
throw new Error(`Optional doc init did not create expected path: ${relPath}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
strictTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-strict-'));
|
|
197
|
+
await copyFramework(strictTarget);
|
|
198
|
+
run(strictTarget, [
|
|
199
|
+
'.qa-ai/scripts/init.mjs',
|
|
200
|
+
'--preset', 'webdriverio-playwright-api',
|
|
201
|
+
'--with-doc-templates',
|
|
202
|
+
'--with-test-management-mapping',
|
|
203
|
+
'--adapters', 'generic'
|
|
204
|
+
]);
|
|
205
|
+
await fs.writeFile(path.join(strictTarget, 'wdio.conf.js'), 'export const config = {};\n', 'utf8');
|
|
206
|
+
await fs.writeFile(path.join(strictTarget, 'playwright.config.js'), 'export default {};\n', 'utf8');
|
|
207
|
+
run(strictTarget, ['.qa-ai/scripts/doctor.mjs', '--strict']);
|
|
208
|
+
await fs.rm(path.join(strictTarget, 'qa-ai-output', 'traceability-matrix.md'), { force: true });
|
|
209
|
+
run(strictTarget, ['.qa-ai/scripts/doctor.mjs', '--strict'], { expectFailure: true });
|
|
210
|
+
|
|
211
|
+
quickStrictTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-quick-strict-'));
|
|
212
|
+
await copyFramework(quickStrictTarget);
|
|
213
|
+
run(quickStrictTarget, [
|
|
214
|
+
'.qa-ai/scripts/init.mjs',
|
|
215
|
+
'--preset', 'manual-only',
|
|
216
|
+
'--no-adapters'
|
|
217
|
+
]);
|
|
218
|
+
await fs.writeFile(
|
|
219
|
+
path.join(quickStrictTarget, 'qa-ai-output', 'requirement-analysis.md'),
|
|
220
|
+
'# Requirement Analysis\n\nRF-101 login.\n',
|
|
221
|
+
'utf8'
|
|
222
|
+
);
|
|
223
|
+
await fs.writeFile(
|
|
224
|
+
path.join(quickStrictTarget, 'qa-ai-output', 'traceability-matrix.md'),
|
|
225
|
+
'# Traceability Matrix\n\n| Requirement Source | RF | CA | Feature File | Test Management Case ID | Type | Priority | Automation Status | Automation File |\n|---|---|---|---|---|---|---|---|---|\n',
|
|
226
|
+
'utf8'
|
|
227
|
+
);
|
|
228
|
+
await fs.writeFile(
|
|
229
|
+
path.join(quickStrictTarget, 'qa-ai-output', 'pr-summary.md'),
|
|
230
|
+
'# PR Summary\n\nQuick track summary.\n',
|
|
231
|
+
'utf8'
|
|
232
|
+
);
|
|
233
|
+
run(quickStrictTarget, ['.qa-ai/scripts/doctor.mjs', '--strict']);
|
|
234
|
+
|
|
235
|
+
validatorTarget = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-validators-'));
|
|
236
|
+
await copyFramework(validatorTarget);
|
|
237
|
+
run(validatorTarget, [
|
|
238
|
+
'.qa-ai/scripts/init.mjs',
|
|
239
|
+
'--preset', 'manual-only',
|
|
240
|
+
'--no-adapters'
|
|
241
|
+
]);
|
|
242
|
+
await fs.mkdir(path.join(validatorTarget, 'features', 'functional'), { recursive: true });
|
|
243
|
+
await fs.writeFile(
|
|
244
|
+
path.join(validatorTarget, 'features', 'functional', 'RF-101-TC-001-login.feature'),
|
|
245
|
+
[
|
|
246
|
+
'@priority:high @type:functional @manual:false @id:TC-001',
|
|
247
|
+
'Feature: RF-101 Login',
|
|
248
|
+
'',
|
|
249
|
+
'Acceptance Criteria:',
|
|
250
|
+
'- User can sign in with valid credentials.',
|
|
251
|
+
'',
|
|
252
|
+
'Scenario: RF-101 TC-001 Valid login',
|
|
253
|
+
' Given a registered user',
|
|
254
|
+
' When the user signs in',
|
|
255
|
+
' Then the dashboard is displayed',
|
|
256
|
+
''
|
|
257
|
+
].join('\n'),
|
|
258
|
+
'utf8'
|
|
259
|
+
);
|
|
260
|
+
await fs.writeFile(
|
|
261
|
+
path.join(validatorTarget, 'qa-ai-output', 'traceability-matrix.md'),
|
|
262
|
+
[
|
|
263
|
+
'# Traceability Matrix',
|
|
264
|
+
'',
|
|
265
|
+
'| Requirement Source | RF | CA | Feature File | Test Management Case ID | Type | Priority | Automation Status | Automation File |',
|
|
266
|
+
'|---|---|---|---|---|---|---|---|---|',
|
|
267
|
+
'| Jira | RF-101 | User can sign in with valid credentials. | features/functional/RF-101-TC-001-login.feature | TC-001 | functional | high | automated | tests/login.spec.js |',
|
|
268
|
+
''
|
|
269
|
+
].join('\n'),
|
|
270
|
+
'utf8'
|
|
271
|
+
);
|
|
272
|
+
await fs.writeFile(path.join(validatorTarget, 'qa-ai-output', 'requirement-analysis.md'), '# Requirement Analysis\n\nRF-101 login.\n', 'utf8');
|
|
273
|
+
await fs.writeFile(
|
|
274
|
+
path.join(validatorTarget, 'qa-ai-output', 'test-design-proposal.md'),
|
|
275
|
+
[
|
|
276
|
+
'# Test Design Proposal (per RF / epic)',
|
|
277
|
+
'',
|
|
278
|
+
'## Official RF ID',
|
|
279
|
+
'RF-101',
|
|
280
|
+
'',
|
|
281
|
+
'## Scope',
|
|
282
|
+
'Login for RF-101.',
|
|
283
|
+
'',
|
|
284
|
+
'## Proposed tests',
|
|
285
|
+
'| RF | CA | Test ID | Title | Type | Priority | Manual | Action |',
|
|
286
|
+
'|---|---|---|---|---|---|---|---|',
|
|
287
|
+
'| RF-101 | Valid login | TC-001 | Valid login | functional | high | false | create |',
|
|
288
|
+
'',
|
|
289
|
+
'## Existing tests to reuse',
|
|
290
|
+
'None.',
|
|
291
|
+
'',
|
|
292
|
+
'## Existing tests requiring modification',
|
|
293
|
+
'None.',
|
|
294
|
+
'',
|
|
295
|
+
'## New tests to create',
|
|
296
|
+
'TC-001.',
|
|
297
|
+
'',
|
|
298
|
+
'## Ambiguities requiring user decision',
|
|
299
|
+
'None.',
|
|
300
|
+
'',
|
|
301
|
+
'## Approval request',
|
|
302
|
+
'Approve TC-001 feature generation.',
|
|
303
|
+
''
|
|
304
|
+
].join('\n'),
|
|
305
|
+
'utf8'
|
|
306
|
+
);
|
|
307
|
+
await fs.writeFile(path.join(validatorTarget, 'qa-ai-output', 'testrail-coverage-analysis.md'), '# TestRail Coverage Analysis\n\nRF-101 coverage.\n', 'utf8');
|
|
308
|
+
await fs.writeFile(path.join(validatorTarget, 'qa-ai-output', 'pr-summary.md'), '# PR Summary\n\nValidation pending.\n', 'utf8');
|
|
309
|
+
await fs.writeFile(
|
|
310
|
+
path.join(validatorTarget, 'qa-ai-output', 'testrail-sync-plan.md'),
|
|
311
|
+
[
|
|
312
|
+
'# TestRail Sync Plan',
|
|
313
|
+
'',
|
|
314
|
+
'Approval is required before any external write.',
|
|
315
|
+
'',
|
|
316
|
+
'| ID | Proposed action | Approval status | Target section | Notes |',
|
|
317
|
+
'|---|---|---|---|---|',
|
|
318
|
+
'| RF-101 | Review coverage | Pending approval | Login | Requirement coverage check |',
|
|
319
|
+
'| TC-001 | Propose create/update only | Approval required | Login | Do not sync without approval |',
|
|
320
|
+
''
|
|
321
|
+
].join('\n'),
|
|
322
|
+
'utf8'
|
|
323
|
+
);
|
|
324
|
+
await fs.writeFile(
|
|
325
|
+
path.join(validatorTarget, 'qa-ai-output', 'test-management-mapping.json'),
|
|
326
|
+
`${JSON.stringify({
|
|
327
|
+
'TC-001': {
|
|
328
|
+
externalId: 'C123',
|
|
329
|
+
section: 'Login',
|
|
330
|
+
suite: 'Regression',
|
|
331
|
+
status: 'planned',
|
|
332
|
+
lastReviewedAt: '2026-05-25',
|
|
333
|
+
notes: 'Mapped from validated sync proposal.'
|
|
334
|
+
}
|
|
335
|
+
}, null, 2)}\n`,
|
|
336
|
+
'utf8'
|
|
337
|
+
);
|
|
338
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-features.mjs']);
|
|
339
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-traceability.mjs']);
|
|
340
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-target.mjs']);
|
|
341
|
+
await fs.rm(path.join(validatorTarget, 'qa-ai-output', 'traceability-matrix.md'), { force: true });
|
|
342
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-target.mjs'], { expectFailure: true });
|
|
343
|
+
await fs.writeFile(
|
|
344
|
+
path.join(validatorTarget, 'qa-ai-output', 'traceability-matrix.md'),
|
|
345
|
+
[
|
|
346
|
+
'# Traceability Matrix',
|
|
347
|
+
'',
|
|
348
|
+
'| Requirement Source | RF | CA | Feature File | Test Management Case ID | Type | Priority | Automation Status | Automation File |',
|
|
349
|
+
'|---|---|---|---|---|---|---|---|---|',
|
|
350
|
+
'| Jira | RF-101 | User can sign in with valid credentials. | features/functional/RF-101-TC-001-login.feature | TC-001 | functional | high | automated | tests/login.spec.js |',
|
|
351
|
+
''
|
|
352
|
+
].join('\n'),
|
|
353
|
+
'utf8'
|
|
354
|
+
);
|
|
355
|
+
await fs.writeFile(
|
|
356
|
+
path.join(validatorTarget, 'qa-ai-output', 'traceability-matrix.md'),
|
|
357
|
+
[
|
|
358
|
+
'# Traceability Matrix',
|
|
359
|
+
'',
|
|
360
|
+
'| Requirement Source | RF | CA | Feature File | Test Management Case ID | Type | Priority | Automation Status | Automation File |',
|
|
361
|
+
'|---|---|---|---|---|---|---|---|---|',
|
|
362
|
+
'| Jira | RF-101 | User can sign in with valid credentials. | features/functional/RF-101-TC-001-login.feature | TC-001 | functional | high | automated | tests/login.spec.js |',
|
|
363
|
+
'| Jira | RF-101 | User can sign in with valid credentials. | features/functional/RF-101-TC-001-login.feature | TC-001 | functional | high | automated | tests/login.spec.js |',
|
|
364
|
+
''
|
|
365
|
+
].join('\n'),
|
|
366
|
+
'utf8'
|
|
367
|
+
);
|
|
368
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-traceability.mjs'], { expectFailure: true });
|
|
369
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-sync-plan.mjs']);
|
|
370
|
+
await fs.writeFile(
|
|
371
|
+
path.join(validatorTarget, 'qa-ai-output', 'testrail-sync-plan.md'),
|
|
372
|
+
[
|
|
373
|
+
'# TestRail Sync Plan',
|
|
374
|
+
'',
|
|
375
|
+
'Approval is required before any external write.',
|
|
376
|
+
'',
|
|
377
|
+
'| ID | Proposed action | Approval status | Target section | Notes |',
|
|
378
|
+
'|---|---|---|---|---|',
|
|
379
|
+
'| TC-001 | Created in TestRail | Done | Login | Direct write claim |',
|
|
380
|
+
'| TC-001 | Updated in TestRail | Done | Login | Duplicate direct write claim |',
|
|
381
|
+
''
|
|
382
|
+
].join('\n'),
|
|
383
|
+
'utf8'
|
|
384
|
+
);
|
|
385
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-sync-plan.mjs'], { expectFailure: true });
|
|
386
|
+
await fs.writeFile(
|
|
387
|
+
path.join(validatorTarget, 'qa-ai-output', 'testrail-sync-plan.md'),
|
|
388
|
+
[
|
|
389
|
+
'# TestRail Sync Plan',
|
|
390
|
+
'',
|
|
391
|
+
'Approval is required before any external write.',
|
|
392
|
+
'',
|
|
393
|
+
'| ID | Proposed action | Approval status | Target section | Notes |',
|
|
394
|
+
'|---|---|---|---|---|',
|
|
395
|
+
'| RF-101 | Review coverage | Pending approval | Login | Requirement coverage check |',
|
|
396
|
+
'| TC-001 | Propose create/update only | Approval required | Login | Do not sync without approval |',
|
|
397
|
+
''
|
|
398
|
+
].join('\n'),
|
|
399
|
+
'utf8'
|
|
400
|
+
);
|
|
401
|
+
await fs.writeFile(
|
|
402
|
+
path.join(validatorTarget, 'qa-ai-output', 'test-management-mapping.json'),
|
|
403
|
+
`${JSON.stringify({
|
|
404
|
+
'TC-001': { externalId: 'C123' },
|
|
405
|
+
'TC-002': { externalId: 'C123' }
|
|
406
|
+
}, null, 2)}\n`,
|
|
407
|
+
'utf8'
|
|
408
|
+
);
|
|
409
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-sync-plan.mjs'], { expectFailure: true });
|
|
410
|
+
run(validatorTarget, ['.qa-ai/scripts/validate-active-specialists.mjs']);
|
|
411
|
+
|
|
412
|
+
const preservedPath = path.join(tempRoot, 'qa-ai-output', 'requirement-analysis.md');
|
|
413
|
+
await fs.mkdir(path.dirname(preservedPath), { recursive: true });
|
|
414
|
+
await fs.writeFile(preservedPath, 'USER EDIT\n', 'utf8');
|
|
415
|
+
run(tempRoot, [
|
|
416
|
+
'.qa-ai/scripts/init.mjs',
|
|
417
|
+
'--preset', 'webdriverio-playwright-api',
|
|
418
|
+
'--interface-language', 'en',
|
|
419
|
+
'--gherkin-language', 'en',
|
|
420
|
+
'--adapters', 'generic'
|
|
421
|
+
]);
|
|
422
|
+
const preservedContent = await readText(preservedPath);
|
|
423
|
+
if (preservedContent !== 'USER EDIT\n') {
|
|
424
|
+
throw new Error('requirement-analysis.md was overwritten without --force.');
|
|
425
|
+
}
|
|
426
|
+
const activePath = path.join(tempRoot, '.qa-ai/agents/specialists/active.md');
|
|
427
|
+
const activeContent = await readText(activePath);
|
|
428
|
+
if (!activeContent.includes('Active QA AI Specialists')) {
|
|
429
|
+
throw new Error('active.md was not regenerated from init config.');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
unsafeRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'qa-flowkit-unsafe-'));
|
|
433
|
+
await copyFramework(unsafeRoot);
|
|
434
|
+
run(unsafeRoot, [
|
|
435
|
+
'.qa-ai/scripts/init.mjs',
|
|
436
|
+
'--preset', 'manual-only',
|
|
437
|
+
'--set', 'traceability.matrixPath=../traceability.md',
|
|
438
|
+
'--no-adapters'
|
|
439
|
+
], { expectFailure: true });
|
|
440
|
+
run(unsafeRoot, [
|
|
441
|
+
'.qa-ai/scripts/init.mjs',
|
|
442
|
+
'--preset', 'manual-only',
|
|
443
|
+
'--qa-context', '../qa-knowledge',
|
|
444
|
+
'--no-adapters'
|
|
445
|
+
], { expectFailure: true });
|
|
446
|
+
|
|
447
|
+
console.log('Smoke tests passed.');
|
|
448
|
+
} finally {
|
|
449
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
450
|
+
if (unsafeRoot) await fs.rm(unsafeRoot, { recursive: true, force: true });
|
|
451
|
+
if (defaultTarget) await fs.rm(defaultTarget, { recursive: true, force: true });
|
|
452
|
+
if (geminiTarget) await fs.rm(geminiTarget, { recursive: true, force: true });
|
|
453
|
+
if (qaContextTarget) await fs.rm(qaContextTarget, { recursive: true, force: true });
|
|
454
|
+
if (optionalDocsTarget) await fs.rm(optionalDocsTarget, { recursive: true, force: true });
|
|
455
|
+
if (strictTarget) await fs.rm(strictTarget, { recursive: true, force: true });
|
|
456
|
+
if (quickStrictTarget) await fs.rm(quickStrictTarget, { recursive: true, force: true });
|
|
457
|
+
if (importProfileTarget) await fs.rm(importProfileTarget, { recursive: true, force: true });
|
|
458
|
+
if (validatorTarget) await fs.rm(validatorTarget, { recursive: true, force: true });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
main().catch((error) => {
|
|
463
|
+
console.error(error);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
});
|