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.
Files changed (232) hide show
  1. package/COMPATIBILITY.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/package.json +90 -0
  5. package/packages/installer/README.md +46 -0
  6. package/packages/installer/bin/qa.mjs +136 -0
  7. package/packages/installer/lib/agents/registry.mjs +209 -0
  8. package/packages/installer/lib/cli/commands.mjs +29 -0
  9. package/packages/installer/lib/cli/flags.mjs +64 -0
  10. package/packages/installer/lib/commands/doctor.mjs +190 -0
  11. package/packages/installer/lib/commands/install.mjs +291 -0
  12. package/packages/installer/lib/commands/onboard.mjs +163 -0
  13. package/packages/installer/lib/commands/repair.mjs +68 -0
  14. package/packages/installer/lib/commands/self-test.mjs +45 -0
  15. package/packages/installer/lib/commands/uninstall.mjs +167 -0
  16. package/packages/installer/lib/commands/update.mjs +69 -0
  17. package/packages/installer/lib/commands/verify.mjs +62 -0
  18. package/packages/installer/lib/constants.mjs +34 -0
  19. package/packages/installer/lib/core/bundle.mjs +124 -0
  20. package/packages/installer/lib/core/config.mjs +73 -0
  21. package/packages/installer/lib/core/conflict.mjs +29 -0
  22. package/packages/installer/lib/core/errors.mjs +29 -0
  23. package/packages/installer/lib/core/fs-safe.mjs +236 -0
  24. package/packages/installer/lib/core/hash.mjs +19 -0
  25. package/packages/installer/lib/core/lockfile.mjs +75 -0
  26. package/packages/installer/lib/core/logger.mjs +46 -0
  27. package/packages/installer/lib/core/manifest.mjs +89 -0
  28. package/packages/installer/lib/core/paths.mjs +74 -0
  29. package/packages/installer/lib/core/schema-validate.mjs +142 -0
  30. package/packages/installer/lib/core/skill-meta.mjs +75 -0
  31. package/packages/installer/lib/core/validate-install.mjs +152 -0
  32. package/packages/installer/lib/core/wrappers.mjs +91 -0
  33. package/packages/installer/lib/detect/environment.mjs +54 -0
  34. package/packages/installer/lib/detect/frameworks.mjs +94 -0
  35. package/packages/installer/lib/detect/project.mjs +126 -0
  36. package/packages/installer/lib/detect/recommend.mjs +85 -0
  37. package/packages/installer/lib/detect/scan.mjs +48 -0
  38. package/packages/installer/lib/ui/progress.mjs +32 -0
  39. package/packages/installer/lib/ui/theme.mjs +81 -0
  40. package/packages/installer/lib/version.mjs +47 -0
  41. package/packages/installer/package.json +28 -0
  42. package/packages/installer/schemas/qa-lock.schema.json +89 -0
  43. package/packages/installer/schemas/qa.config.schema.json +91 -0
  44. package/shared/analysis/lib/qa_analysis/__init__.py +11 -0
  45. package/shared/analysis/lib/qa_analysis/branding.json +9 -0
  46. package/shared/analysis/lib/qa_analysis/branding.py +175 -0
  47. package/shared/analysis/lib/qa_analysis/cli.py +129 -0
  48. package/shared/analysis/lib/qa_analysis/context.py +233 -0
  49. package/shared/analysis/lib/qa_analysis/contracts.py +158 -0
  50. package/shared/analysis/lib/qa_analysis/diff_guard.py +327 -0
  51. package/shared/analysis/lib/qa_analysis/discovery.py +113 -0
  52. package/shared/analysis/lib/qa_analysis/evidence.py +128 -0
  53. package/shared/analysis/lib/qa_analysis/har.py +58 -0
  54. package/shared/analysis/lib/qa_analysis/junit.py +80 -0
  55. package/shared/analysis/lib/qa_analysis/redaction.py +99 -0
  56. package/shared/analysis/lib/qa_analysis/taxonomy.py +98 -0
  57. package/shared/analysis/schemas/context.schema.json +82 -0
  58. package/shared/diagnostics/lib/qa_diagnostics/__init__.py +13 -0
  59. package/shared/diagnostics/lib/qa_diagnostics/cli.py +124 -0
  60. package/shared/diagnostics/lib/qa_diagnostics/engine.py +143 -0
  61. package/shared/diagnostics/lib/qa_diagnostics/internal_contracts.py +75 -0
  62. package/shared/diagnostics/lib/qa_diagnostics/prioritization.py +101 -0
  63. package/shared/diagnostics/lib/qa_diagnostics/repair.py +72 -0
  64. package/shared/diagnostics/lib/qa_diagnostics/root_cause.py +89 -0
  65. package/shared/diagnostics/lib/qa_diagnostics/timeline.py +71 -0
  66. package/shared/diagnostics/schemas/internal/analysis-result.schema.json +28 -0
  67. package/shared/diagnostics/schemas/internal/diagnosis.schema.json +55 -0
  68. package/shared/diagnostics/schemas/internal/execution-result-min.schema.json +34 -0
  69. package/shared/frameworks/cypress/lib/cypress_analysis.py +27 -0
  70. package/shared/frameworks/playwright/lib/playwright_analysis.py +167 -0
  71. package/shared/frameworks/registry.json +150 -0
  72. package/shared/frameworks/registry.mjs +37 -0
  73. package/shared/frameworks/registry.schema.json +74 -0
  74. package/shared/frameworks/selenium/lib/selenium_analysis.py +28 -0
  75. package/shared/frameworks/webdriverio/lib/webdriverio_analysis.py +24 -0
  76. package/shared/tooling/qa_tool.py +127 -0
  77. package/skills/README.md +31 -0
  78. package/skills/qa/README.md +19 -0
  79. package/skills/qa/SKILL.md +52 -0
  80. package/skills/qa/examples/routing.md +86 -0
  81. package/skills/qa/references/routing-map.md +33 -0
  82. package/skills/qa-api/README.md +19 -0
  83. package/skills/qa-api/SKILL.md +68 -0
  84. package/skills/qa-api/contracts/api-result.schema.json +51 -0
  85. package/skills/qa-api/examples/graphql-review.md +43 -0
  86. package/skills/qa-api/references/authentication.md +45 -0
  87. package/skills/qa-api/references/deterministic-tooling.md +128 -0
  88. package/skills/qa-api/references/evidence-and-reporting.md +66 -0
  89. package/skills/qa-api/references/graphql.md +44 -0
  90. package/skills/qa-api/references/rest.md +45 -0
  91. package/skills/qa-api/references/websocket.md +44 -0
  92. package/skills/qa-audit/README.md +19 -0
  93. package/skills/qa-audit/SKILL.md +70 -0
  94. package/skills/qa-audit/contracts/audit-result.schema.json +63 -0
  95. package/skills/qa-audit/examples/accessibility-audit.md +46 -0
  96. package/skills/qa-audit/references/accessibility.md +44 -0
  97. package/skills/qa-audit/references/deterministic-tooling.md +128 -0
  98. package/skills/qa-audit/references/evidence-and-reporting.md +66 -0
  99. package/skills/qa-audit/references/performance.md +44 -0
  100. package/skills/qa-audit/references/security.md +43 -0
  101. package/skills/qa-audit/references/visual-testing.md +44 -0
  102. package/skills/qa-debug/README.md +19 -0
  103. package/skills/qa-debug/SKILL.md +76 -0
  104. package/skills/qa-debug/contracts/debug-result.schema.json +121 -0
  105. package/skills/qa-debug/examples/failed-login.md +61 -0
  106. package/skills/qa-debug/examples/locator-break.md +59 -0
  107. package/skills/qa-debug/examples/network-timeout.md +61 -0
  108. package/skills/qa-debug/examples/successful-debug.md +62 -0
  109. package/skills/qa-debug/references/confidence-model.md +26 -0
  110. package/skills/qa-debug/references/deterministic-tooling.md +128 -0
  111. package/skills/qa-debug/references/diagnostic-engine.md +36 -0
  112. package/skills/qa-debug/references/evidence-and-reporting.md +66 -0
  113. package/skills/qa-debug/references/evidence-model.md +41 -0
  114. package/skills/qa-debug/references/failure-taxonomy.md +44 -0
  115. package/skills/qa-debug/references/finding-prioritization.md +43 -0
  116. package/skills/qa-debug/references/investigation-workflow.md +48 -0
  117. package/skills/qa-debug/references/recommendation-ranking.md +34 -0
  118. package/skills/qa-debug/references/root-cause-analysis.md +49 -0
  119. package/skills/qa-debug/references/timeline-builder.md +37 -0
  120. package/skills/qa-example/README.md +19 -0
  121. package/skills/qa-example/SKILL.md +60 -0
  122. package/skills/qa-example/contracts/self-check-report.schema.json +65 -0
  123. package/skills/qa-example/examples/self-check.md +53 -0
  124. package/skills/qa-example/references/example-domain.md +28 -0
  125. package/skills/qa-example/references/skill-format-notes.md +26 -0
  126. package/skills/qa-explore/README.md +22 -0
  127. package/skills/qa-explore/SKILL.md +87 -0
  128. package/skills/qa-explore/contracts/explore-result.schema.json +249 -0
  129. package/skills/qa-explore/examples/attached-test-cases.md +70 -0
  130. package/skills/qa-explore/examples/screenshot-proof-finding.md +50 -0
  131. package/skills/qa-explore/examples/url-smoke-explore.md +80 -0
  132. package/skills/qa-explore/references/accessibility.md +44 -0
  133. package/skills/qa-explore/references/api-replay.md +48 -0
  134. package/skills/qa-explore/references/browser-adapters.md +51 -0
  135. package/skills/qa-explore/references/evidence-and-reporting.md +66 -0
  136. package/skills/qa-explore/references/evidence-capture.md +54 -0
  137. package/skills/qa-explore/references/exploratory-qa.md +52 -0
  138. package/skills/qa-explore/references/finding-taxonomy.md +47 -0
  139. package/skills/qa-explore/references/performance.md +44 -0
  140. package/skills/qa-explore/references/pipeline.md +74 -0
  141. package/skills/qa-explore/references/report-pipeline.md +69 -0
  142. package/skills/qa-explore/references/security.md +43 -0
  143. package/skills/qa-explore/references/test-case-intake.md +44 -0
  144. package/skills/qa-fix/README.md +19 -0
  145. package/skills/qa-fix/SKILL.md +70 -0
  146. package/skills/qa-fix/contracts/fix-result.schema.json +132 -0
  147. package/skills/qa-fix/examples/repair-plan.md +56 -0
  148. package/skills/qa-fix/references/deterministic-tooling.md +128 -0
  149. package/skills/qa-fix/references/diagnostic-engine.md +36 -0
  150. package/skills/qa-fix/references/evidence-and-reporting.md +66 -0
  151. package/skills/qa-fix/references/repair-strategy.md +48 -0
  152. package/skills/qa-fix/references/root-cause-analysis.md +49 -0
  153. package/skills/qa-fix/references/suite-extension.md +73 -0
  154. package/skills/qa-flaky/README.md +19 -0
  155. package/skills/qa-flaky/SKILL.md +67 -0
  156. package/skills/qa-flaky/contracts/flaky-result.schema.json +62 -0
  157. package/skills/qa-flaky/examples/flaky-locator.md +46 -0
  158. package/skills/qa-flaky/references/deterministic-tooling.md +128 -0
  159. package/skills/qa-flaky/references/evidence-and-reporting.md +66 -0
  160. package/skills/qa-flaky/references/flakiness.md +48 -0
  161. package/skills/qa-flaky/references/retry.md +44 -0
  162. package/skills/qa-flaky/references/waiting-strategies.md +44 -0
  163. package/skills/qa-generate/README.md +42 -0
  164. package/skills/qa-generate/SKILL.md +83 -0
  165. package/skills/qa-generate/contracts/generation-result.schema.json +124 -0
  166. package/skills/qa-generate/examples/bootstrap-new-framework.md +24 -0
  167. package/skills/qa-generate/examples/extend-existing-suite.md +26 -0
  168. package/skills/qa-generate/references/code-style.md +29 -0
  169. package/skills/qa-generate/references/evidence-and-reporting.md +66 -0
  170. package/skills/qa-generate/references/framework-selection.md +88 -0
  171. package/skills/qa-generate/references/generation-strategy.md +48 -0
  172. package/skills/qa-generate/references/naming-conventions.md +27 -0
  173. package/skills/qa-generate/references/playwright-conventions.md +23 -0
  174. package/skills/qa-generate/references/playwright-generation.md +41 -0
  175. package/skills/qa-generate/references/playwright-project-discovery.md +42 -0
  176. package/skills/qa-generate/references/project-bootstrap.md +54 -0
  177. package/skills/qa-generate/references/repository-analysis.md +43 -0
  178. package/skills/qa-generate/references/suite-extension.md +73 -0
  179. package/skills/qa-generate/references/template-selection.md +48 -0
  180. package/skills/qa-generate/templates/playwright/api.ts +36 -0
  181. package/skills/qa-generate/templates/playwright/base-page.ts +14 -0
  182. package/skills/qa-generate/templates/playwright/data.ts +25 -0
  183. package/skills/qa-generate/templates/playwright/env.example +16 -0
  184. package/skills/qa-generate/templates/playwright/example.spec.ts +14 -0
  185. package/skills/qa-generate/templates/playwright/fixtures.ts +28 -0
  186. package/skills/qa-generate/templates/playwright/framework-readme.md +39 -0
  187. package/skills/qa-generate/templates/playwright/login.page.ts +26 -0
  188. package/skills/qa-generate/templates/playwright/playwright.config.ts +28 -0
  189. package/skills/qa-generate/templates/playwright/utils.ts +18 -0
  190. package/skills/qa-init/README.md +20 -0
  191. package/skills/qa-init/SKILL.md +72 -0
  192. package/skills/qa-init/examples/initialize-a-repo.md +91 -0
  193. package/skills/qa-init/references/detection-guide.md +76 -0
  194. package/skills/qa-init/references/deterministic-tooling.md +128 -0
  195. package/skills/qa-init/references/evidence-and-reporting.md +66 -0
  196. package/skills/qa-init/templates/context.md +68 -0
  197. package/skills/qa-report/README.md +19 -0
  198. package/skills/qa-report/SKILL.md +72 -0
  199. package/skills/qa-report/contracts/report-result.schema.json +186 -0
  200. package/skills/qa-report/examples/release-report.md +68 -0
  201. package/skills/qa-report/references/deterministic-tooling.md +128 -0
  202. package/skills/qa-report/references/diagnostic-engine.md +36 -0
  203. package/skills/qa-report/references/evidence-and-reporting.md +66 -0
  204. package/skills/qa-report/references/finding-prioritization.md +43 -0
  205. package/skills/qa-report/references/recommendation-ranking.md +34 -0
  206. package/skills/qa-report/references/report-aggregation.md +44 -0
  207. package/skills/qa-review/README.md +19 -0
  208. package/skills/qa-review/SKILL.md +58 -0
  209. package/skills/qa-review/contracts/review-result.schema.json +59 -0
  210. package/skills/qa-review/examples/suite-review.md +51 -0
  211. package/skills/qa-review/references/anti-patterns.md +49 -0
  212. package/skills/qa-review/references/assertion-patterns.md +45 -0
  213. package/skills/qa-review/references/evidence-and-reporting.md +66 -0
  214. package/skills/qa-review/references/fixtures.md +45 -0
  215. package/skills/qa-review/references/page-objects.md +45 -0
  216. package/skills/qa-run/README.md +22 -0
  217. package/skills/qa-run/SKILL.md +92 -0
  218. package/skills/qa-run/contracts/execution-plan.schema.json +132 -0
  219. package/skills/qa-run/contracts/execution-result.schema.json +204 -0
  220. package/skills/qa-run/examples/execute-playwright.md +89 -0
  221. package/skills/qa-run/examples/plan-a-run.md +83 -0
  222. package/skills/qa-run/references/artifact-collector.md +40 -0
  223. package/skills/qa-run/references/browser-launch.md +39 -0
  224. package/skills/qa-run/references/command-builder.md +39 -0
  225. package/skills/qa-run/references/deterministic-tooling.md +128 -0
  226. package/skills/qa-run/references/environment-detection.md +37 -0
  227. package/skills/qa-run/references/evidence-and-reporting.md +66 -0
  228. package/skills/qa-run/references/execution-strategy.md +54 -0
  229. package/skills/qa-run/references/playwright-artifacts.md +29 -0
  230. package/skills/qa-run/references/playwright-execution.md +48 -0
  231. package/skills/qa-run/references/playwright-project-discovery.md +42 -0
  232. 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
+ }