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,65 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
project:
|
|
3
|
+
name: CHANGE_ME
|
|
4
|
+
repoMode: qa-design-only
|
|
5
|
+
qaTrack: quick
|
|
6
|
+
defaultLanguage: en
|
|
7
|
+
interfaceLanguage: en
|
|
8
|
+
tools:
|
|
9
|
+
testManagement: testrail
|
|
10
|
+
issueTracker: none
|
|
11
|
+
documentation: markdown
|
|
12
|
+
agents:
|
|
13
|
+
specialistMode: auto
|
|
14
|
+
sources:
|
|
15
|
+
main: markdown
|
|
16
|
+
knowledge:
|
|
17
|
+
enabled: false
|
|
18
|
+
sourcePath: ''
|
|
19
|
+
summaryPath: qa-ai-output/qa-knowledge-summary.md
|
|
20
|
+
decisionsPath: qa-ai-output/qa-init-decisions.md
|
|
21
|
+
requirements:
|
|
22
|
+
requireOfficialRfId: true
|
|
23
|
+
allowInferredAcceptanceCriteria: true
|
|
24
|
+
requireApprovalForInferredCriteria: true
|
|
25
|
+
gherkin:
|
|
26
|
+
language: en
|
|
27
|
+
oneScenarioPerFile: true
|
|
28
|
+
requireAcceptanceCriteria: true
|
|
29
|
+
manualTestsNeedFeatureFile: true
|
|
30
|
+
featurePath: features
|
|
31
|
+
tags:
|
|
32
|
+
required:
|
|
33
|
+
- priority
|
|
34
|
+
- type
|
|
35
|
+
- manual
|
|
36
|
+
testrail:
|
|
37
|
+
enabled: true
|
|
38
|
+
projectName: CHANGE_ME
|
|
39
|
+
allowCreateSections: true
|
|
40
|
+
allowCreateCases: true
|
|
41
|
+
allowUpdateCases: approval-only
|
|
42
|
+
allowDeleteCases: never
|
|
43
|
+
mappingFile: qa-ai-output/test-management-mapping.json
|
|
44
|
+
automation:
|
|
45
|
+
ui:
|
|
46
|
+
framework: none
|
|
47
|
+
api:
|
|
48
|
+
framework: none
|
|
49
|
+
strategy:
|
|
50
|
+
automateAllTechnicallyPossible: false
|
|
51
|
+
requireProposalBeforeImplementation: true
|
|
52
|
+
allowModifyExistingTests: approval-only
|
|
53
|
+
testDesign:
|
|
54
|
+
systemPath: qa-ai-output/test-design-system.md
|
|
55
|
+
proposalPath: qa-ai-output/test-design-proposal.md
|
|
56
|
+
traceability:
|
|
57
|
+
matrixPath: qa-ai-output/traceability-matrix.md
|
|
58
|
+
approval:
|
|
59
|
+
beforeExternalWrite: true
|
|
60
|
+
beforeExistingFileModification: true
|
|
61
|
+
beforeTestRailSync: true
|
|
62
|
+
beforeJiraTaskCreation: true
|
|
63
|
+
beforePullRequest: true
|
|
64
|
+
commands:
|
|
65
|
+
testQA: node .qa-ai/scripts/validate-features.mjs
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
project:
|
|
3
|
+
name: CHANGE_ME
|
|
4
|
+
repoMode: qa-and-automation
|
|
5
|
+
qaTrack: standard
|
|
6
|
+
defaultLanguage: en
|
|
7
|
+
interfaceLanguage: en
|
|
8
|
+
tools:
|
|
9
|
+
testManagement: testrail
|
|
10
|
+
issueTracker: jira
|
|
11
|
+
documentation: markdown
|
|
12
|
+
agents:
|
|
13
|
+
specialistMode: auto
|
|
14
|
+
sources:
|
|
15
|
+
main: jira
|
|
16
|
+
knowledge:
|
|
17
|
+
enabled: false
|
|
18
|
+
sourcePath: ''
|
|
19
|
+
summaryPath: qa-ai-output/qa-knowledge-summary.md
|
|
20
|
+
decisionsPath: qa-ai-output/qa-init-decisions.md
|
|
21
|
+
requirements:
|
|
22
|
+
requireOfficialRfId: true
|
|
23
|
+
allowInferredAcceptanceCriteria: true
|
|
24
|
+
requireApprovalForInferredCriteria: true
|
|
25
|
+
gherkin:
|
|
26
|
+
language: en
|
|
27
|
+
oneScenarioPerFile: true
|
|
28
|
+
requireAcceptanceCriteria: true
|
|
29
|
+
manualTestsNeedFeatureFile: true
|
|
30
|
+
featurePath: features
|
|
31
|
+
tags:
|
|
32
|
+
required:
|
|
33
|
+
- priority
|
|
34
|
+
- type
|
|
35
|
+
- manual
|
|
36
|
+
testrail:
|
|
37
|
+
enabled: true
|
|
38
|
+
projectName: CHANGE_ME
|
|
39
|
+
allowCreateSections: true
|
|
40
|
+
allowCreateCases: true
|
|
41
|
+
allowUpdateCases: approval-only
|
|
42
|
+
allowDeleteCases: never
|
|
43
|
+
mappingFile: qa-ai-output/test-management-mapping.json
|
|
44
|
+
automation:
|
|
45
|
+
ui:
|
|
46
|
+
framework: selenium-jest-browserstack
|
|
47
|
+
specsPath: tests/selenium/specs
|
|
48
|
+
pageObjectsPath: tests/selenium/pageobjects
|
|
49
|
+
api:
|
|
50
|
+
framework: undecided
|
|
51
|
+
specsPath: tests/api/specs
|
|
52
|
+
strategy:
|
|
53
|
+
automateAllTechnicallyPossible: true
|
|
54
|
+
requireProposalBeforeImplementation: true
|
|
55
|
+
allowModifyExistingTests: approval-only
|
|
56
|
+
testDesign:
|
|
57
|
+
systemPath: qa-ai-output/test-design-system.md
|
|
58
|
+
proposalPath: qa-ai-output/test-design-proposal.md
|
|
59
|
+
traceability:
|
|
60
|
+
matrixPath: qa-ai-output/traceability-matrix.md
|
|
61
|
+
release:
|
|
62
|
+
gatePath: qa-ai-output/release-gate.yaml
|
|
63
|
+
approval:
|
|
64
|
+
beforeExternalWrite: true
|
|
65
|
+
beforeExistingFileModification: true
|
|
66
|
+
beforeTestRailSync: true
|
|
67
|
+
beforeJiraTaskCreation: true
|
|
68
|
+
beforePullRequest: true
|
|
69
|
+
commands:
|
|
70
|
+
lint: npm run lint
|
|
71
|
+
testE2E: npm run test:e2e
|
|
72
|
+
testQA: npm run test:qa
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
project:
|
|
3
|
+
name: CHANGE_ME
|
|
4
|
+
repoMode: qa-and-automation
|
|
5
|
+
qaTrack: standard
|
|
6
|
+
defaultLanguage: en
|
|
7
|
+
interfaceLanguage: en
|
|
8
|
+
tools:
|
|
9
|
+
testManagement: testrail
|
|
10
|
+
issueTracker: jira
|
|
11
|
+
documentation: confluence
|
|
12
|
+
agents:
|
|
13
|
+
specialistMode: auto
|
|
14
|
+
sources:
|
|
15
|
+
main: jira
|
|
16
|
+
jira:
|
|
17
|
+
enabled: true
|
|
18
|
+
projectKey: CHANGE_ME
|
|
19
|
+
storyIsSourceOfTruth: true
|
|
20
|
+
readAttachments: true
|
|
21
|
+
confluence:
|
|
22
|
+
enabled: true
|
|
23
|
+
markdown:
|
|
24
|
+
enabled: true
|
|
25
|
+
paths:
|
|
26
|
+
- docs/PRD
|
|
27
|
+
- docs/RF
|
|
28
|
+
knowledge:
|
|
29
|
+
enabled: false
|
|
30
|
+
sourcePath: ''
|
|
31
|
+
summaryPath: qa-ai-output/qa-knowledge-summary.md
|
|
32
|
+
decisionsPath: qa-ai-output/qa-init-decisions.md
|
|
33
|
+
requirements:
|
|
34
|
+
requireOfficialRfId: true
|
|
35
|
+
allowInferredAcceptanceCriteria: true
|
|
36
|
+
requireApprovalForInferredCriteria: true
|
|
37
|
+
gherkin:
|
|
38
|
+
language: en
|
|
39
|
+
oneScenarioPerFile: true
|
|
40
|
+
requireAcceptanceCriteria: true
|
|
41
|
+
manualTestsNeedFeatureFile: true
|
|
42
|
+
featurePath: features
|
|
43
|
+
tags:
|
|
44
|
+
required:
|
|
45
|
+
- priority
|
|
46
|
+
- type
|
|
47
|
+
- manual
|
|
48
|
+
testrail:
|
|
49
|
+
enabled: true
|
|
50
|
+
projectName: CHANGE_ME
|
|
51
|
+
allowCreateSections: true
|
|
52
|
+
allowCreateCases: true
|
|
53
|
+
allowUpdateCases: approval-only
|
|
54
|
+
allowDeleteCases: never
|
|
55
|
+
mappingFile: qa-ai-output/test-management-mapping.json
|
|
56
|
+
automation:
|
|
57
|
+
ui:
|
|
58
|
+
framework: webdriverio
|
|
59
|
+
specsPath: tests/wdio/specs
|
|
60
|
+
pageObjectsPath: tests/wdio/pageobjects
|
|
61
|
+
api:
|
|
62
|
+
framework: playwright-api
|
|
63
|
+
specsPath: tests/api/specs
|
|
64
|
+
strategy:
|
|
65
|
+
automateAllTechnicallyPossible: true
|
|
66
|
+
requireProposalBeforeImplementation: true
|
|
67
|
+
allowModifyExistingTests: approval-only
|
|
68
|
+
traceability:
|
|
69
|
+
matrixPath: qa-ai-output/traceability-matrix.md
|
|
70
|
+
testDesign:
|
|
71
|
+
systemPath: qa-ai-output/test-design-system.md
|
|
72
|
+
proposalPath: qa-ai-output/test-design-proposal.md
|
|
73
|
+
release:
|
|
74
|
+
gatePath: qa-ai-output/release-gate.yaml
|
|
75
|
+
approval:
|
|
76
|
+
beforeExternalWrite: true
|
|
77
|
+
beforeExistingFileModification: true
|
|
78
|
+
beforeTestRailSync: true
|
|
79
|
+
beforeJiraTaskCreation: true
|
|
80
|
+
beforePullRequest: true
|
|
81
|
+
commands:
|
|
82
|
+
lint: npm run lint
|
|
83
|
+
testApi: npm run test:api
|
|
84
|
+
testE2E: npm run test:e2e
|
|
85
|
+
testQA: npm run test:qa
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# API Testing Rules
|
|
2
|
+
|
|
3
|
+
- Use the API/integration framework configured in `qa-ai.config.yaml` (`automation.api.framework`).
|
|
4
|
+
- Check existing API testing patterns before creating new ones.
|
|
5
|
+
- Use clients, fixtures and schemas for maintainability.
|
|
6
|
+
- Do not add dependencies without approval.
|
|
7
|
+
- If API framework is `none`, `undecided` or missing, create a proposal and keep tests as manual/blocked until decided.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Approval Rules
|
|
2
|
+
|
|
3
|
+
- Always present a detailed plan before modifying files.
|
|
4
|
+
- Ask approval before external writes to configured tools.
|
|
5
|
+
- Ask approval before modifying existing tests.
|
|
6
|
+
- Never delete external test cases by default.
|
|
7
|
+
- Never overwrite existing files unless explicitly approved.
|
|
8
|
+
- Ask the user when ambiguity exists and offer options.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Automation Rules
|
|
2
|
+
|
|
3
|
+
- Automate every test that is technically possible and valuable under current repo constraints.
|
|
4
|
+
- Generate a technical proposal before writing code.
|
|
5
|
+
- Do not modify existing tests without approval.
|
|
6
|
+
- If tests cannot be executed, mark first execution as manual.
|
|
7
|
+
- Create configured issue tracker task drafts for automatable tests that cannot be implemented now.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Gherkin Rules
|
|
2
|
+
|
|
3
|
+
- Use the configured Gherkin language from `qa-ai.config.yaml` (`gherkin.language`): English (`en`) or Spanish (`es`).
|
|
4
|
+
- Use English Gherkin keywords and `Acceptance Criteria:` for `en`.
|
|
5
|
+
- Use Spanish Gherkin keywords and `Criterios de aceptación:` for `es`.
|
|
6
|
+
- Spanish `.feature` files must include `# language: es`.
|
|
7
|
+
- One `.feature` file per test case.
|
|
8
|
+
- One configured scenario keyword per `.feature` file: `Scenario:` / `Scenario Outline:` for English or `Escenario:` / `Esquema del escenario:` for Spanish.
|
|
9
|
+
- Include the configured acceptance criteria section after the Feature narrative.
|
|
10
|
+
- Required tags: `@priority:`, `@type:`, `@manual:`.
|
|
11
|
+
- Manual tests also require `.feature` files.
|
|
12
|
+
- Unit tests are excluded.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Test Management Rules
|
|
2
|
+
|
|
3
|
+
- Use the configured test management tool from `qa-ai.config.yaml` (`tools.testManagement`).
|
|
4
|
+
- Ask the user for the target project/suite when needed.
|
|
5
|
+
- Search existing cases before proposing new cases.
|
|
6
|
+
- Detect duplicates and potential overlaps.
|
|
7
|
+
- Create new sections only after informing the user.
|
|
8
|
+
- Create new cases only after approval.
|
|
9
|
+
- Update existing cases only after explicit approval.
|
|
10
|
+
- Delete or archive cases only after explicit approval and only if allowed by configuration.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# UI Automation Rules
|
|
2
|
+
|
|
3
|
+
- Follow existing repository patterns.
|
|
4
|
+
- Reuse page objects, helpers and fixtures when possible.
|
|
5
|
+
- Create new page objects only when needed.
|
|
6
|
+
- Use the UI/E2E framework configured in `qa-ai.config.yaml` (`automation.ui.framework`).
|
|
7
|
+
- Do not change global framework config without approval.
|
|
8
|
+
- Prefer stable selectors and accessibility attributes.
|
|
9
|
+
- Keep tests isolated and deterministic.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
commaList,
|
|
5
|
+
copyFileSafe,
|
|
6
|
+
ensureDir,
|
|
7
|
+
manifestEntry,
|
|
8
|
+
manifestPath,
|
|
9
|
+
parseArgs,
|
|
10
|
+
pathExists,
|
|
11
|
+
recordManifestEntries,
|
|
12
|
+
relativeTo,
|
|
13
|
+
logHeader
|
|
14
|
+
} from './lib/utils.mjs';
|
|
15
|
+
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const args = parseArgs(process.argv);
|
|
18
|
+
const force = Boolean(args.force);
|
|
19
|
+
const qaAiDir = path.join(cwd, '.qa-ai');
|
|
20
|
+
|
|
21
|
+
const bootstrapMap = {
|
|
22
|
+
claude: {
|
|
23
|
+
source: '.qa-ai/adapters/claude/commands/qa-init.md',
|
|
24
|
+
target: '.claude/commands/qa-init.md',
|
|
25
|
+
command: '/qa-init'
|
|
26
|
+
},
|
|
27
|
+
opencode: {
|
|
28
|
+
source: '.qa-ai/adapters/opencode/commands/qa-init.md',
|
|
29
|
+
target: '.opencode/commands/qa-init.md',
|
|
30
|
+
command: '/qa-init'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function printHelp() {
|
|
35
|
+
console.log(`Usage: node .qa-ai/scripts/bootstrap-agent-adapters.mjs [options]
|
|
36
|
+
|
|
37
|
+
Copies minimal root slash commands so Claude Code and OpenCode can run /qa-init
|
|
38
|
+
after only the .qa-ai folder has been copied.
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
--agents <list> Comma-separated agents to bootstrap (default: claude,opencode)
|
|
42
|
+
--agent <name> Repeatable single agent name
|
|
43
|
+
--force Overwrite existing bootstrap command files
|
|
44
|
+
--help Show this help
|
|
45
|
+
|
|
46
|
+
Supported agents: ${Object.keys(bootstrapMap).join(', ')}
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function selectedAgents() {
|
|
51
|
+
const requested = [...commaList(args.agents), ...commaList(args.agent)].map((name) => name.toLowerCase());
|
|
52
|
+
if (requested.length === 0 || requested.includes('all')) return Object.keys(bootstrapMap);
|
|
53
|
+
if (requested.includes('none')) return [];
|
|
54
|
+
return [...new Set(requested)];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
if (args.help) {
|
|
59
|
+
printHelp();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
logHeader('QA FlowKit agent bootstrap');
|
|
64
|
+
|
|
65
|
+
if (!await pathExists(qaAiDir)) {
|
|
66
|
+
console.error('Missing .qa-ai folder. Copy it into the repository root first.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const agents = selectedAgents();
|
|
71
|
+
const unknown = agents.filter((name) => !(name in bootstrapMap));
|
|
72
|
+
if (unknown.length > 0) {
|
|
73
|
+
console.error(`Unknown agent(s): ${unknown.join(', ')}`);
|
|
74
|
+
console.error(`Supported agents: ${Object.keys(bootstrapMap).join(', ')}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (agents.length === 0) {
|
|
79
|
+
console.log('No agents selected.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const manifestEntries = [];
|
|
84
|
+
|
|
85
|
+
for (const name of agents) {
|
|
86
|
+
const bootstrap = bootstrapMap[name];
|
|
87
|
+
const source = path.join(cwd, bootstrap.source);
|
|
88
|
+
const target = path.join(cwd, bootstrap.target);
|
|
89
|
+
|
|
90
|
+
if (!await pathExists(source)) {
|
|
91
|
+
console.log(`[FAIL] ${name}: missing ${bootstrap.source}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const dirResult = await ensureDir(path.dirname(target));
|
|
96
|
+
if (dirResult.created) {
|
|
97
|
+
manifestEntries.push(await manifestEntry(cwd, dirResult.path, {
|
|
98
|
+
type: 'dir',
|
|
99
|
+
category: 'bootstrap',
|
|
100
|
+
source: `bootstrap:${name}`
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = await copyFileSafe(source, target, { force });
|
|
105
|
+
console.log(`${name}: ${result.copied ? 'copied ' : 'skipped'} ${relativeTo(cwd, result.path)}`);
|
|
106
|
+
if (result.copied) {
|
|
107
|
+
manifestEntries.push(await manifestEntry(cwd, result.path, {
|
|
108
|
+
type: 'file',
|
|
109
|
+
category: 'bootstrap',
|
|
110
|
+
source: `bootstrap:${name}`
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const manifest = await recordManifestEntries(cwd, manifestEntries);
|
|
116
|
+
if (manifest) console.log(`updated ${relativeTo(cwd, manifestPath(cwd))}`);
|
|
117
|
+
|
|
118
|
+
console.log('\nNext: open Claude Code or OpenCode in this repository and run:');
|
|
119
|
+
console.log('/qa-init');
|
|
120
|
+
console.log('\nAdvanced example for a manual-only QA setup:');
|
|
121
|
+
console.log('/qa-init --preset manual-only --adapters claude,opencode');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main().catch((error) => {
|
|
125
|
+
console.error(error);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
hashFile,
|
|
6
|
+
loadInitManifest,
|
|
7
|
+
manifestRelativePath,
|
|
8
|
+
parseArgs,
|
|
9
|
+
pathExists,
|
|
10
|
+
resolveInsideCwd,
|
|
11
|
+
saveInitManifest,
|
|
12
|
+
logHeader
|
|
13
|
+
} from './lib/utils.mjs';
|
|
14
|
+
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const args = parseArgs(process.argv);
|
|
17
|
+
const force = Boolean(args.force);
|
|
18
|
+
const includeModified = Boolean(args['include-modified']);
|
|
19
|
+
const pruneState = Boolean(args['prune-state']);
|
|
20
|
+
|
|
21
|
+
function printHelp() {
|
|
22
|
+
console.log(`Usage: node .qa-ai/scripts/clean.mjs [options]
|
|
23
|
+
|
|
24
|
+
Default behavior is a dry-run. Nothing is deleted unless --force is passed.
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--generated Include generated non-adapter files and directories from init
|
|
28
|
+
--adapters Include generated adapter files and adapter directories
|
|
29
|
+
--empty-dirs Include tracked empty directories
|
|
30
|
+
--all Include all tracked entries (default when no category is passed)
|
|
31
|
+
--force Execute the cleanup plan
|
|
32
|
+
--include-modified Also delete tracked files whose content changed since init
|
|
33
|
+
--prune-state Remove .qa-ai/state/init-manifest.json when no entries remain
|
|
34
|
+
--help Show this help
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
node .qa-ai/scripts/clean.mjs
|
|
38
|
+
node .qa-ai/scripts/clean.mjs --generated --force
|
|
39
|
+
node .qa-ai/scripts/clean.mjs --adapters --empty-dirs --force
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function entryKey(entry) {
|
|
44
|
+
return `${entry.type}:${entry.path}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function selectedCategories() {
|
|
48
|
+
const explicit = Boolean(args.generated || args.adapters || args['empty-dirs'] || args.all);
|
|
49
|
+
return {
|
|
50
|
+
all: Boolean(args.all || !explicit),
|
|
51
|
+
generated: Boolean(args.generated),
|
|
52
|
+
adapters: Boolean(args.adapters),
|
|
53
|
+
emptyDirs: Boolean(args['empty-dirs'])
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function shouldSelect(entry, categories) {
|
|
58
|
+
if (categories.all) return true;
|
|
59
|
+
if (entry.type === 'dir') {
|
|
60
|
+
if (categories.emptyDirs) return true;
|
|
61
|
+
if (categories.generated && entry.category === 'generated') return true;
|
|
62
|
+
if (categories.adapters && entry.category === 'adapter') return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (categories.generated && entry.category === 'generated') return true;
|
|
66
|
+
if (categories.adapters && entry.category === 'adapter') return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function safeTarget(entry) {
|
|
71
|
+
if (!entry.path || path.isAbsolute(entry.path) || entry.path === '.') {
|
|
72
|
+
return { ok: false, reason: 'unsafe manifest path' };
|
|
73
|
+
}
|
|
74
|
+
if (entry.path === manifestRelativePath) {
|
|
75
|
+
return { ok: false, reason: 'manifest state is handled separately' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const target = resolveInsideCwd(cwd, entry.path);
|
|
79
|
+
if (!target.inside || target.resolved === path.resolve(cwd)) {
|
|
80
|
+
return { ok: false, reason: 'path resolves outside repository root' };
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, path: target.resolved };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function planFile(entry) {
|
|
86
|
+
const target = safeTarget(entry);
|
|
87
|
+
if (!target.ok) return { entry, action: 'skip', reason: target.reason };
|
|
88
|
+
if (!await pathExists(target.path)) return { entry, action: 'missing', removeFromManifest: true };
|
|
89
|
+
|
|
90
|
+
const stat = await fs.lstat(target.path);
|
|
91
|
+
if (!stat.isFile()) return { entry, action: 'skip', reason: 'not a regular file' };
|
|
92
|
+
|
|
93
|
+
if (entry.sha256) {
|
|
94
|
+
const currentHash = await hashFile(target.path);
|
|
95
|
+
if (currentHash !== entry.sha256 && !includeModified) {
|
|
96
|
+
return { entry, action: 'skip', reason: 'modified since init' };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
entry,
|
|
102
|
+
action: force ? 'delete-file' : 'would-delete-file',
|
|
103
|
+
target: target.path,
|
|
104
|
+
removeFromManifest: force
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function planDirectory(entry, removalPaths) {
|
|
109
|
+
const target = safeTarget(entry);
|
|
110
|
+
if (!target.ok) return { entry, action: 'skip', reason: target.reason };
|
|
111
|
+
if (!await pathExists(target.path)) return { entry, action: 'missing', removeFromManifest: true };
|
|
112
|
+
|
|
113
|
+
const stat = await fs.lstat(target.path);
|
|
114
|
+
if (!stat.isDirectory()) return { entry, action: 'skip', reason: 'not a directory' };
|
|
115
|
+
|
|
116
|
+
const children = await fs.readdir(target.path);
|
|
117
|
+
const removableAfterTrackedChildren = children.every((child) => {
|
|
118
|
+
const childPath = `${entry.path.replace(/\/$/, '')}/${child}`;
|
|
119
|
+
return removalPaths.has(childPath);
|
|
120
|
+
});
|
|
121
|
+
if (children.length > 0 && !removableAfterTrackedChildren) {
|
|
122
|
+
return { entry, action: 'skip', reason: 'directory is not empty' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
entry,
|
|
127
|
+
action: force ? 'remove-dir' : 'would-remove-dir',
|
|
128
|
+
target: target.path,
|
|
129
|
+
removeFromManifest: force
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function planEntries(entries) {
|
|
134
|
+
const files = entries.filter((entry) => entry.type === 'file');
|
|
135
|
+
const dirs = entries
|
|
136
|
+
.filter((entry) => entry.type === 'dir')
|
|
137
|
+
.sort((a, b) => b.path.length - a.path.length);
|
|
138
|
+
const planned = [];
|
|
139
|
+
const removalPaths = new Set();
|
|
140
|
+
|
|
141
|
+
for (const entry of files) {
|
|
142
|
+
const item = await planFile(entry);
|
|
143
|
+
planned.push(item);
|
|
144
|
+
if (item.removeFromManifest || item.action === 'would-delete-file') removalPaths.add(entry.path);
|
|
145
|
+
}
|
|
146
|
+
for (const entry of dirs) {
|
|
147
|
+
const item = await planDirectory(entry, removalPaths);
|
|
148
|
+
planned.push(item);
|
|
149
|
+
if (item.removeFromManifest || item.action === 'would-remove-dir') removalPaths.add(entry.path);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return planned;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function executePlan(plan) {
|
|
156
|
+
for (const item of plan) {
|
|
157
|
+
if (item.action === 'delete-file') await fs.rm(item.target, { force: false });
|
|
158
|
+
if (item.action === 'remove-dir') await fs.rmdir(item.target);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function printPlan(plan) {
|
|
163
|
+
for (const item of plan) {
|
|
164
|
+
const label = item.action.toUpperCase().replaceAll('-', ' ');
|
|
165
|
+
const suffix = item.reason ? ` (${item.reason})` : '';
|
|
166
|
+
console.log(`[${label}] ${item.entry.path}${suffix}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function summarize(plan) {
|
|
171
|
+
const counts = new Map();
|
|
172
|
+
for (const item of plan) counts.set(item.action, (counts.get(item.action) || 0) + 1);
|
|
173
|
+
return [...counts.entries()]
|
|
174
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
175
|
+
.map(([action, count]) => `${action}: ${count}`)
|
|
176
|
+
.join(', ');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function pruneManifestState(manifestPath) {
|
|
180
|
+
if (!pruneState) return;
|
|
181
|
+
await fs.rm(manifestPath, { force: true });
|
|
182
|
+
try {
|
|
183
|
+
await fs.rmdir(path.dirname(manifestPath));
|
|
184
|
+
} catch {
|
|
185
|
+
// Keep the state directory when other files are present.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function main() {
|
|
190
|
+
if (args.help) {
|
|
191
|
+
printHelp();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
logHeader('QA FlowKit clean');
|
|
196
|
+
const { exists, path: manifestPath, data: manifest } = await loadInitManifest(cwd);
|
|
197
|
+
if (!exists) {
|
|
198
|
+
console.log(`No init manifest found at ${manifestRelativePath}. Run init before clean can safely remove generated files.`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const categories = selectedCategories();
|
|
203
|
+
const selected = manifest.entries.filter((entry) => shouldSelect(entry, categories));
|
|
204
|
+
if (selected.length === 0) {
|
|
205
|
+
console.log('No matching manifest entries for the selected cleanup options.');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(force ? 'Mode: execute cleanup' : 'Mode: dry-run only');
|
|
210
|
+
if (includeModified) console.log('Modified tracked files are included because --include-modified was passed.');
|
|
211
|
+
console.log('');
|
|
212
|
+
|
|
213
|
+
const plan = await planEntries(selected);
|
|
214
|
+
printPlan(plan);
|
|
215
|
+
console.log(`\nSummary: ${summarize(plan) || 'nothing to do'}`);
|
|
216
|
+
|
|
217
|
+
if (!force) {
|
|
218
|
+
console.log('\nNo files were deleted. Re-run with --force to execute this plan.');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await executePlan(plan);
|
|
223
|
+
|
|
224
|
+
const removeKeys = new Set(plan.filter((item) => item.removeFromManifest).map((item) => entryKey(item.entry)));
|
|
225
|
+
const remainingEntries = manifest.entries.filter((entry) => !removeKeys.has(entryKey(entry)));
|
|
226
|
+
|
|
227
|
+
if (remainingEntries.length === 0 && pruneState) {
|
|
228
|
+
await pruneManifestState(manifestPath);
|
|
229
|
+
console.log(`\nRemoved ${manifestRelativePath}.`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await saveInitManifest(cwd, {
|
|
234
|
+
...manifest,
|
|
235
|
+
entries: remainingEntries
|
|
236
|
+
});
|
|
237
|
+
console.log(`\nUpdated ${manifestRelativePath}.`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
main().catch((error) => {
|
|
241
|
+
console.error(error);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
});
|