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.
Files changed (149) hide show
  1. package/.qa-ai/adapters/aider/.aider/README.md +25 -0
  2. package/.qa-ai/adapters/aider/.aider.conf.yml +6 -0
  3. package/.qa-ai/adapters/claude/agents/qa-workflow-orchestrator.md +18 -0
  4. package/.qa-ai/adapters/claude/commands/qa-add-tests.md +42 -0
  5. package/.qa-ai/adapters/claude/commands/qa-automation-plan.md +43 -0
  6. package/.qa-ai/adapters/claude/commands/qa-clean.md +42 -0
  7. package/.qa-ai/adapters/claude/commands/qa-config.md +51 -0
  8. package/.qa-ai/adapters/claude/commands/qa-coverage.md +46 -0
  9. package/.qa-ai/adapters/claude/commands/qa-doctor.md +11 -0
  10. package/.qa-ai/adapters/claude/commands/qa-full-flow.md +59 -0
  11. package/.qa-ai/adapters/claude/commands/qa-gate.md +36 -0
  12. package/.qa-ai/adapters/claude/commands/qa-help.md +30 -0
  13. package/.qa-ai/adapters/claude/commands/qa-init.md +70 -0
  14. package/.qa-ai/adapters/claude/commands/qa-status.md +56 -0
  15. package/.qa-ai/adapters/claude/commands/qa-update-tests.md +47 -0
  16. package/.qa-ai/adapters/claude/commands/qa-validate-features.md +36 -0
  17. package/.qa-ai/adapters/cline/.cline/README.md +25 -0
  18. package/.qa-ai/adapters/cline/.clinerules +9 -0
  19. package/.qa-ai/adapters/codex/README.md +44 -0
  20. package/.qa-ai/adapters/codex/prompts/implement-project.md +15 -0
  21. package/.qa-ai/adapters/continue/README.md +26 -0
  22. package/.qa-ai/adapters/continue/checks/qa-feature-conventions.md +15 -0
  23. package/.qa-ai/adapters/gemini/GEMINI.md +40 -0
  24. package/.qa-ai/adapters/generic/AGENTS.md +100 -0
  25. package/.qa-ai/adapters/goose/recipes/qa-flowkit.yaml +20 -0
  26. package/.qa-ai/adapters/opencode/README.md +57 -0
  27. package/.qa-ai/adapters/opencode/agents/qa-workflow.md +18 -0
  28. package/.qa-ai/adapters/opencode/commands/qa-add-tests.md +42 -0
  29. package/.qa-ai/adapters/opencode/commands/qa-automation-plan.md +43 -0
  30. package/.qa-ai/adapters/opencode/commands/qa-clean.md +42 -0
  31. package/.qa-ai/adapters/opencode/commands/qa-config.md +51 -0
  32. package/.qa-ai/adapters/opencode/commands/qa-coverage.md +46 -0
  33. package/.qa-ai/adapters/opencode/commands/qa-doctor.md +13 -0
  34. package/.qa-ai/adapters/opencode/commands/qa-full-flow.md +59 -0
  35. package/.qa-ai/adapters/opencode/commands/qa-gate.md +36 -0
  36. package/.qa-ai/adapters/opencode/commands/qa-help.md +30 -0
  37. package/.qa-ai/adapters/opencode/commands/qa-init.md +70 -0
  38. package/.qa-ai/adapters/opencode/commands/qa-status.md +56 -0
  39. package/.qa-ai/adapters/opencode/commands/qa-update-tests.md +47 -0
  40. package/.qa-ai/adapters/opencode/commands/qa-validate-features.md +36 -0
  41. package/.qa-ai/agents/README.md +39 -0
  42. package/.qa-ai/agents/api-testing-agent.md +73 -0
  43. package/.qa-ai/agents/automation-feasibility-agent.md +128 -0
  44. package/.qa-ai/agents/gherkin-test-design-agent.md +110 -0
  45. package/.qa-ai/agents/jira-task-agent.md +92 -0
  46. package/.qa-ai/agents/pr-agent.md +101 -0
  47. package/.qa-ai/agents/qa-context-intake-agent.md +75 -0
  48. package/.qa-ai/agents/qa-workflow-orchestrator.md +113 -0
  49. package/.qa-ai/agents/release-gate-agent.md +50 -0
  50. package/.qa-ai/agents/requirements-intake-agent.md +79 -0
  51. package/.qa-ai/agents/requirements-normalization-agent.md +80 -0
  52. package/.qa-ai/agents/specialists/available/appium.md +59 -0
  53. package/.qa-ai/agents/specialists/available/cypress.md +68 -0
  54. package/.qa-ai/agents/specialists/available/generic-test-design.md +117 -0
  55. package/.qa-ai/agents/specialists/available/jira.md +108 -0
  56. package/.qa-ai/agents/specialists/available/karate.md +97 -0
  57. package/.qa-ai/agents/specialists/available/playwright-api.md +87 -0
  58. package/.qa-ai/agents/specialists/available/playwright-ui.md +87 -0
  59. package/.qa-ai/agents/specialists/available/postman.md +108 -0
  60. package/.qa-ai/agents/specialists/available/rest-assured.md +103 -0
  61. package/.qa-ai/agents/specialists/available/selenium.md +91 -0
  62. package/.qa-ai/agents/specialists/available/testrail.md +85 -0
  63. package/.qa-ai/agents/specialists/available/webdriverio.md +81 -0
  64. package/.qa-ai/agents/test-design-system-agent.md +33 -0
  65. package/.qa-ai/agents/testrail-coverage-agent.md +84 -0
  66. package/.qa-ai/agents/testrail-sync-agent.md +96 -0
  67. package/.qa-ai/agents/webdriverio-implementation-agent.md +84 -0
  68. package/.qa-ai/presets/manual-only.yaml +65 -0
  69. package/.qa-ai/presets/selenium-jest-browserstack.yaml +72 -0
  70. package/.qa-ai/presets/webdriverio-playwright-api.yaml +85 -0
  71. package/.qa-ai/rules/api-testing.rules.md +7 -0
  72. package/.qa-ai/rules/approval.rules.md +8 -0
  73. package/.qa-ai/rules/automation.rules.md +7 -0
  74. package/.qa-ai/rules/gherkin.rules.md +12 -0
  75. package/.qa-ai/rules/testrail.rules.md +10 -0
  76. package/.qa-ai/rules/webdriverio.rules.md +9 -0
  77. package/.qa-ai/scripts/bootstrap-agent-adapters.mjs +127 -0
  78. package/.qa-ai/scripts/clean.mjs +243 -0
  79. package/.qa-ai/scripts/config.mjs +202 -0
  80. package/.qa-ai/scripts/doctor.mjs +383 -0
  81. package/.qa-ai/scripts/init.mjs +447 -0
  82. package/.qa-ai/scripts/lib/markdown-table.mjs +76 -0
  83. package/.qa-ai/scripts/lib/project-config.mjs +184 -0
  84. package/.qa-ai/scripts/lib/qa-next-steps.mjs +578 -0
  85. package/.qa-ai/scripts/lib/release-gate.mjs +66 -0
  86. package/.qa-ai/scripts/lib/test-design.mjs +92 -0
  87. package/.qa-ai/scripts/lib/test-management-mapping.mjs +73 -0
  88. package/.qa-ai/scripts/lib/utils.mjs +331 -0
  89. package/.qa-ai/scripts/qa-help.mjs +44 -0
  90. package/.qa-ai/scripts/smoke-npm-pack.mjs +187 -0
  91. package/.qa-ai/scripts/smoke-test.mjs +465 -0
  92. package/.qa-ai/scripts/sync-agent-adapters.mjs +121 -0
  93. package/.qa-ai/scripts/test-validators.mjs +334 -0
  94. package/.qa-ai/scripts/validate-active-specialists.mjs +106 -0
  95. package/.qa-ai/scripts/validate-features.mjs +277 -0
  96. package/.qa-ai/scripts/validate-release-gate.mjs +105 -0
  97. package/.qa-ai/scripts/validate-sync-plan.mjs +186 -0
  98. package/.qa-ai/scripts/validate-target.mjs +104 -0
  99. package/.qa-ai/scripts/validate-test-design.mjs +117 -0
  100. package/.qa-ai/scripts/validate-traceability.mjs +183 -0
  101. package/.qa-ai/templates/automation-feasibility-report.template.md +21 -0
  102. package/.qa-ai/templates/automation-implementation-plan.template.md +23 -0
  103. package/.qa-ai/templates/feature.template +13 -0
  104. package/.qa-ai/templates/jira-automation-task.template.md +25 -0
  105. package/.qa-ai/templates/pr-template.md +60 -0
  106. package/.qa-ai/templates/release-gate.template.yaml +16 -0
  107. package/.qa-ai/templates/requirement-analysis.template.md +17 -0
  108. package/.qa-ai/templates/test-design-proposal.template.md +26 -0
  109. package/.qa-ai/templates/test-design-system.template.md +15 -0
  110. package/.qa-ai/templates/test-management-mapping.template.json +18 -0
  111. package/.qa-ai/templates/testrail-coverage-analysis.template.md +17 -0
  112. package/.qa-ai/templates/testrail-sync-plan.template.md +22 -0
  113. package/.qa-ai/templates/traceability-matrix.template.md +4 -0
  114. package/.qa-ai/workflows/automation-analysis.md +23 -0
  115. package/.qa-ai/workflows/cleanup.md +52 -0
  116. package/.qa-ai/workflows/context-intake.md +66 -0
  117. package/.qa-ai/workflows/full-flow.md +55 -0
  118. package/.qa-ai/workflows/implementation.md +24 -0
  119. package/.qa-ai/workflows/intake.md +3 -0
  120. package/.qa-ai/workflows/pr.md +3 -0
  121. package/.qa-ai/workflows/release-gate.md +22 -0
  122. package/.qa-ai/workflows/test-design-system.md +33 -0
  123. package/.qa-ai/workflows/test-design.md +23 -0
  124. package/.qa-ai/workflows/testrail-sync.md +23 -0
  125. package/CHANGELOG.md +108 -0
  126. package/CODE_OF_CONDUCT.md +11 -0
  127. package/CONTRIBUTING.md +39 -0
  128. package/LICENSE +21 -0
  129. package/README.es.md +602 -0
  130. package/README.md +633 -0
  131. package/ROADMAP.md +107 -0
  132. package/SECURITY.md +18 -0
  133. package/bin/qa-flowkit.mjs +214 -0
  134. package/docs/qa-ai/agent-compatibility.md +100 -0
  135. package/docs/qa-ai/architecture.md +130 -0
  136. package/docs/qa-ai/backlog.md +393 -0
  137. package/docs/qa-ai/cleanup.md +104 -0
  138. package/docs/qa-ai/customizing-agents.md +148 -0
  139. package/docs/qa-ai/getting-started.md +385 -0
  140. package/docs/qa-ai/implementation-guide-for-codex.md +210 -0
  141. package/docs/qa-ai/npm-migration-plan.md +50 -0
  142. package/docs/qa-ai/open-source-release-checklist.md +17 -0
  143. package/docs/qa-ai/qa-help.md +76 -0
  144. package/docs/qa-ai/release-gate.md +60 -0
  145. package/docs/qa-ai/terminal-transcripts.md +316 -0
  146. package/docs/qa-ai/test-design-dual-mode.md +75 -0
  147. package/docs/qa-ai/troubleshooting.md +740 -0
  148. package/docs/qa-ai/workflow.md +147 -0
  149. package/package.json +72 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import {
4
+ activeSpecialistsContent,
5
+ configuredDirs
6
+ } from './lib/project-config.mjs';
7
+ import {
8
+ copyFileSafe,
9
+ ensureDir,
10
+ loadQaAiConfig,
11
+ logHeader,
12
+ manifestEntry,
13
+ manifestPath,
14
+ parseArgs,
15
+ parseSimpleYaml,
16
+ pathExists,
17
+ readText,
18
+ recordManifestEntries,
19
+ relativeTo,
20
+ resolveRepoPath,
21
+ writeFileSafe
22
+ } from './lib/utils.mjs';
23
+
24
+ const cwd = process.cwd();
25
+ const args = parseArgs(process.argv);
26
+ const force = Boolean(args.force);
27
+ const applyStructure = !args['no-structure'];
28
+ const qaAiDir = path.join(cwd, '.qa-ai');
29
+
30
+ function printHelp() {
31
+ console.log(`Usage: node .qa-ai/scripts/config.mjs [options]
32
+
33
+ Options:
34
+ --export <path> Export qa-ai.config.yaml to a repo-local profile path
35
+ --import <path> Import a repo-local profile path as qa-ai.config.yaml
36
+ --no-structure On import, skip creating configured folders and active specialists
37
+ --force Overwrite the export target or existing imported config files
38
+ --help Show this help
39
+
40
+ Examples:
41
+ node .qa-ai/scripts/config.mjs --export .qa-ai/config-profiles/team.yaml
42
+ node .qa-ai/scripts/config.mjs --import .qa-ai/config-profiles/team.yaml --force
43
+ `);
44
+ }
45
+
46
+ function selectedMode() {
47
+ const hasExport = Boolean(args.export);
48
+ const hasImport = Boolean(args.import);
49
+ if (hasExport && hasImport) {
50
+ console.error('Use either --export or --import, not both.');
51
+ process.exit(1);
52
+ }
53
+ if (hasExport) return 'export';
54
+ if (hasImport) return 'import';
55
+ console.error('Missing mode. Use --export <path> or --import <path>.');
56
+ printHelp();
57
+ process.exit(1);
58
+ }
59
+
60
+ function validateProfileContent(content, sourcePath) {
61
+ const config = parseSimpleYaml(content);
62
+ if (!config || typeof config !== 'object' || Object.keys(config).length === 0) {
63
+ console.error(`Invalid QA AI config profile: ${relativeTo(cwd, sourcePath)} is empty or not supported YAML.`);
64
+ process.exit(1);
65
+ }
66
+ return config;
67
+ }
68
+
69
+ async function exportConfig(targetArg) {
70
+ logHeader('QA AI config export');
71
+
72
+ const configInfo = await loadQaAiConfig(cwd);
73
+ if (!configInfo.exists) {
74
+ console.error('Missing qa-ai.config.yaml. Run init first or import an existing profile.');
75
+ process.exit(1);
76
+ }
77
+
78
+ const target = resolveRepoPath(cwd, targetArg, { label: 'export path' });
79
+ const result = await copyFileSafe(configInfo.path, target, { force });
80
+ console.log(`${result.copied ? 'exported' : 'skipped '} ${relativeTo(cwd, result.path)}`);
81
+
82
+ if (result.copied) {
83
+ const manifest = await recordManifestEntries(cwd, [
84
+ await manifestEntry(cwd, result.path, {
85
+ type: 'file',
86
+ category: 'generated',
87
+ source: 'config-export'
88
+ })
89
+ ]);
90
+ if (manifest) console.log(`updated ${relativeTo(cwd, manifestPath(cwd))}`);
91
+ }
92
+
93
+ if (!result.copied) {
94
+ console.log('Use --force to overwrite the existing export target.');
95
+ }
96
+ }
97
+
98
+ async function applyImportedStructure(config) {
99
+ const manifestEntries = [];
100
+ const dirResults = [];
101
+ for (const dir of [...configuredDirs(config)].filter(Boolean).sort()) {
102
+ const result = await ensureDir(resolveRepoPath(cwd, dir, { label: `configured directory "${dir}"` }));
103
+ dirResults.push(result);
104
+ if (result.created) {
105
+ manifestEntries.push(await manifestEntry(cwd, result.path, {
106
+ type: 'dir',
107
+ category: 'generated',
108
+ source: 'config-import'
109
+ }));
110
+ }
111
+ }
112
+
113
+ const specialistsResult = await writeFileSafe(
114
+ resolveRepoPath(cwd, '.qa-ai/agents/specialists/active.md', { label: 'active specialists index' }),
115
+ activeSpecialistsContent(config, 'node .qa-ai/scripts/config.mjs --import'),
116
+ { force }
117
+ );
118
+ if (specialistsResult.written) {
119
+ manifestEntries.push(await manifestEntry(cwd, specialistsResult.path, {
120
+ type: 'file',
121
+ category: 'generated',
122
+ source: 'config-import'
123
+ }));
124
+ }
125
+
126
+ return { dirResults, specialistsResult, manifestEntries };
127
+ }
128
+
129
+ async function importConfig(sourceArg) {
130
+ logHeader('QA AI config import');
131
+
132
+ if (!await pathExists(qaAiDir)) {
133
+ console.error('Missing .qa-ai folder. Copy it into the repository root first.');
134
+ process.exit(1);
135
+ }
136
+
137
+ const source = resolveRepoPath(cwd, sourceArg, { label: 'import path' });
138
+ if (!await pathExists(source)) {
139
+ console.error(`Import profile not found: ${relativeTo(cwd, source)}`);
140
+ process.exit(1);
141
+ }
142
+
143
+ const content = await readText(source);
144
+ const config = validateProfileContent(content, source);
145
+ const configPath = path.join(cwd, 'qa-ai.config.yaml');
146
+ const configWrite = await writeFileSafe(configPath, content.endsWith('\n') ? content : `${content}\n`, { force });
147
+ console.log(`${configWrite.written ? 'imported' : 'skipped '} ${relativeTo(cwd, configWrite.path)}`);
148
+
149
+ const manifestEntries = [];
150
+ if (configWrite.written) {
151
+ manifestEntries.push(await manifestEntry(cwd, configWrite.path, {
152
+ type: 'file',
153
+ category: 'generated',
154
+ source: 'config-import'
155
+ }));
156
+ }
157
+
158
+ let structureResult = null;
159
+ if (applyStructure) {
160
+ structureResult = await applyImportedStructure(config);
161
+ manifestEntries.push(...structureResult.manifestEntries);
162
+ } else {
163
+ console.log('Skipping configured folders and active specialists because --no-structure was passed.');
164
+ }
165
+
166
+ const manifest = await recordManifestEntries(cwd, manifestEntries);
167
+
168
+ console.log('\nImport summary:');
169
+ if (structureResult) {
170
+ for (const result of structureResult.dirResults) {
171
+ console.log(`${result.created ? 'created' : 'exists '} ${relativeTo(cwd, result.path)}`);
172
+ }
173
+ console.log(`${structureResult.specialistsResult.written ? 'created' : 'skipped'} ${relativeTo(cwd, structureResult.specialistsResult.path)}`);
174
+ if (!structureResult.specialistsResult.written) {
175
+ console.log('Use --force to refresh the active specialists index.');
176
+ }
177
+ }
178
+ if (!configWrite.written) {
179
+ console.log('Use --force to overwrite the existing qa-ai.config.yaml.');
180
+ }
181
+ if (manifest) console.log(`updated ${relativeTo(cwd, manifestPath(cwd))}`);
182
+ console.log('\nNext: node .qa-ai/scripts/doctor.mjs');
183
+ }
184
+
185
+ async function main() {
186
+ if (args.help) {
187
+ printHelp();
188
+ return;
189
+ }
190
+
191
+ const mode = selectedMode();
192
+ if (mode === 'export') {
193
+ await exportConfig(args.export);
194
+ return;
195
+ }
196
+ await importConfig(args.import);
197
+ }
198
+
199
+ main().catch((error) => {
200
+ console.error(error);
201
+ process.exit(1);
202
+ });
@@ -0,0 +1,383 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { normalizeQaTrack } from './lib/qa-next-steps.mjs';
4
+ import { getConfigValue, loadQaAiConfig, parseArgs, pathExists, resolveRepoPath, logHeader } from './lib/utils.mjs';
5
+
6
+ const cwd = process.cwd();
7
+ const args = parseArgs(process.argv);
8
+ const strict = Boolean(args.strict);
9
+
10
+ const requiredScripts = [
11
+ '.qa-ai/scripts/init.mjs',
12
+ '.qa-ai/scripts/config.mjs',
13
+ '.qa-ai/scripts/doctor.mjs',
14
+ '.qa-ai/scripts/bootstrap-agent-adapters.mjs',
15
+ '.qa-ai/scripts/clean.mjs',
16
+ '.qa-ai/scripts/validate-features.mjs',
17
+ '.qa-ai/scripts/validate-traceability.mjs',
18
+ '.qa-ai/scripts/validate-sync-plan.mjs',
19
+ '.qa-ai/scripts/validate-active-specialists.mjs',
20
+ '.qa-ai/scripts/validate-target.mjs',
21
+ '.qa-ai/scripts/qa-help.mjs',
22
+ '.qa-ai/scripts/validate-release-gate.mjs',
23
+ '.qa-ai/scripts/validate-test-design.mjs',
24
+ '.qa-ai/scripts/test-validators.mjs',
25
+ '.qa-ai/scripts/smoke-test.mjs',
26
+ '.qa-ai/scripts/smoke-npm-pack.mjs',
27
+ '.qa-ai/scripts/sync-agent-adapters.mjs',
28
+ '.qa-ai/scripts/lib/qa-next-steps.mjs',
29
+ '.qa-ai/scripts/lib/release-gate.mjs',
30
+ '.qa-ai/scripts/lib/test-design.mjs',
31
+ '.qa-ai/scripts/lib/markdown-table.mjs',
32
+ '.qa-ai/scripts/lib/project-config.mjs',
33
+ '.qa-ai/scripts/lib/test-management-mapping.mjs',
34
+ '.qa-ai/scripts/lib/utils.mjs'
35
+ ];
36
+
37
+ const requiredRules = [
38
+ '.qa-ai/rules/approval.rules.md',
39
+ '.qa-ai/rules/api-testing.rules.md',
40
+ '.qa-ai/rules/automation.rules.md',
41
+ '.qa-ai/rules/gherkin.rules.md',
42
+ '.qa-ai/rules/testrail.rules.md',
43
+ '.qa-ai/rules/webdriverio.rules.md'
44
+ ];
45
+
46
+ const requiredTemplates = [
47
+ '.qa-ai/templates/automation-feasibility-report.template.md',
48
+ '.qa-ai/templates/automation-implementation-plan.template.md',
49
+ '.qa-ai/templates/feature.template',
50
+ '.qa-ai/templates/jira-automation-task.template.md',
51
+ '.qa-ai/templates/pr-template.md',
52
+ '.qa-ai/templates/requirement-analysis.template.md',
53
+ '.qa-ai/templates/test-design-system.template.md',
54
+ '.qa-ai/templates/test-design-proposal.template.md',
55
+ '.qa-ai/templates/testrail-coverage-analysis.template.md',
56
+ '.qa-ai/templates/test-management-mapping.template.json',
57
+ '.qa-ai/templates/testrail-sync-plan.template.md',
58
+ '.qa-ai/templates/traceability-matrix.template.md',
59
+ '.qa-ai/templates/release-gate.template.yaml'
60
+ ];
61
+
62
+ const requiredAgents = [
63
+ '.qa-ai/agents/README.md',
64
+ '.qa-ai/agents/api-testing-agent.md',
65
+ '.qa-ai/agents/automation-feasibility-agent.md',
66
+ '.qa-ai/agents/gherkin-test-design-agent.md',
67
+ '.qa-ai/agents/test-design-system-agent.md',
68
+ '.qa-ai/agents/qa-context-intake-agent.md',
69
+ '.qa-ai/agents/jira-task-agent.md',
70
+ '.qa-ai/agents/pr-agent.md',
71
+ '.qa-ai/agents/qa-workflow-orchestrator.md',
72
+ '.qa-ai/agents/requirements-intake-agent.md',
73
+ '.qa-ai/agents/requirements-normalization-agent.md',
74
+ '.qa-ai/agents/testrail-coverage-agent.md',
75
+ '.qa-ai/agents/testrail-sync-agent.md',
76
+ '.qa-ai/agents/webdriverio-implementation-agent.md',
77
+ '.qa-ai/agents/release-gate-agent.md'
78
+ ];
79
+
80
+ const requiredSpecialists = [
81
+ '.qa-ai/agents/specialists/available/appium.md',
82
+ '.qa-ai/agents/specialists/available/cypress.md',
83
+ '.qa-ai/agents/specialists/available/generic-test-design.md',
84
+ '.qa-ai/agents/specialists/available/jira.md',
85
+ '.qa-ai/agents/specialists/available/karate.md',
86
+ '.qa-ai/agents/specialists/available/playwright-api.md',
87
+ '.qa-ai/agents/specialists/available/playwright-ui.md',
88
+ '.qa-ai/agents/specialists/available/postman.md',
89
+ '.qa-ai/agents/specialists/available/rest-assured.md',
90
+ '.qa-ai/agents/specialists/available/selenium.md',
91
+ '.qa-ai/agents/specialists/available/testrail.md',
92
+ '.qa-ai/agents/specialists/available/webdriverio.md'
93
+ ];
94
+
95
+ const requiredPresets = [
96
+ '.qa-ai/presets/manual-only.yaml',
97
+ '.qa-ai/presets/selenium-jest-browserstack.yaml',
98
+ '.qa-ai/presets/webdriverio-playwright-api.yaml'
99
+ ];
100
+
101
+ const requiredWorkflows = [
102
+ '.qa-ai/workflows/automation-analysis.md',
103
+ '.qa-ai/workflows/cleanup.md',
104
+ '.qa-ai/workflows/context-intake.md',
105
+ '.qa-ai/workflows/full-flow.md',
106
+ '.qa-ai/workflows/implementation.md',
107
+ '.qa-ai/workflows/intake.md',
108
+ '.qa-ai/workflows/pr.md',
109
+ '.qa-ai/workflows/test-design.md',
110
+ '.qa-ai/workflows/test-design-system.md',
111
+ '.qa-ai/workflows/testrail-sync.md',
112
+ '.qa-ai/workflows/release-gate.md'
113
+ ];
114
+
115
+ const requiredAdapterTemplates = [
116
+ '.qa-ai/adapters/aider/.aider.conf.yml',
117
+ '.qa-ai/adapters/aider/.aider/README.md',
118
+ '.qa-ai/adapters/claude/agents/qa-workflow-orchestrator.md',
119
+ '.qa-ai/adapters/claude/commands/qa-add-tests.md',
120
+ '.qa-ai/adapters/claude/commands/qa-automation-plan.md',
121
+ '.qa-ai/adapters/claude/commands/qa-clean.md',
122
+ '.qa-ai/adapters/claude/commands/qa-config.md',
123
+ '.qa-ai/adapters/claude/commands/qa-coverage.md',
124
+ '.qa-ai/adapters/claude/commands/qa-doctor.md',
125
+ '.qa-ai/adapters/claude/commands/qa-full-flow.md',
126
+ '.qa-ai/adapters/claude/commands/qa-init.md',
127
+ '.qa-ai/adapters/claude/commands/qa-help.md',
128
+ '.qa-ai/adapters/claude/commands/qa-gate.md',
129
+ '.qa-ai/adapters/claude/commands/qa-status.md',
130
+ '.qa-ai/adapters/claude/commands/qa-update-tests.md',
131
+ '.qa-ai/adapters/claude/commands/qa-validate-features.md',
132
+ '.qa-ai/adapters/cline/.clinerules',
133
+ '.qa-ai/adapters/cline/.cline/README.md',
134
+ '.qa-ai/adapters/codex/README.md',
135
+ '.qa-ai/adapters/codex/prompts/implement-project.md',
136
+ '.qa-ai/adapters/continue/README.md',
137
+ '.qa-ai/adapters/continue/checks/qa-feature-conventions.md',
138
+ '.qa-ai/adapters/generic/AGENTS.md',
139
+ '.qa-ai/adapters/gemini/GEMINI.md',
140
+ '.qa-ai/adapters/goose/recipes/qa-flowkit.yaml',
141
+ '.qa-ai/adapters/opencode/README.md',
142
+ '.qa-ai/adapters/opencode/agents/qa-workflow.md',
143
+ '.qa-ai/adapters/opencode/commands/qa-add-tests.md',
144
+ '.qa-ai/adapters/opencode/commands/qa-automation-plan.md',
145
+ '.qa-ai/adapters/opencode/commands/qa-clean.md',
146
+ '.qa-ai/adapters/opencode/commands/qa-config.md',
147
+ '.qa-ai/adapters/opencode/commands/qa-coverage.md',
148
+ '.qa-ai/adapters/opencode/commands/qa-doctor.md',
149
+ '.qa-ai/adapters/opencode/commands/qa-full-flow.md',
150
+ '.qa-ai/adapters/opencode/commands/qa-init.md',
151
+ '.qa-ai/adapters/opencode/commands/qa-help.md',
152
+ '.qa-ai/adapters/opencode/commands/qa-gate.md',
153
+ '.qa-ai/adapters/opencode/commands/qa-status.md',
154
+ '.qa-ai/adapters/opencode/commands/qa-update-tests.md',
155
+ '.qa-ai/adapters/opencode/commands/qa-validate-features.md'
156
+ ];
157
+
158
+ const generatedAdapters = [
159
+ ['Claude adapter', '.claude'],
160
+ ['Codex adapter', '.codex'],
161
+ ['OpenCode adapter', '.opencode'],
162
+ ['Cline rules', '.clinerules'],
163
+ ['Cline docs', '.cline'],
164
+ ['Continue adapter', '.continue'],
165
+ ['Aider config', '.aider.conf.yml'],
166
+ ['Aider docs', '.aider'],
167
+ ['Goose recipe', '.goose/recipes/qa-flowkit.yaml'],
168
+ ['Gemini context', 'GEMINI.md']
169
+ ];
170
+
171
+ function pathCheck(level, label, relPath) {
172
+ return { level, label, paths: [relPath] };
173
+ }
174
+
175
+ function anyPathCheck(level, label, relPaths) {
176
+ return { level, label, paths: relPaths, any: true };
177
+ }
178
+
179
+ function isConfiguredFramework(value) {
180
+ const normalized = String(value || '').trim().toLowerCase();
181
+ return Boolean(normalized) && !['none', 'undecided', 'manual', 'n/a', 'na'].includes(normalized);
182
+ }
183
+
184
+ function isEnabled(value) {
185
+ return value === true || String(value || '').trim().toLowerCase() === 'true';
186
+ }
187
+
188
+ function checkLevel(defaultLevel) {
189
+ return strict && defaultLevel === 'optional' ? 'required' : defaultLevel;
190
+ }
191
+
192
+ function isConfiguredTool(value) {
193
+ const normalized = String(value || '').trim().toLowerCase();
194
+ return Boolean(normalized) && !['none', 'undecided', 'n/a', 'na'].includes(normalized);
195
+ }
196
+
197
+ function addWorkflowArtifactChecks(checks, config) {
198
+ const testManagementTool = getConfigValue(config, 'tools.testManagement', '');
199
+ const issueTracker = getConfigValue(config, 'tools.issueTracker', '');
200
+ const uiFramework = getConfigValue(config, 'automation.ui.framework', 'none');
201
+ const apiFramework = getConfigValue(config, 'automation.api.framework', 'none');
202
+ const hasAutomation = isConfiguredFramework(uiFramework) || isConfiguredFramework(apiFramework);
203
+ const track = normalizeQaTrack(getConfigValue(config, 'project.qaTrack', 'standard'));
204
+ const proposalPath = getConfigValue(config, 'testDesign.proposalPath', 'qa-ai-output/test-design-proposal.md');
205
+ const isQuickTrack = track === 'quick';
206
+
207
+ checks.push(pathCheck(checkLevel('optional'), 'requirement analysis artifact', 'qa-ai-output/requirement-analysis.md'));
208
+ if (!isQuickTrack) {
209
+ const systemPath = getConfigValue(config, 'testDesign.systemPath', 'qa-ai-output/test-design-system.md');
210
+ checks.push(pathCheck(checkLevel('optional'), 'system test design artifact', systemPath));
211
+ }
212
+ checks.push(pathCheck(isQuickTrack ? 'optional' : checkLevel('optional'), 'test design proposal artifact', proposalPath));
213
+ checks.push(pathCheck(checkLevel('optional'), 'PR summary artifact', 'qa-ai-output/pr-summary.md'));
214
+
215
+ if (!isQuickTrack && isConfiguredTool(testManagementTool)) {
216
+ checks.push(pathCheck(checkLevel('optional'), 'test management coverage artifact', 'qa-ai-output/testrail-coverage-analysis.md'));
217
+ checks.push(pathCheck(checkLevel('optional'), 'test management sync plan artifact', 'qa-ai-output/testrail-sync-plan.md'));
218
+ }
219
+
220
+ if (hasAutomation) {
221
+ checks.push(pathCheck(checkLevel('optional'), 'automation feasibility artifact', 'qa-ai-output/automation-feasibility-report.md'));
222
+ checks.push(pathCheck(checkLevel('optional'), 'automation implementation plan artifact', 'qa-ai-output/automation-implementation-plan.md'));
223
+ }
224
+
225
+ if (isConfiguredTool(issueTracker)) {
226
+ checks.push(pathCheck(checkLevel('optional'), 'issue tracker task draft artifact', 'qa-ai-output/jira-automation-task.md'));
227
+ }
228
+ }
229
+
230
+ function addConfiguredChecks(checks, config) {
231
+ const featurePath = getConfigValue(config, 'gherkin.featurePath', 'features');
232
+ const matrixPath = getConfigValue(config, 'traceability.matrixPath', 'qa-ai-output/traceability-matrix.md');
233
+ const mappingFile = getConfigValue(config, 'testrail.mappingFile', '');
234
+ const uiFramework = String(getConfigValue(config, 'automation.ui.framework', 'none')).toLowerCase();
235
+ const uiSpecsPath = getConfigValue(config, 'automation.ui.specsPath', '');
236
+ const uiPageObjectsPath = getConfigValue(config, 'automation.ui.pageObjectsPath', '');
237
+ const apiFramework = String(getConfigValue(config, 'automation.api.framework', 'none')).toLowerCase();
238
+ const apiSpecsPath = getConfigValue(config, 'automation.api.specsPath', '');
239
+ const knowledgeEnabled = isEnabled(getConfigValue(config, 'knowledge.enabled', false));
240
+ const knowledgeSourcePath = getConfigValue(config, 'knowledge.sourcePath', '');
241
+ const knowledgeSummaryPath = getConfigValue(config, 'knowledge.summaryPath', 'qa-ai-output/qa-knowledge-summary.md');
242
+ const knowledgeDecisionsPath = getConfigValue(config, 'knowledge.decisionsPath', 'qa-ai-output/qa-init-decisions.md');
243
+ const track = normalizeQaTrack(getConfigValue(config, 'project.qaTrack', 'standard'));
244
+
245
+ checks.push(pathCheck('required', 'configured feature root', featurePath));
246
+ checks.push(pathCheck('required', 'configured QA output path', path.dirname(matrixPath)));
247
+ checks.push(pathCheck(checkLevel('optional'), 'configured traceability matrix', matrixPath));
248
+ if (mappingFile && track !== 'quick') checks.push(pathCheck(checkLevel('optional'), 'configured test management mapping file', mappingFile));
249
+ addWorkflowArtifactChecks(checks, config);
250
+
251
+ if (knowledgeEnabled) {
252
+ checks.push(pathCheck('required', 'configured QA context folder', knowledgeSourcePath));
253
+ if (knowledgeSummaryPath) checks.push(pathCheck(checkLevel('optional'), 'QA context summary artifact', knowledgeSummaryPath));
254
+ if (knowledgeDecisionsPath) checks.push(pathCheck(checkLevel('optional'), 'QA init decisions artifact', knowledgeDecisionsPath));
255
+ }
256
+
257
+ if (isConfiguredFramework(uiFramework)) {
258
+ if (uiSpecsPath) checks.push(pathCheck('required', 'configured UI specs path', uiSpecsPath));
259
+ if (uiPageObjectsPath) checks.push(pathCheck('required', 'configured UI page objects path', uiPageObjectsPath));
260
+ }
261
+
262
+ if (isConfiguredFramework(apiFramework)) {
263
+ if (apiSpecsPath) checks.push(pathCheck('required', 'configured API specs path', apiSpecsPath));
264
+ }
265
+
266
+ if (uiFramework === 'webdriverio') {
267
+ checks.push(anyPathCheck(checkLevel('optional'), 'WebdriverIO config', [
268
+ 'wdio.conf.ts',
269
+ 'wdio.conf.js',
270
+ 'wdio.conf.mjs',
271
+ 'wdio.conf.cjs'
272
+ ]));
273
+ }
274
+
275
+ if (uiFramework === 'selenium-jest-browserstack' || uiFramework === 'selenium') {
276
+ checks.push(anyPathCheck(checkLevel('optional'), 'Jest config', [
277
+ 'jest.config.ts',
278
+ 'jest.config.js',
279
+ 'jest.config.mjs',
280
+ 'jest.config.cjs'
281
+ ]));
282
+ checks.push(anyPathCheck(checkLevel('optional'), 'BrowserStack config', [
283
+ 'browserstack.yml',
284
+ 'browserstack.yaml'
285
+ ]));
286
+ }
287
+
288
+ if (apiFramework === 'playwright-api' || apiFramework === 'playwright') {
289
+ checks.push(anyPathCheck(checkLevel('optional'), 'Playwright API config', [
290
+ 'playwright.api.config.ts',
291
+ 'playwright.api.config.js',
292
+ 'playwright.config.ts',
293
+ 'playwright.config.js',
294
+ 'playwright.config.mjs'
295
+ ]));
296
+ }
297
+ }
298
+
299
+ async function runCheck(check) {
300
+ const resolvedPaths = [];
301
+ for (const relPath of check.paths) {
302
+ try {
303
+ resolvedPaths.push(resolveRepoPath(cwd, relPath, {
304
+ label: check.label,
305
+ allowRoot: relPath === '.'
306
+ }));
307
+ } catch (error) {
308
+ return { ...check, ok: false, reason: error.message };
309
+ }
310
+ }
311
+ const results = await Promise.all(resolvedPaths.map((filePath) => pathExists(filePath)));
312
+ const ok = check.any ? results.some(Boolean) : results.every(Boolean);
313
+ return { ...check, ok };
314
+ }
315
+
316
+ function describePaths(paths, any = false) {
317
+ if (paths.length === 1) return paths[0];
318
+ return paths.join(any ? ' or ' : ', ');
319
+ }
320
+
321
+ async function main() {
322
+ logHeader(`QA FlowKit doctor${strict ? ' --strict' : ''}`);
323
+ const configInfo = await loadQaAiConfig(cwd);
324
+ const isFrameworkSourceRepo = await pathExists(path.join(cwd, 'docs/qa-ai/architecture.md'));
325
+ const configLevel = isFrameworkSourceRepo && !strict ? 'optional' : 'required';
326
+ const genericInstructionsLevel = isFrameworkSourceRepo ? 'required' : 'optional';
327
+ const checks = [
328
+ pathCheck(configLevel, 'config', 'qa-ai.config.yaml'),
329
+ pathCheck('required', 'framework folder', '.qa-ai'),
330
+ pathCheck('required', 'agents folder', '.qa-ai/agents'),
331
+ pathCheck('required', 'rules folder', '.qa-ai/rules'),
332
+ pathCheck('required', 'templates folder', '.qa-ai/templates'),
333
+ pathCheck('required', 'scripts folder', '.qa-ai/scripts'),
334
+ pathCheck('required', 'presets folder', '.qa-ai/presets'),
335
+ pathCheck('required', 'adapters folder', '.qa-ai/adapters'),
336
+ pathCheck(genericInstructionsLevel, 'generic agent instructions', 'AGENTS.md'),
337
+ ...requiredScripts.map((relPath) => pathCheck('required', `script ${path.basename(relPath)}`, relPath)),
338
+ ...requiredRules.map((relPath) => pathCheck('required', `rule ${path.basename(relPath)}`, relPath)),
339
+ ...requiredTemplates.map((relPath) => pathCheck('required', `template ${path.basename(relPath)}`, relPath)),
340
+ ...requiredAgents.map((relPath) => pathCheck('required', `agent ${path.basename(relPath)}`, relPath)),
341
+ ...requiredSpecialists.map((relPath) => pathCheck('required', `specialist ${path.basename(relPath)}`, relPath)),
342
+ ...requiredPresets.map((relPath) => pathCheck('required', `preset ${path.basename(relPath)}`, relPath)),
343
+ ...requiredWorkflows.map((relPath) => pathCheck('required', `workflow ${path.basename(relPath)}`, relPath)),
344
+ ...requiredAdapterTemplates.map((relPath) => pathCheck('required', `adapter template ${relPath.split('/').slice(2).join('/')}`, relPath)),
345
+ ...generatedAdapters.map(([label, relPath]) => pathCheck('optional', label, relPath))
346
+ ];
347
+
348
+ if (configInfo.exists) addConfiguredChecks(checks, configInfo.data);
349
+ if (configInfo.exists) checks.push(pathCheck('optional', 'init manifest', '.qa-ai/state/init-manifest.json'));
350
+ if (configInfo.exists) checks.push(pathCheck('required', 'active specialists index', '.qa-ai/agents/specialists/active.md'));
351
+
352
+ let failed = 0;
353
+ let warned = 0;
354
+ for (const check of checks) {
355
+ const result = await runCheck(check);
356
+ const target = describePaths(result.paths, result.any);
357
+ if (result.ok) {
358
+ console.log(`[PASS] ${check.label}: ${target}`);
359
+ } else if (check.level === 'required') {
360
+ failed += 1;
361
+ console.log(`[FAIL] ${check.label}: ${target}${result.reason ? ` (${result.reason})` : ''}`);
362
+ } else {
363
+ warned += 1;
364
+ console.log(`[WARN] ${check.label}: ${target}${result.reason ? ` (${result.reason})` : ''}`);
365
+ }
366
+ }
367
+
368
+ console.log('\nResult:');
369
+ if (failed > 0) {
370
+ console.log(`FAILED - ${failed} required checks failed, ${warned} warnings.`);
371
+ process.exit(1);
372
+ }
373
+ if (warned > 0) {
374
+ console.log(`VALID WITH WARNINGS - ${warned} optional checks missing.`);
375
+ return;
376
+ }
377
+ console.log('VALID - all checks passed.');
378
+ }
379
+
380
+ main().catch((error) => {
381
+ console.error(error);
382
+ process.exit(1);
383
+ });