qa-engineer 0.9.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/COMPATIBILITY.md +71 -0
- package/LICENSE +21 -0
- package/README.md +508 -0
- package/package.json +90 -0
- package/packages/installer/README.md +46 -0
- package/packages/installer/bin/qa.mjs +136 -0
- package/packages/installer/lib/agents/registry.mjs +209 -0
- package/packages/installer/lib/cli/commands.mjs +29 -0
- package/packages/installer/lib/cli/flags.mjs +64 -0
- package/packages/installer/lib/commands/doctor.mjs +190 -0
- package/packages/installer/lib/commands/install.mjs +291 -0
- package/packages/installer/lib/commands/onboard.mjs +163 -0
- package/packages/installer/lib/commands/repair.mjs +68 -0
- package/packages/installer/lib/commands/self-test.mjs +45 -0
- package/packages/installer/lib/commands/uninstall.mjs +167 -0
- package/packages/installer/lib/commands/update.mjs +69 -0
- package/packages/installer/lib/commands/verify.mjs +62 -0
- package/packages/installer/lib/constants.mjs +34 -0
- package/packages/installer/lib/core/bundle.mjs +124 -0
- package/packages/installer/lib/core/config.mjs +73 -0
- package/packages/installer/lib/core/conflict.mjs +29 -0
- package/packages/installer/lib/core/errors.mjs +29 -0
- package/packages/installer/lib/core/fs-safe.mjs +236 -0
- package/packages/installer/lib/core/hash.mjs +19 -0
- package/packages/installer/lib/core/lockfile.mjs +75 -0
- package/packages/installer/lib/core/logger.mjs +46 -0
- package/packages/installer/lib/core/manifest.mjs +89 -0
- package/packages/installer/lib/core/paths.mjs +74 -0
- package/packages/installer/lib/core/schema-validate.mjs +142 -0
- package/packages/installer/lib/core/skill-meta.mjs +75 -0
- package/packages/installer/lib/core/validate-install.mjs +152 -0
- package/packages/installer/lib/core/wrappers.mjs +91 -0
- package/packages/installer/lib/detect/environment.mjs +54 -0
- package/packages/installer/lib/detect/frameworks.mjs +94 -0
- package/packages/installer/lib/detect/project.mjs +126 -0
- package/packages/installer/lib/detect/recommend.mjs +85 -0
- package/packages/installer/lib/detect/scan.mjs +48 -0
- package/packages/installer/lib/ui/progress.mjs +32 -0
- package/packages/installer/lib/ui/theme.mjs +81 -0
- package/packages/installer/lib/version.mjs +47 -0
- package/packages/installer/package.json +28 -0
- package/packages/installer/schemas/qa-lock.schema.json +89 -0
- package/packages/installer/schemas/qa.config.schema.json +91 -0
- package/shared/analysis/lib/qa_analysis/__init__.py +11 -0
- package/shared/analysis/lib/qa_analysis/branding.json +9 -0
- package/shared/analysis/lib/qa_analysis/branding.py +175 -0
- package/shared/analysis/lib/qa_analysis/cli.py +129 -0
- package/shared/analysis/lib/qa_analysis/context.py +233 -0
- package/shared/analysis/lib/qa_analysis/contracts.py +158 -0
- package/shared/analysis/lib/qa_analysis/diff_guard.py +327 -0
- package/shared/analysis/lib/qa_analysis/discovery.py +113 -0
- package/shared/analysis/lib/qa_analysis/evidence.py +128 -0
- package/shared/analysis/lib/qa_analysis/har.py +58 -0
- package/shared/analysis/lib/qa_analysis/junit.py +80 -0
- package/shared/analysis/lib/qa_analysis/redaction.py +99 -0
- package/shared/analysis/lib/qa_analysis/taxonomy.py +98 -0
- package/shared/analysis/schemas/context.schema.json +82 -0
- package/shared/diagnostics/lib/qa_diagnostics/__init__.py +13 -0
- package/shared/diagnostics/lib/qa_diagnostics/cli.py +124 -0
- package/shared/diagnostics/lib/qa_diagnostics/engine.py +143 -0
- package/shared/diagnostics/lib/qa_diagnostics/internal_contracts.py +75 -0
- package/shared/diagnostics/lib/qa_diagnostics/prioritization.py +101 -0
- package/shared/diagnostics/lib/qa_diagnostics/repair.py +72 -0
- package/shared/diagnostics/lib/qa_diagnostics/root_cause.py +89 -0
- package/shared/diagnostics/lib/qa_diagnostics/timeline.py +71 -0
- package/shared/diagnostics/schemas/internal/analysis-result.schema.json +28 -0
- package/shared/diagnostics/schemas/internal/diagnosis.schema.json +55 -0
- package/shared/diagnostics/schemas/internal/execution-result-min.schema.json +34 -0
- package/shared/frameworks/cypress/lib/cypress_analysis.py +27 -0
- package/shared/frameworks/playwright/lib/playwright_analysis.py +167 -0
- package/shared/frameworks/registry.json +150 -0
- package/shared/frameworks/registry.mjs +37 -0
- package/shared/frameworks/registry.schema.json +74 -0
- package/shared/frameworks/selenium/lib/selenium_analysis.py +28 -0
- package/shared/frameworks/webdriverio/lib/webdriverio_analysis.py +24 -0
- package/shared/tooling/qa_tool.py +127 -0
- package/skills/README.md +31 -0
- package/skills/qa/README.md +19 -0
- package/skills/qa/SKILL.md +52 -0
- package/skills/qa/examples/routing.md +86 -0
- package/skills/qa/references/routing-map.md +33 -0
- package/skills/qa-api/README.md +19 -0
- package/skills/qa-api/SKILL.md +68 -0
- package/skills/qa-api/contracts/api-result.schema.json +51 -0
- package/skills/qa-api/examples/graphql-review.md +43 -0
- package/skills/qa-api/references/authentication.md +45 -0
- package/skills/qa-api/references/deterministic-tooling.md +128 -0
- package/skills/qa-api/references/evidence-and-reporting.md +66 -0
- package/skills/qa-api/references/graphql.md +44 -0
- package/skills/qa-api/references/rest.md +45 -0
- package/skills/qa-api/references/websocket.md +44 -0
- package/skills/qa-audit/README.md +19 -0
- package/skills/qa-audit/SKILL.md +70 -0
- package/skills/qa-audit/contracts/audit-result.schema.json +63 -0
- package/skills/qa-audit/examples/accessibility-audit.md +46 -0
- package/skills/qa-audit/references/accessibility.md +44 -0
- package/skills/qa-audit/references/deterministic-tooling.md +128 -0
- package/skills/qa-audit/references/evidence-and-reporting.md +66 -0
- package/skills/qa-audit/references/performance.md +44 -0
- package/skills/qa-audit/references/security.md +43 -0
- package/skills/qa-audit/references/visual-testing.md +44 -0
- package/skills/qa-debug/README.md +19 -0
- package/skills/qa-debug/SKILL.md +76 -0
- package/skills/qa-debug/contracts/debug-result.schema.json +121 -0
- package/skills/qa-debug/examples/failed-login.md +61 -0
- package/skills/qa-debug/examples/locator-break.md +59 -0
- package/skills/qa-debug/examples/network-timeout.md +61 -0
- package/skills/qa-debug/examples/successful-debug.md +62 -0
- package/skills/qa-debug/references/confidence-model.md +26 -0
- package/skills/qa-debug/references/deterministic-tooling.md +128 -0
- package/skills/qa-debug/references/diagnostic-engine.md +36 -0
- package/skills/qa-debug/references/evidence-and-reporting.md +66 -0
- package/skills/qa-debug/references/evidence-model.md +41 -0
- package/skills/qa-debug/references/failure-taxonomy.md +44 -0
- package/skills/qa-debug/references/finding-prioritization.md +43 -0
- package/skills/qa-debug/references/investigation-workflow.md +48 -0
- package/skills/qa-debug/references/recommendation-ranking.md +34 -0
- package/skills/qa-debug/references/root-cause-analysis.md +49 -0
- package/skills/qa-debug/references/timeline-builder.md +37 -0
- package/skills/qa-example/README.md +19 -0
- package/skills/qa-example/SKILL.md +60 -0
- package/skills/qa-example/contracts/self-check-report.schema.json +65 -0
- package/skills/qa-example/examples/self-check.md +53 -0
- package/skills/qa-example/references/example-domain.md +28 -0
- package/skills/qa-example/references/skill-format-notes.md +26 -0
- package/skills/qa-explore/README.md +22 -0
- package/skills/qa-explore/SKILL.md +87 -0
- package/skills/qa-explore/contracts/explore-result.schema.json +249 -0
- package/skills/qa-explore/examples/attached-test-cases.md +70 -0
- package/skills/qa-explore/examples/screenshot-proof-finding.md +50 -0
- package/skills/qa-explore/examples/url-smoke-explore.md +80 -0
- package/skills/qa-explore/references/accessibility.md +44 -0
- package/skills/qa-explore/references/api-replay.md +48 -0
- package/skills/qa-explore/references/browser-adapters.md +51 -0
- package/skills/qa-explore/references/evidence-and-reporting.md +66 -0
- package/skills/qa-explore/references/evidence-capture.md +54 -0
- package/skills/qa-explore/references/exploratory-qa.md +52 -0
- package/skills/qa-explore/references/finding-taxonomy.md +47 -0
- package/skills/qa-explore/references/performance.md +44 -0
- package/skills/qa-explore/references/pipeline.md +74 -0
- package/skills/qa-explore/references/report-pipeline.md +69 -0
- package/skills/qa-explore/references/security.md +43 -0
- package/skills/qa-explore/references/test-case-intake.md +44 -0
- package/skills/qa-fix/README.md +19 -0
- package/skills/qa-fix/SKILL.md +70 -0
- package/skills/qa-fix/contracts/fix-result.schema.json +132 -0
- package/skills/qa-fix/examples/repair-plan.md +56 -0
- package/skills/qa-fix/references/deterministic-tooling.md +128 -0
- package/skills/qa-fix/references/diagnostic-engine.md +36 -0
- package/skills/qa-fix/references/evidence-and-reporting.md +66 -0
- package/skills/qa-fix/references/repair-strategy.md +48 -0
- package/skills/qa-fix/references/root-cause-analysis.md +49 -0
- package/skills/qa-fix/references/suite-extension.md +73 -0
- package/skills/qa-flaky/README.md +19 -0
- package/skills/qa-flaky/SKILL.md +67 -0
- package/skills/qa-flaky/contracts/flaky-result.schema.json +62 -0
- package/skills/qa-flaky/examples/flaky-locator.md +46 -0
- package/skills/qa-flaky/references/deterministic-tooling.md +128 -0
- package/skills/qa-flaky/references/evidence-and-reporting.md +66 -0
- package/skills/qa-flaky/references/flakiness.md +48 -0
- package/skills/qa-flaky/references/retry.md +44 -0
- package/skills/qa-flaky/references/waiting-strategies.md +44 -0
- package/skills/qa-generate/README.md +42 -0
- package/skills/qa-generate/SKILL.md +83 -0
- package/skills/qa-generate/contracts/generation-result.schema.json +124 -0
- package/skills/qa-generate/examples/bootstrap-new-framework.md +24 -0
- package/skills/qa-generate/examples/extend-existing-suite.md +26 -0
- package/skills/qa-generate/references/code-style.md +29 -0
- package/skills/qa-generate/references/evidence-and-reporting.md +66 -0
- package/skills/qa-generate/references/framework-selection.md +88 -0
- package/skills/qa-generate/references/generation-strategy.md +48 -0
- package/skills/qa-generate/references/naming-conventions.md +27 -0
- package/skills/qa-generate/references/playwright-conventions.md +23 -0
- package/skills/qa-generate/references/playwright-generation.md +41 -0
- package/skills/qa-generate/references/playwright-project-discovery.md +42 -0
- package/skills/qa-generate/references/project-bootstrap.md +54 -0
- package/skills/qa-generate/references/repository-analysis.md +43 -0
- package/skills/qa-generate/references/suite-extension.md +73 -0
- package/skills/qa-generate/references/template-selection.md +48 -0
- package/skills/qa-generate/templates/playwright/api.ts +36 -0
- package/skills/qa-generate/templates/playwright/base-page.ts +14 -0
- package/skills/qa-generate/templates/playwright/data.ts +25 -0
- package/skills/qa-generate/templates/playwright/env.example +16 -0
- package/skills/qa-generate/templates/playwright/example.spec.ts +14 -0
- package/skills/qa-generate/templates/playwright/fixtures.ts +28 -0
- package/skills/qa-generate/templates/playwright/framework-readme.md +39 -0
- package/skills/qa-generate/templates/playwright/login.page.ts +26 -0
- package/skills/qa-generate/templates/playwright/playwright.config.ts +28 -0
- package/skills/qa-generate/templates/playwright/utils.ts +18 -0
- package/skills/qa-init/README.md +20 -0
- package/skills/qa-init/SKILL.md +72 -0
- package/skills/qa-init/examples/initialize-a-repo.md +91 -0
- package/skills/qa-init/references/detection-guide.md +76 -0
- package/skills/qa-init/references/deterministic-tooling.md +128 -0
- package/skills/qa-init/references/evidence-and-reporting.md +66 -0
- package/skills/qa-init/templates/context.md +68 -0
- package/skills/qa-report/README.md +19 -0
- package/skills/qa-report/SKILL.md +72 -0
- package/skills/qa-report/contracts/report-result.schema.json +186 -0
- package/skills/qa-report/examples/release-report.md +68 -0
- package/skills/qa-report/references/deterministic-tooling.md +128 -0
- package/skills/qa-report/references/diagnostic-engine.md +36 -0
- package/skills/qa-report/references/evidence-and-reporting.md +66 -0
- package/skills/qa-report/references/finding-prioritization.md +43 -0
- package/skills/qa-report/references/recommendation-ranking.md +34 -0
- package/skills/qa-report/references/report-aggregation.md +44 -0
- package/skills/qa-review/README.md +19 -0
- package/skills/qa-review/SKILL.md +58 -0
- package/skills/qa-review/contracts/review-result.schema.json +59 -0
- package/skills/qa-review/examples/suite-review.md +51 -0
- package/skills/qa-review/references/anti-patterns.md +49 -0
- package/skills/qa-review/references/assertion-patterns.md +45 -0
- package/skills/qa-review/references/evidence-and-reporting.md +66 -0
- package/skills/qa-review/references/fixtures.md +45 -0
- package/skills/qa-review/references/page-objects.md +45 -0
- package/skills/qa-run/README.md +22 -0
- package/skills/qa-run/SKILL.md +92 -0
- package/skills/qa-run/contracts/execution-plan.schema.json +132 -0
- package/skills/qa-run/contracts/execution-result.schema.json +204 -0
- package/skills/qa-run/examples/execute-playwright.md +89 -0
- package/skills/qa-run/examples/plan-a-run.md +83 -0
- package/skills/qa-run/references/artifact-collector.md +40 -0
- package/skills/qa-run/references/browser-launch.md +39 -0
- package/skills/qa-run/references/command-builder.md +39 -0
- package/skills/qa-run/references/deterministic-tooling.md +128 -0
- package/skills/qa-run/references/environment-detection.md +37 -0
- package/skills/qa-run/references/evidence-and-reporting.md +66 -0
- package/skills/qa-run/references/execution-strategy.md +54 -0
- package/skills/qa-run/references/playwright-artifacts.md +29 -0
- package/skills/qa-run/references/playwright-execution.md +48 -0
- package/skills/qa-run/references/playwright-project-discovery.md +42 -0
- package/skills/qa-run/references/report-normalization.md +49 -0
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qa-engineer",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "A vendor-neutral Agent Skills pack that teaches AI coding agents to work like senior QA automation engineers.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://github.com/abisheik88/qa-engineer#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/abisheik88/qa-engineer/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/abisheik88/qa-engineer.git"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.18.0"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"qa-engineer": "packages/installer/bin/qa.mjs",
|
|
20
|
+
"qa": "packages/installer/bin/qa.mjs",
|
|
21
|
+
"qa-pack": "packages/installer/bin/qa.mjs"
|
|
22
|
+
},
|
|
23
|
+
"workspaces": [
|
|
24
|
+
"packages/*"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"packages/installer/bin",
|
|
28
|
+
"packages/installer/lib",
|
|
29
|
+
"packages/installer/schemas",
|
|
30
|
+
"packages/installer/package.json",
|
|
31
|
+
"packages/installer/README.md",
|
|
32
|
+
"skills",
|
|
33
|
+
"shared/analysis/lib/qa_analysis",
|
|
34
|
+
"shared/analysis/schemas",
|
|
35
|
+
"shared/diagnostics/lib/qa_diagnostics",
|
|
36
|
+
"shared/diagnostics/schemas",
|
|
37
|
+
"shared/tooling/qa_tool.py",
|
|
38
|
+
"shared/frameworks/registry.json",
|
|
39
|
+
"shared/frameworks/registry.schema.json",
|
|
40
|
+
"shared/frameworks/registry.mjs",
|
|
41
|
+
"shared/frameworks/playwright/lib/playwright_analysis.py",
|
|
42
|
+
"shared/frameworks/selenium/lib/selenium_analysis.py",
|
|
43
|
+
"shared/frameworks/cypress/lib/cypress_analysis.py",
|
|
44
|
+
"shared/frameworks/webdriverio/lib/webdriverio_analysis.py",
|
|
45
|
+
"COMPATIBILITY.md",
|
|
46
|
+
"!**/__pycache__",
|
|
47
|
+
"!**/*.pyc",
|
|
48
|
+
"!**/tests"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"qa": "node packages/installer/bin/qa.mjs",
|
|
52
|
+
"eval:live": "python3 tests/evals/run_live.py --baseline tests/evals/baselines/reference.json",
|
|
53
|
+
"prepublishOnly": "npm run validate:release && npm run validate:doc-claims && npm run validate:docs-commands && npm test && npm run validate:python && npm run validate:evals",
|
|
54
|
+
"release:checksums": "node scripts/release/release-notes.mjs --checksums",
|
|
55
|
+
"release:notes": "node scripts/release/release-notes.mjs",
|
|
56
|
+
"test": "node --test packages/installer/test/*.test.mjs",
|
|
57
|
+
"validate:architecture": "node scripts/check-architecture-fitness.mjs",
|
|
58
|
+
"validate:branding": "node scripts/check-branding.mjs",
|
|
59
|
+
"validate:bundle": "python3 scripts/bundle_python.py --check",
|
|
60
|
+
"validate:context": "cd shared/analysis/lib && python3 -m qa_analysis.cli context --path tests/fixtures/valid-context.md",
|
|
61
|
+
"validate:doc-claims": "node scripts/check-doc-claims.mjs",
|
|
62
|
+
"validate:docs-commands": "node scripts/check-docs-commands.mjs",
|
|
63
|
+
"validate:evals": "python3 tests/evals/run_evals.py",
|
|
64
|
+
"validate:keywords": "node scripts/check-keywords.mjs",
|
|
65
|
+
"validate:knowledge": "node scripts/check-knowledge.mjs",
|
|
66
|
+
"validate:matrix": "node scripts/check-capability-matrix.mjs",
|
|
67
|
+
"validate:python": "python3 shared/analysis/lib/run_tests.py",
|
|
68
|
+
"validate:registry": "node scripts/check-framework-registry.mjs",
|
|
69
|
+
"validate:release": "node scripts/release/validate-release.mjs",
|
|
70
|
+
"validate:release-notes": "node scripts/release/release-notes.mjs --check",
|
|
71
|
+
"validate:skills": "node scripts/validate-skills.mjs",
|
|
72
|
+
"validate:spec-sync": "node scripts/check-spec-code-sync.mjs",
|
|
73
|
+
"validate:sync": "node scripts/sync-shared.mjs --check"
|
|
74
|
+
},
|
|
75
|
+
"keywords": [
|
|
76
|
+
"qa-engineer",
|
|
77
|
+
"agent-skills",
|
|
78
|
+
"qa",
|
|
79
|
+
"test-automation",
|
|
80
|
+
"playwright",
|
|
81
|
+
"selenium",
|
|
82
|
+
"cypress",
|
|
83
|
+
"webdriverio",
|
|
84
|
+
"ai-agents",
|
|
85
|
+
"claude-code"
|
|
86
|
+
],
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"@clack/prompts": "0.11.0"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @qa-engineer/installer
|
|
2
|
+
|
|
3
|
+
Copy-based installer for the QA Automation Pack. Installs Agent Skills into `.agents/skills/` and `.claude/skills/`, writes `qa-lock.json`, and generates thin slash wrappers for agents that need them. **No skill code runs at install time.**
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Interactive (default)
|
|
9
|
+
npx qa-engineer
|
|
10
|
+
npx qa-engineer --yes --project /path/to/app
|
|
11
|
+
|
|
12
|
+
# Explicit commands
|
|
13
|
+
npx qa-engineer install --project /path/to/app
|
|
14
|
+
npx qa-engineer verify --project /path/to/app
|
|
15
|
+
npx qa-engineer doctor --project /path/to/app
|
|
16
|
+
npx qa-engineer self-test --project /path/to/app
|
|
17
|
+
npx qa-engineer repair --project /path/to/app
|
|
18
|
+
npx qa-engineer update --project /path/to/app
|
|
19
|
+
npx qa-engineer uninstall --project /path/to/app
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Root package binaries: `qa` / `qa-pack` / `qa-engineer` (see repository
|
|
23
|
+
`package.json`). The name-matching binary is what makes `npx qa-engineer`
|
|
24
|
+
resolve without `-p`.
|
|
25
|
+
|
|
26
|
+
From a local checkout before npm publish:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install
|
|
30
|
+
npm run qa -- --yes --project /path/to/app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Agents
|
|
34
|
+
|
|
35
|
+
| Id | Skills path | Wrappers |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| claude-code | `.claude/skills/` | native |
|
|
38
|
+
| cursor | `.agents/skills/` | native |
|
|
39
|
+
| codex | `.agents/skills/` | native |
|
|
40
|
+
| opencode | `.agents/skills/` | `.opencode/commands/` |
|
|
41
|
+
| gemini-cli | `.agents/skills/` | `.gemini/commands/` |
|
|
42
|
+
| github-copilot | `.agents/skills/` | `.github/prompts/` |
|
|
43
|
+
| antigravity | `.agents/skills/` | `.agents/workflows/` |
|
|
44
|
+
| kimi | `.agents/skills/` | copy-only |
|
|
45
|
+
|
|
46
|
+
See [docs/installation/](../../docs/installation/README.md).
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// qa-engineer CLI entry.
|
|
3
|
+
|
|
4
|
+
import { EXIT } from '../lib/constants.mjs';
|
|
5
|
+
import { VERSION } from '../lib/version.mjs';
|
|
6
|
+
import { QaError } from '../lib/core/errors.mjs';
|
|
7
|
+
import { createLogger } from '../lib/core/logger.mjs';
|
|
8
|
+
import { runInstall } from '../lib/commands/install.mjs';
|
|
9
|
+
import { runVerify } from '../lib/commands/verify.mjs';
|
|
10
|
+
import { runDoctor } from '../lib/commands/doctor.mjs';
|
|
11
|
+
import { runOnboard } from '../lib/commands/onboard.mjs';
|
|
12
|
+
import { runSelfTest } from '../lib/commands/self-test.mjs';
|
|
13
|
+
import { runRepair } from '../lib/commands/repair.mjs';
|
|
14
|
+
import { runUpdate } from '../lib/commands/update.mjs';
|
|
15
|
+
import { runUninstall } from '../lib/commands/uninstall.mjs';
|
|
16
|
+
import { listAgentIds } from '../lib/agents/registry.mjs';
|
|
17
|
+
import { COMMANDS, ACCEPTED } from '../lib/cli/commands.mjs';
|
|
18
|
+
import { formatUserError } from '../lib/ui/theme.mjs';
|
|
19
|
+
|
|
20
|
+
const debug =
|
|
21
|
+
process.env.QA_DEBUG === '1' ||
|
|
22
|
+
process.env.QA_DEBUG === 'true' ||
|
|
23
|
+
process.argv.includes('--debug');
|
|
24
|
+
|
|
25
|
+
const log = createLogger({ level: process.env.QA_LOG_LEVEL || 'info' });
|
|
26
|
+
|
|
27
|
+
function printHelp() {
|
|
28
|
+
const commandLines = COMMANDS.map((c) => ` ${c.name.padEnd(10)} ${c.summary}`).join('\n');
|
|
29
|
+
log.result(`qa-engineer ${VERSION}
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
qa Interactive install + guided first-run
|
|
33
|
+
qa <command> [options]
|
|
34
|
+
|
|
35
|
+
Commands:
|
|
36
|
+
${commandLines}
|
|
37
|
+
|
|
38
|
+
Common options:
|
|
39
|
+
--project <d> Project root (default: cwd)
|
|
40
|
+
--agent <id> Target agent (repeatable). Known: ${listAgentIds().join(', ')}
|
|
41
|
+
--yes / --ci Skip prompts
|
|
42
|
+
--json Machine-readable stdout
|
|
43
|
+
--force Overwrite conflicting files
|
|
44
|
+
--dry-run Plan without writing
|
|
45
|
+
--debug Show stack traces on unexpected errors
|
|
46
|
+
|
|
47
|
+
Skills install into .agents/skills/ (most agents) and .claude/skills/
|
|
48
|
+
(Claude Code).`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveCommand(argv) {
|
|
52
|
+
const first = argv[0];
|
|
53
|
+
const commands = new Set(ACCEPTED);
|
|
54
|
+
|
|
55
|
+
if (!first) {
|
|
56
|
+
// Bare `qa`: interactive onboard on TTY; help otherwise.
|
|
57
|
+
if (process.stdin.isTTY && process.stdout.isTTY) return { cmd: 'onboard', rest: [] };
|
|
58
|
+
return { cmd: 'help-non-tty', rest: [] };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (commands.has(first)) {
|
|
62
|
+
return { cmd: first, rest: argv.slice(1) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Flags without a command → onboard with those flags (e.g. qa --yes --project .)
|
|
66
|
+
if (first.startsWith('-')) {
|
|
67
|
+
return { cmd: 'onboard', rest: argv };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { cmd: 'unknown', rest: argv };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function main() {
|
|
74
|
+
const argv = process.argv.slice(2);
|
|
75
|
+
const { cmd, rest } = resolveCommand(argv);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
let code = EXIT.OK;
|
|
79
|
+
switch (cmd) {
|
|
80
|
+
case 'onboard':
|
|
81
|
+
code = await runOnboard(rest, { log });
|
|
82
|
+
break;
|
|
83
|
+
case 'install':
|
|
84
|
+
code = await runInstall(rest, { log });
|
|
85
|
+
break;
|
|
86
|
+
case 'verify':
|
|
87
|
+
code = await runVerify(rest, { log });
|
|
88
|
+
break;
|
|
89
|
+
case 'doctor':
|
|
90
|
+
code = await runDoctor(rest, { log });
|
|
91
|
+
break;
|
|
92
|
+
case 'self-test':
|
|
93
|
+
code = await runSelfTest(rest, { log });
|
|
94
|
+
break;
|
|
95
|
+
case 'repair':
|
|
96
|
+
code = await runRepair(rest, { log });
|
|
97
|
+
break;
|
|
98
|
+
case 'update':
|
|
99
|
+
code = await runUpdate(rest, { log });
|
|
100
|
+
break;
|
|
101
|
+
case 'uninstall':
|
|
102
|
+
code = await runUninstall(rest, { log });
|
|
103
|
+
break;
|
|
104
|
+
case 'version':
|
|
105
|
+
case '--version':
|
|
106
|
+
case '-V':
|
|
107
|
+
log.result(VERSION);
|
|
108
|
+
break;
|
|
109
|
+
case 'help':
|
|
110
|
+
case '--help':
|
|
111
|
+
case '-h':
|
|
112
|
+
printHelp();
|
|
113
|
+
break;
|
|
114
|
+
case 'help-non-tty':
|
|
115
|
+
printHelp();
|
|
116
|
+
code = EXIT.USAGE;
|
|
117
|
+
break;
|
|
118
|
+
case 'unknown':
|
|
119
|
+
default:
|
|
120
|
+
log.error(`unknown command: ${argv[0]}`);
|
|
121
|
+
printHelp();
|
|
122
|
+
code = EXIT.USAGE;
|
|
123
|
+
}
|
|
124
|
+
process.exitCode = code;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof QaError) {
|
|
127
|
+
log.error(formatUserError(error, { debug }));
|
|
128
|
+
process.exitCode = error.code ?? EXIT.FAILURE;
|
|
129
|
+
} else {
|
|
130
|
+
log.error(formatUserError(error, { debug: true }));
|
|
131
|
+
process.exitCode = EXIT.FAILURE;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Known Agent Skills hosts the installer can target. Detection is best-effort
|
|
2
|
+
// from project markers; users can override with qa.config.json "agents" or
|
|
3
|
+
// `qa install --agent <id>`.
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { SHARED_SKILLS_DIR, CLAUDE_SKILLS_DIR } from '../constants.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} AgentDef
|
|
11
|
+
* @property {string} id
|
|
12
|
+
* @property {string} name
|
|
13
|
+
* @property {number} tier
|
|
14
|
+
* @property {string} skillsDir project-relative skills discovery path
|
|
15
|
+
* @property {string|null} wrapperFormat wrappers.mjs renderer key, or null
|
|
16
|
+
* @property {string|null} wrapperDir where wrappers are written, or null
|
|
17
|
+
* @property {(root:string)=>boolean} detect
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** @type {AgentDef[]} */
|
|
21
|
+
export const AGENTS = Object.freeze([
|
|
22
|
+
{
|
|
23
|
+
id: 'claude-code',
|
|
24
|
+
name: 'Claude Code',
|
|
25
|
+
tier: 1,
|
|
26
|
+
skillsDir: CLAUDE_SKILLS_DIR,
|
|
27
|
+
wrapperFormat: null,
|
|
28
|
+
wrapperDir: null,
|
|
29
|
+
detect: (root) =>
|
|
30
|
+
fs.existsSync(path.join(root, '.claude')) ||
|
|
31
|
+
fs.existsSync(path.join(root, 'CLAUDE.md')),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'cursor',
|
|
35
|
+
name: 'Cursor',
|
|
36
|
+
tier: 2,
|
|
37
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
38
|
+
wrapperFormat: null,
|
|
39
|
+
wrapperDir: null,
|
|
40
|
+
detect: (root) =>
|
|
41
|
+
fs.existsSync(path.join(root, '.cursor')) ||
|
|
42
|
+
fs.existsSync(path.join(root, '.cursorrules')),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'codex',
|
|
46
|
+
name: 'OpenAI Codex CLI',
|
|
47
|
+
tier: 1,
|
|
48
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
49
|
+
wrapperFormat: null,
|
|
50
|
+
wrapperDir: null,
|
|
51
|
+
detect: (root) =>
|
|
52
|
+
fs.existsSync(path.join(root, '.codex')) ||
|
|
53
|
+
fs.existsSync(path.join(root, 'AGENTS.md')),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'opencode',
|
|
57
|
+
name: 'OpenCode',
|
|
58
|
+
tier: 1,
|
|
59
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
60
|
+
wrapperFormat: 'command-md',
|
|
61
|
+
wrapperDir: '.opencode/commands',
|
|
62
|
+
detect: (root) => fs.existsSync(path.join(root, '.opencode')),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'gemini-cli',
|
|
66
|
+
name: 'Gemini CLI',
|
|
67
|
+
tier: 2,
|
|
68
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
69
|
+
wrapperFormat: 'command-toml',
|
|
70
|
+
wrapperDir: '.gemini/commands',
|
|
71
|
+
detect: (root) => fs.existsSync(path.join(root, '.gemini')),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'github-copilot',
|
|
75
|
+
name: 'GitHub Copilot',
|
|
76
|
+
tier: 2,
|
|
77
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
78
|
+
wrapperFormat: 'prompt-md',
|
|
79
|
+
wrapperDir: '.github/prompts',
|
|
80
|
+
// Detected by a Copilot-specific marker, not by `.github/` — almost every
|
|
81
|
+
// repository has `.github/` for Actions or issue templates, so keying on it
|
|
82
|
+
// reported Copilot for projects that do not use it and wrote 13 wrapper
|
|
83
|
+
// files nobody asked for. Request them with `--agent github-copilot`.
|
|
84
|
+
detect: (root) =>
|
|
85
|
+
fs.existsSync(path.join(root, '.github', 'copilot-instructions.md')) ||
|
|
86
|
+
fs.existsSync(path.join(root, '.github', 'prompts')),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'antigravity',
|
|
90
|
+
name: 'Antigravity',
|
|
91
|
+
tier: 2,
|
|
92
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
93
|
+
wrapperFormat: 'workflow-md',
|
|
94
|
+
wrapperDir: '.agents/workflows',
|
|
95
|
+
// Detected by its own configuration directory, NOT by `.agents/`.
|
|
96
|
+
// `.agents/skills/` is the shared Agent Skills path that this installer
|
|
97
|
+
// creates for every host, so keying on `.agents/` made the installer detect
|
|
98
|
+
// Antigravity purely because it had run once before: a second `install`
|
|
99
|
+
// silently added 13 wrapper files and reported a host that was never really
|
|
100
|
+
// there. Detection must never treat the pack's own footprint as evidence.
|
|
101
|
+
// Request the wrappers explicitly with `--agent antigravity`.
|
|
102
|
+
detect: (root) => fs.existsSync(path.join(root, '.antigravity')),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'kimi',
|
|
106
|
+
name: 'Kimi (Agent Skills copy)',
|
|
107
|
+
tier: 2,
|
|
108
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
109
|
+
wrapperFormat: null,
|
|
110
|
+
wrapperDir: null,
|
|
111
|
+
detect: () => false,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
// The honest fallback when no agent marker is present. Installing into the
|
|
115
|
+
// shared Agent Skills path is right for any spec-compliant host, but naming
|
|
116
|
+
// a specific product would report a detection that never happened — so this
|
|
117
|
+
// entry says exactly what is known: nothing, beyond the standard path.
|
|
118
|
+
id: 'agent-skills',
|
|
119
|
+
name: 'Unknown agent (shared Agent Skills path)',
|
|
120
|
+
tier: null,
|
|
121
|
+
skillsDir: SHARED_SKILLS_DIR,
|
|
122
|
+
wrapperFormat: null,
|
|
123
|
+
wrapperDir: null,
|
|
124
|
+
detected: false,
|
|
125
|
+
detect: () => false,
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
/** The id used when nothing could be detected. */
|
|
130
|
+
export const UNKNOWN_AGENT_ID = 'agent-skills';
|
|
131
|
+
|
|
132
|
+
const BY_ID = new Map(AGENTS.map((a) => [a.id, a]));
|
|
133
|
+
|
|
134
|
+
export function getAgent(id) {
|
|
135
|
+
return BY_ID.get(id) ?? null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function listAgentIds() {
|
|
139
|
+
return AGENTS.map((a) => a.id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve which agents to install for. Explicit ids win; otherwise detect; if
|
|
144
|
+
* nothing is detected, install once into the shared `.agents/skills` path under
|
|
145
|
+
* the honest "Unknown agent" entry rather than naming a product that was never
|
|
146
|
+
* detected. The install location is identical either way — only the report
|
|
147
|
+
* differs, and a lockfile that claims a detection that did not happen is a
|
|
148
|
+
* small lie the pack cannot afford.
|
|
149
|
+
*/
|
|
150
|
+
export function resolveAgents(projectRoot, explicitIds = []) {
|
|
151
|
+
if (explicitIds.length > 0) {
|
|
152
|
+
const resolved = [];
|
|
153
|
+
for (const id of explicitIds) {
|
|
154
|
+
const agent = getAgent(id);
|
|
155
|
+
if (!agent) throw new Error(`unknown agent id: ${id}`);
|
|
156
|
+
resolved.push({ ...agent, detected: false, requested: true });
|
|
157
|
+
}
|
|
158
|
+
return dedupeBySkillsDir(resolved);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const detected = AGENTS.filter((a) => a.detect(projectRoot));
|
|
162
|
+
if (detected.length > 0) {
|
|
163
|
+
return dedupeBySkillsDir(detected.map((a) => ({ ...a, detected: true })));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Nothing detected. The shared Agent Skills path still serves Cursor, Codex,
|
|
167
|
+
// Copilot, OpenCode, Gemini, Antigravity, Kimi, and peers that read it.
|
|
168
|
+
return [{ ...getAgent(UNKNOWN_AGENT_ID), detected: false }];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Prefer one install per unique skillsDir (avoid duplicating files). */
|
|
172
|
+
function dedupeBySkillsDir(agents) {
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const agent of agents) {
|
|
176
|
+
if (seen.has(agent.skillsDir)) continue;
|
|
177
|
+
seen.add(agent.skillsDir);
|
|
178
|
+
out.push(agent);
|
|
179
|
+
}
|
|
180
|
+
// Claude Code always needs its own directory in addition to .agents/skills
|
|
181
|
+
// when both are requested — restore claude if it was collapsed away incorrectly.
|
|
182
|
+
const wantsClaude = agents.some((a) => a.id === 'claude-code');
|
|
183
|
+
if (wantsClaude && !out.some((a) => a.id === 'claude-code')) {
|
|
184
|
+
out.push(getAgent('claude-code'));
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* When installing for "all common hosts", copy into both canonical dirs.
|
|
191
|
+
*/
|
|
192
|
+
export function resolveInstallTargets(projectRoot, explicitIds = []) {
|
|
193
|
+
const agents = resolveAgents(projectRoot, explicitIds);
|
|
194
|
+
const hasClaude = agents.some((a) => a.id === 'claude-code');
|
|
195
|
+
const hasShared = agents.some((a) => a.skillsDir === SHARED_SKILLS_DIR);
|
|
196
|
+
|
|
197
|
+
// If only Claude was detected, still install the shared path so other tools
|
|
198
|
+
// work — but attribute it to the unknown-agent entry, because no host that
|
|
199
|
+
// reads .agents/skills/ was actually detected here.
|
|
200
|
+
// If only shared was detected, also install Claude when .claude exists.
|
|
201
|
+
const targets = [...agents];
|
|
202
|
+
if (hasClaude && !hasShared) {
|
|
203
|
+
targets.push({ ...getAgent(UNKNOWN_AGENT_ID), detected: false });
|
|
204
|
+
}
|
|
205
|
+
if (hasShared && fs.existsSync(path.join(projectRoot, '.claude')) && !hasClaude) {
|
|
206
|
+
targets.push({ ...getAgent('claude-code'), detected: true });
|
|
207
|
+
}
|
|
208
|
+
return dedupeBySkillsDir(targets);
|
|
209
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The CLI's command surface, declared once.
|
|
2
|
+
//
|
|
3
|
+
// bin/qa.mjs dispatches on this list, `help` prints from it, and
|
|
4
|
+
// scripts/check-docs-commands.mjs holds the documentation to it — so a command
|
|
5
|
+
// cannot ship undocumented, and the docs cannot name a command that does not
|
|
6
|
+
// exist. (`uninstall` shipped only after being documented in fs-safe.mjs but
|
|
7
|
+
// never implemented; this is the check that would have caught it.)
|
|
8
|
+
|
|
9
|
+
/** @type {Array<{name: string, summary: string}>} */
|
|
10
|
+
export const COMMANDS = Object.freeze([
|
|
11
|
+
{ name: 'onboard', summary: 'Interactive installer (default when no command is given)' },
|
|
12
|
+
{ name: 'install', summary: 'Non-interactive install into Agent Skills paths' },
|
|
13
|
+
{ name: 'verify', summary: 'Check installed files against qa-lock.json' },
|
|
14
|
+
{ name: 'doctor', summary: 'Diagnose environment and installation' },
|
|
15
|
+
{ name: 'self-test', summary: 'PASS/FAIL smoke checks for the installed pack' },
|
|
16
|
+
{ name: 'repair', summary: 'Fix drifted or missing pack files' },
|
|
17
|
+
{ name: 'update', summary: 'Refresh install from the current pack source' },
|
|
18
|
+
{ name: 'uninstall', summary: 'Remove every file listed in qa-lock.json' },
|
|
19
|
+
{ name: 'version', summary: 'Print pack version' },
|
|
20
|
+
{ name: 'help', summary: 'Show this help' },
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/** Aliases accepted in addition to the command names above. */
|
|
24
|
+
export const ALIASES = Object.freeze(['--version', '-V', '--help', '-h']);
|
|
25
|
+
|
|
26
|
+
export const COMMAND_NAMES = Object.freeze(COMMANDS.map((c) => c.name));
|
|
27
|
+
|
|
28
|
+
/** Every token `resolveCommand` accepts as a command. */
|
|
29
|
+
export const ACCEPTED = Object.freeze([...COMMAND_NAMES, ...ALIASES]);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Shared CLI flag parsing helpers for installer commands.
|
|
2
|
+
|
|
3
|
+
import { usageError } from '../core/errors.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse common flags from argv. Unknown tokens are returned in `rest` unless
|
|
7
|
+
* `strict` is true (then they throw).
|
|
8
|
+
*
|
|
9
|
+
* @param {string[]} argv
|
|
10
|
+
* @param {{ strict?: boolean }} [opts]
|
|
11
|
+
*/
|
|
12
|
+
export function parseCommonFlags(argv, { strict = true } = {}) {
|
|
13
|
+
const flags = {
|
|
14
|
+
yes: false,
|
|
15
|
+
ci: false,
|
|
16
|
+
json: false,
|
|
17
|
+
debug: false,
|
|
18
|
+
force: false,
|
|
19
|
+
dryRun: false,
|
|
20
|
+
help: false,
|
|
21
|
+
project: null,
|
|
22
|
+
agents: [],
|
|
23
|
+
rest: /** @type {string[]} */ ([]),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
27
|
+
const a = argv[i];
|
|
28
|
+
if (a === '--yes' || a === '-y') flags.yes = true;
|
|
29
|
+
else if (a === '--ci') {
|
|
30
|
+
flags.ci = true;
|
|
31
|
+
flags.yes = true;
|
|
32
|
+
} else if (a === '--json') flags.json = true;
|
|
33
|
+
else if (a === '--debug') flags.debug = true;
|
|
34
|
+
else if (a === '--force') flags.force = true;
|
|
35
|
+
else if (a === '--dry-run') flags.dryRun = true;
|
|
36
|
+
else if (a === '--help' || a === '-h') flags.help = true;
|
|
37
|
+
else if (a === '--agent' || a === '-a') {
|
|
38
|
+
const id = argv[++i];
|
|
39
|
+
if (!id) throw usageError('--agent requires an id');
|
|
40
|
+
flags.agents.push(id);
|
|
41
|
+
} else if (a === '--project' || a === '-C') {
|
|
42
|
+
flags.project = argv[++i];
|
|
43
|
+
if (!flags.project) throw usageError('--project requires a path');
|
|
44
|
+
} else if (strict) {
|
|
45
|
+
throw usageError(`unknown option: ${a}`);
|
|
46
|
+
} else {
|
|
47
|
+
flags.rest.push(a);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (process.env.CI === 'true' || process.env.CI === '1') {
|
|
52
|
+
flags.ci = true;
|
|
53
|
+
flags.yes = true;
|
|
54
|
+
}
|
|
55
|
+
if (process.env.QA_DEBUG === '1' || process.env.QA_DEBUG === 'true') {
|
|
56
|
+
flags.debug = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return flags;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function wantsNonInteractive(flags) {
|
|
63
|
+
return Boolean(flags.yes || flags.ci || !process.stdin.isTTY || !process.stdout.isTTY);
|
|
64
|
+
}
|