baldart 4.40.0 → 4.42.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 (32) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -2
  3. package/VERSION +1 -1
  4. package/framework/.claude/agents/REGISTRY.md +1 -1
  5. package/framework/.claude/agents/coder.md +4 -2
  6. package/framework/.claude/agents/qa-sentinel.md +10 -1
  7. package/framework/.claude/commands/qa.md +2 -0
  8. package/framework/.claude/skills/new/references/setup.md +19 -7
  9. package/framework/.claude/skills/toolchain-bootstrap/SKILL.md +127 -0
  10. package/framework/.claude/workflows/new-card-review.js +17 -2
  11. package/framework/.claude/workflows/new2.js +44 -3
  12. package/framework/agents/index.md +2 -0
  13. package/framework/agents/toolchain-protocol.md +80 -0
  14. package/framework/docs/TOOLCHAIN-LAYER.md +135 -0
  15. package/framework/scripts/validate-card-baseline.js +133 -3
  16. package/framework/templates/baldart.config.template.yml +40 -0
  17. package/framework/templates/ci/check-card-baseline.yml +0 -3
  18. package/package.json +1 -1
  19. package/src/commands/configure.js +81 -0
  20. package/src/commands/doctor.js +67 -0
  21. package/src/commands/update.js +12 -0
  22. package/src/utils/tool-currency.js +52 -0
  23. package/src/utils/toolchain-adapters/biome.js +92 -0
  24. package/src/utils/toolchain-adapters/eslint.js +39 -0
  25. package/src/utils/toolchain-adapters/husky.js +30 -0
  26. package/src/utils/toolchain-adapters/index.js +83 -0
  27. package/src/utils/toolchain-adapters/jest.js +34 -0
  28. package/src/utils/toolchain-adapters/lefthook.js +84 -0
  29. package/src/utils/toolchain-adapters/prettier.js +39 -0
  30. package/src/utils/toolchain-adapters/tsc.js +50 -0
  31. package/src/utils/toolchain-adapters/vitest.js +46 -0
  32. package/src/utils/toolchain-installer.js +233 -0
@@ -0,0 +1,92 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Biome toolchain adapter — format + lint + import organizer in one binary.
6
+ *
7
+ * Install mode: `npm-dev` (project devDependency, pinned `-E`). UNLIKE the LSP
8
+ * servers (which go global because Claude Code spawns them by name from `$PATH`),
9
+ * Biome is a project tool invoked via `npx --no-install biome …` — it must land in
10
+ * `node_modules/.bin`, not on the global `$PATH`. There is no global install case
11
+ * for the JS toolchain.
12
+ *
13
+ * Detection signal (in-scope): the project is JS/TS — a `package.json` exists, or a
14
+ * `tsconfig.json`/`jsconfig.json`, or loose `.ts`/`.js` sources. Biome installs as a
15
+ * devDep, so a `package.json` is required for the actual install (the installer
16
+ * guards on that separately).
17
+ *
18
+ * `replaces` lists the incumbent tools Biome supersedes — drives the migration
19
+ * proposal in `configure` (never an automatic replacement).
20
+ */
21
+ class BiomeAdapter {
22
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
23
+
24
+ get name() { return 'biome'; }
25
+ get label() { return 'Biome (format + lint + import organizer)'; }
26
+ get binary() { return 'biome'; }
27
+ get installMode() { return 'npm-dev'; }
28
+ get npmPackage() { return '@biomejs/biome'; }
29
+
30
+ /** devDep install, pinned exact (`-E`) so the toolchain is reproducible. */
31
+ installCommand() { return 'npm install --save-dev --save-exact @biomejs/biome'; }
32
+
33
+ /** Functional probe: resolves the devDep the SAME way agents invoke it (npx). */
34
+ verifyCommand() { return 'npx --no-install biome --version'; }
35
+
36
+ /** Config file Biome reads; `initConfig` writes it only when absent. */
37
+ get configFile() { return 'biome.json'; }
38
+
39
+ /**
40
+ * The literal commands agents run (read from toolchain.commands.* in the
41
+ * consumer's baldart.config.yml). Keys map onto the config block; Biome owns
42
+ * `lint` + `format` (it is the linter, formatter, AND import organizer).
43
+ */
44
+ commands() {
45
+ return {
46
+ lint: 'npx biome check .',
47
+ format: 'npx biome format --write .',
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Write a default biome.json ONLY when none exists (never overwrite — the
53
+ * non-destructive invariant). Returns `{ status: 'written'|'skipped', reason?, path }`.
54
+ * Never throws.
55
+ */
56
+ initConfig(cwd = this.cwd) {
57
+ const target = path.join(cwd, this.configFile);
58
+ if (fs.existsSync(target)) {
59
+ return { status: 'skipped', reason: 'exists', path: target };
60
+ }
61
+ const defaultConfig = {
62
+ $schema: 'https://biomejs.dev/schemas/2.0.0/schema.json',
63
+ vcs: { enabled: true, clientKind: 'git', useIgnoreFile: true },
64
+ files: { ignoreUnknown: true },
65
+ formatter: { enabled: true, indentStyle: 'space', indentWidth: 2 },
66
+ linter: { enabled: true, rules: { recommended: true } },
67
+ assist: { actions: { source: { organizeImports: 'on' } } },
68
+ };
69
+ try {
70
+ fs.writeFileSync(target, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
71
+ return { status: 'written', path: target };
72
+ } catch (err) {
73
+ return { status: 'skipped', reason: (err.message || '').split('\n')[0], path: target };
74
+ }
75
+ }
76
+
77
+ /** Incumbent tools Biome supersedes — drives the migration proposal. */
78
+ static get replaces() { return ['eslint', 'prettier']; }
79
+
80
+ /** Autodetect whether the JS/TS toolchain is in scope for the project. */
81
+ static detect(cwd = process.cwd()) {
82
+ const markers = ['package.json', 'tsconfig.json', 'jsconfig.json'];
83
+ for (const m of markers) {
84
+ if (fs.existsSync(path.join(cwd, m))) return true;
85
+ }
86
+ try {
87
+ return fs.readdirSync(cwd).some((e) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(e));
88
+ } catch { return false; }
89
+ }
90
+ }
91
+
92
+ module.exports = BiomeAdapter;
@@ -0,0 +1,39 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * ESLint INCUMBENT detector — detection only, no install.
6
+ *
7
+ * BALDART never installs or removes ESLint; this adapter exists so `configure`
8
+ * can detect an existing ESLint setup and offer a (manual, never automatic)
9
+ * migration to Biome — never clobbering the user's config. `replacedBy` names
10
+ * the curated tool that would supersede it.
11
+ */
12
+ class EslintDetector {
13
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
14
+
15
+ get name() { return 'eslint'; }
16
+ get label() { return 'ESLint'; }
17
+ get replacedBy() { return 'biome'; }
18
+
19
+ static detect(cwd = process.cwd()) {
20
+ const markers = [
21
+ '.eslintrc', '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json',
22
+ '.eslintrc.yml', '.eslintrc.yaml', 'eslint.config.js', 'eslint.config.mjs',
23
+ 'eslint.config.cjs', 'eslint.config.ts',
24
+ ];
25
+ for (const m of markers) {
26
+ if (fs.existsSync(path.join(cwd, m))) return true;
27
+ }
28
+ try {
29
+ const pkgPath = path.join(cwd, 'package.json');
30
+ if (!fs.existsSync(pkgPath)) return false;
31
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
32
+ if (pkg.eslintConfig) return true;
33
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
34
+ return Boolean(allDeps.eslint);
35
+ } catch { return false; }
36
+ }
37
+ }
38
+
39
+ module.exports = EslintDetector;
@@ -0,0 +1,30 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * husky INCUMBENT detector — detection only, no install. When present, the
6
+ * Lefthook migration stays MANUAL: BALDART never overwrites `.husky/` or runs
7
+ * `lefthook install` over an existing husky setup (the hook-handoff is the
8
+ * user's call). Drives the migration proposal in `configure`.
9
+ */
10
+ class HuskyDetector {
11
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
12
+
13
+ get name() { return 'husky'; }
14
+ get label() { return 'husky'; }
15
+ get replacedBy() { return 'lefthook'; }
16
+
17
+ static detect(cwd = process.cwd()) {
18
+ if (fs.existsSync(path.join(cwd, '.husky'))) return true;
19
+ try {
20
+ const pkgPath = path.join(cwd, 'package.json');
21
+ if (!fs.existsSync(pkgPath)) return false;
22
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
23
+ if (pkg.husky) return true;
24
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
25
+ return Boolean(allDeps.husky);
26
+ } catch { return false; }
27
+ }
28
+ }
29
+
30
+ module.exports = HuskyDetector;
@@ -0,0 +1,83 @@
1
+ const BiomeAdapter = require('./biome');
2
+ const VitestAdapter = require('./vitest');
3
+ const TscAdapter = require('./tsc');
4
+ const LefthookAdapter = require('./lefthook');
5
+ const EslintDetector = require('./eslint');
6
+ const PrettierDetector = require('./prettier');
7
+ const JestDetector = require('./jest');
8
+ const HuskyDetector = require('./husky');
9
+
10
+ /**
11
+ * Toolchain adapter registry.
12
+ *
13
+ * Two registries, two questions:
14
+ * - REGISTRY — curated tools BALDART can INSTALL (Biome, …; later Vitest,
15
+ * tsc, Lefthook, and cross-language Ruff/gofmt). Each exports a
16
+ * class shaped like BiomeAdapter (name, label, binary, installMode,
17
+ * npmPackage, installCommand, verifyCommand, configFile, commands(),
18
+ * initConfig(cwd), static replaces, static detect(cwd)).
19
+ * - INCUMBENTS — tools BALDART only DETECTS (ESLint, Prettier, …; later Jest,
20
+ * husky). Detection-only — never installed or removed. Drives the
21
+ * non-destructive migration proposal in `configure`.
22
+ *
23
+ * Adding a new curated tool:
24
+ * 1. Create `src/utils/toolchain-adapters/<name>.js` (BiomeAdapter shape).
25
+ * 2. Add it to REGISTRY below.
26
+ * Adding a new incumbent detector: same, into INCUMBENTS.
27
+ *
28
+ * Adapters describe HOW to install/verify a tool; they do NOT run the gates
29
+ * themselves. Agents read the resolved commands from `toolchain.commands.*` in
30
+ * baldart.config.yml at runtime (see framework/agents/toolchain-protocol.md).
31
+ */
32
+ const REGISTRY = {
33
+ biome: BiomeAdapter,
34
+ vitest: VitestAdapter,
35
+ tsc: TscAdapter,
36
+ lefthook: LefthookAdapter,
37
+ };
38
+
39
+ const INCUMBENTS = {
40
+ eslint: EslintDetector,
41
+ prettier: PrettierDetector,
42
+ jest: JestDetector,
43
+ husky: HuskyDetector,
44
+ };
45
+
46
+ function listAdapters() { return Object.keys(REGISTRY); }
47
+
48
+ function getAdapter(name, cwd) {
49
+ const Cls = REGISTRY[name];
50
+ if (!Cls) throw new Error(`Unknown toolchain adapter: ${name}. Available: ${listAdapters().join(', ')}`);
51
+ return new Cls(cwd);
52
+ }
53
+
54
+ /**
55
+ * Probe the cwd for every curated tool and return the names of adapters whose
56
+ * static detect() fires (i.e. the tool is in scope for this project — a JS/TS
57
+ * project for Biome). Pure: no install side effects.
58
+ */
59
+ function detectAll(cwd = process.cwd()) {
60
+ return Object.entries(REGISTRY)
61
+ .filter(([, Cls]) => {
62
+ try { return Cls.detect(cwd); } catch { return false; }
63
+ })
64
+ .map(([name]) => name);
65
+ }
66
+
67
+ /**
68
+ * Probe the cwd for incumbent tools already in use. Returns
69
+ * `[{ name, label, replacedBy }]` so `configure` can propose a migration.
70
+ * Pure: no side effects, never throws.
71
+ */
72
+ function detectIncumbents(cwd = process.cwd()) {
73
+ return Object.entries(INCUMBENTS)
74
+ .filter(([, Cls]) => {
75
+ try { return Cls.detect(cwd); } catch { return false; }
76
+ })
77
+ .map(([name, Cls]) => {
78
+ const inst = new Cls(cwd);
79
+ return { name, label: inst.label, replacedBy: inst.replacedBy };
80
+ });
81
+ }
82
+
83
+ module.exports = { REGISTRY, INCUMBENTS, listAdapters, getAdapter, detectAll, detectIncumbents };
@@ -0,0 +1,34 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Jest INCUMBENT detector — detection only, no install. Lets `configure`
6
+ * propose a (manual) migration to Vitest without ever touching the Jest setup.
7
+ */
8
+ class JestDetector {
9
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
10
+
11
+ get name() { return 'jest'; }
12
+ get label() { return 'Jest'; }
13
+ get replacedBy() { return 'vitest'; }
14
+
15
+ static detect(cwd = process.cwd()) {
16
+ const markers = [
17
+ 'jest.config.js', 'jest.config.cjs', 'jest.config.mjs',
18
+ 'jest.config.ts', 'jest.config.json',
19
+ ];
20
+ for (const m of markers) {
21
+ if (fs.existsSync(path.join(cwd, m))) return true;
22
+ }
23
+ try {
24
+ const pkgPath = path.join(cwd, 'package.json');
25
+ if (!fs.existsSync(pkgPath)) return false;
26
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
27
+ if (pkg.jest) return true;
28
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
29
+ return Boolean(allDeps.jest);
30
+ } catch { return false; }
31
+ }
32
+ }
33
+
34
+ module.exports = JestDetector;
@@ -0,0 +1,84 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * Lefthook toolchain adapter — the curated pre-commit hook runner.
7
+ *
8
+ * `npm-dev` install. Owns the `pre-commit` hook (Graphify owns `post-commit`, so
9
+ * no collision). `replaces` = ['husky'] so a husky project routes to the manual
10
+ * migration proposal — BALDART NEVER overwrites `.husky/`.
11
+ *
12
+ * `initConfig` writes a minimal `lefthook.yml` only when absent (non-destructive).
13
+ * `postInstall` registers the git hooks via `npx lefthook install` — run by the
14
+ * installer AFTER the devDep + config land, and only on the clean-install path
15
+ * (never when husky is the incumbent).
16
+ *
17
+ * Contributes no `commands()` gate — it is plumbing, not a quality gate the
18
+ * agents invoke directly.
19
+ */
20
+ class LefthookAdapter {
21
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
22
+
23
+ get name() { return 'lefthook'; }
24
+ get label() { return 'Lefthook (pre-commit hooks)'; }
25
+ get binary() { return 'lefthook'; }
26
+ get installMode() { return 'npm-dev'; }
27
+ get npmPackage() { return 'lefthook'; }
28
+
29
+ installCommand() { return 'npm install --save-dev lefthook'; }
30
+ verifyCommand() { return 'npx --no-install lefthook version'; }
31
+
32
+ get configFile() { return 'lefthook.yml'; }
33
+
34
+ /** Plumbing, not a gate — contributes no toolchain.commands.* entry. */
35
+ commands() { return {}; }
36
+
37
+ /**
38
+ * Write a minimal lefthook.yml (pre-commit → Biome on staged files) only when
39
+ * absent. Never overwrites. Returns `{ status, reason?, path }`. Never throws.
40
+ */
41
+ initConfig(cwd = this.cwd) {
42
+ const target = path.join(cwd, this.configFile);
43
+ if (fs.existsSync(target)) return { status: 'skipped', reason: 'exists', path: target };
44
+ const yml = [
45
+ '# Managed by BALDART toolchain layer — customize freely.',
46
+ '# Runs Biome over staged files before each commit. Edit/remove as needed.',
47
+ 'pre-commit:',
48
+ ' parallel: true',
49
+ ' commands:',
50
+ ' biome:',
51
+ ' glob: "*.{js,ts,jsx,tsx,json,jsonc,css}"',
52
+ ' run: npx biome check --no-errors-on-unmatched {staged_files}',
53
+ '',
54
+ ].join('\n');
55
+ try {
56
+ fs.writeFileSync(target, yml, 'utf8');
57
+ return { status: 'written', path: target };
58
+ } catch (err) {
59
+ return { status: 'skipped', reason: (err.message || '').split('\n')[0], path: target };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Register the git hooks. Run by the installer after install+config, only on
65
+ * the clean-install path. Returns `{ ok, error? }`. Never throws.
66
+ */
67
+ postInstall(cwd = this.cwd) {
68
+ try {
69
+ execSync('npx --no-install lefthook install', { cwd, stdio: 'pipe', timeout: 60000 });
70
+ return { ok: true };
71
+ } catch (err) {
72
+ return { ok: false, error: (err.message || '').split('\n')[0] };
73
+ }
74
+ }
75
+
76
+ static get replaces() { return ['husky']; }
77
+
78
+ /** In scope for any JS/TS project with git (a project benefits from hooks). */
79
+ static detect(cwd = process.cwd()) {
80
+ return fs.existsSync(path.join(cwd, 'package.json'));
81
+ }
82
+ }
83
+
84
+ module.exports = LefthookAdapter;
@@ -0,0 +1,39 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Prettier INCUMBENT detector — detection only, no install.
6
+ *
7
+ * Same contract as the ESLint detector: BALDART never installs or removes
8
+ * Prettier; this lets `configure` detect an existing formatter and offer a
9
+ * manual migration to Biome (which subsumes formatting), never overwriting
10
+ * the user's config.
11
+ */
12
+ class PrettierDetector {
13
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
14
+
15
+ get name() { return 'prettier'; }
16
+ get label() { return 'Prettier'; }
17
+ get replacedBy() { return 'biome'; }
18
+
19
+ static detect(cwd = process.cwd()) {
20
+ const markers = [
21
+ '.prettierrc', '.prettierrc.js', '.prettierrc.cjs', '.prettierrc.json',
22
+ '.prettierrc.yml', '.prettierrc.yaml', '.prettierrc.toml',
23
+ 'prettier.config.js', 'prettier.config.cjs', 'prettier.config.mjs',
24
+ ];
25
+ for (const m of markers) {
26
+ if (fs.existsSync(path.join(cwd, m))) return true;
27
+ }
28
+ try {
29
+ const pkgPath = path.join(cwd, 'package.json');
30
+ if (!fs.existsSync(pkgPath)) return false;
31
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
32
+ if (pkg.prettier) return true;
33
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
34
+ return Boolean(allDeps.prettier);
35
+ } catch { return false; }
36
+ }
37
+ }
38
+
39
+ module.exports = PrettierDetector;
@@ -0,0 +1,50 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * TypeScript type-checker (tsc) toolchain adapter.
6
+ *
7
+ * `npm-dev` install of the `typescript` package (often already present). The
8
+ * gate is `npx tsc --noEmit`. `initConfig` is a NO-OP — BALDART NEVER creates or
9
+ * touches `tsconfig.json` (it is deeply project-specific and the #1 data-loss
10
+ * risk). In scope ONLY for TypeScript projects (a tsconfig or a typescript dep);
11
+ * a plain-JS project is not offered tsc. No incumbent (`replaces` empty).
12
+ */
13
+ class TscAdapter {
14
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
15
+
16
+ get name() { return 'tsc'; }
17
+ get label() { return 'TypeScript type-checker (tsc)'; }
18
+ get binary() { return 'tsc'; }
19
+ get installMode() { return 'npm-dev'; }
20
+ get npmPackage() { return 'typescript'; }
21
+
22
+ installCommand() { return 'npm install --save-dev typescript'; }
23
+ verifyCommand() { return 'npx --no-install tsc --version'; }
24
+
25
+ /** Never managed by BALDART — tsconfig.json is project-owned. */
26
+ get configFile() { return null; }
27
+
28
+ commands() {
29
+ return { typecheck: 'npx tsc --noEmit' };
30
+ }
31
+
32
+ initConfig() { return { status: 'noop' }; }
33
+
34
+ static get replaces() { return []; }
35
+
36
+ /** In scope ONLY for TypeScript projects. */
37
+ static detect(cwd = process.cwd()) {
38
+ if (fs.existsSync(path.join(cwd, 'tsconfig.json'))) return true;
39
+ if (fs.existsSync(path.join(cwd, 'jsconfig.json'))) return true;
40
+ try {
41
+ const pkgPath = path.join(cwd, 'package.json');
42
+ if (!fs.existsSync(pkgPath)) return false;
43
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
44
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
45
+ return Boolean(allDeps.typescript);
46
+ } catch { return false; }
47
+ }
48
+ }
49
+
50
+ module.exports = TscAdapter;
@@ -0,0 +1,46 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Vitest toolchain adapter — the curated test runner.
6
+ *
7
+ * `npm-dev` install. No config is scaffolded: Vitest runs with sensible defaults
8
+ * and many projects configure it inside an existing `vite.config.*`, so writing a
9
+ * standalone `vitest.config.ts` would risk a conflict — `initConfig` is a no-op
10
+ * (non-destructive). `replaces` = ['jest'] so a Jest project routes to the
11
+ * migration proposal instead of an automatic install alongside Jest.
12
+ */
13
+ class VitestAdapter {
14
+ constructor(cwd = process.cwd()) { this.cwd = cwd; }
15
+
16
+ get name() { return 'vitest'; }
17
+ get label() { return 'Vitest (test runner)'; }
18
+ get binary() { return 'vitest'; }
19
+ get installMode() { return 'npm-dev'; }
20
+ get npmPackage() { return 'vitest'; }
21
+
22
+ installCommand() { return 'npm install --save-dev vitest'; }
23
+ verifyCommand() { return 'npx --no-install vitest --version'; }
24
+
25
+ /** No standalone config — Vitest defaults + optional vite.config integration. */
26
+ get configFile() { return null; }
27
+
28
+ commands() {
29
+ return {
30
+ test: 'npx vitest run',
31
+ test_related: 'npx vitest related --run',
32
+ };
33
+ }
34
+
35
+ /** Non-destructive: never scaffolds a config (avoids clobbering vite.config). */
36
+ initConfig() { return { status: 'noop' }; }
37
+
38
+ static get replaces() { return ['jest']; }
39
+
40
+ /** In scope for any JS/TS project (a project needs a test runner). */
41
+ static detect(cwd = process.cwd()) {
42
+ return fs.existsSync(path.join(cwd, 'package.json'));
43
+ }
44
+ }
45
+
46
+ module.exports = VitestAdapter;