@nolrm/contextkit 0.13.4 → 0.13.7

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.
@@ -1,9 +1,8 @@
1
1
  const chalk = require('chalk');
2
- const ora = require('ora');
3
2
  const inquirer = require('inquirer');
4
3
  const fs = require('fs-extra');
5
4
  const path = require('path');
6
- const axios = require('axios');
5
+ const childProcess = require('child_process');
7
6
 
8
7
  const DownloadManager = require('../utils/download');
9
8
  const ProjectDetector = require('../utils/project-detector');
@@ -22,7 +21,7 @@ class InstallCommand {
22
21
 
23
22
  async install(options = {}) {
24
23
  // Migrate legacy .vibe-kit/ directory
25
- if (await fs.pathExists('.vibe-kit') && !await fs.pathExists('.contextkit')) {
24
+ if ((await fs.pathExists('.vibe-kit')) && !(await fs.pathExists('.contextkit'))) {
26
25
  console.log(chalk.yellow('Found legacy .vibe-kit/ directory'));
27
26
  console.log(chalk.yellow('Renaming to .contextkit/...'));
28
27
  await fs.move('.vibe-kit', '.contextkit');
@@ -41,8 +40,12 @@ class InstallCommand {
41
40
  const hasGit = await this._findFileUpward('.git', 3);
42
41
  const hasPkg = await fs.pathExists('package.json');
43
42
  if (!hasGit && !hasPkg) {
44
- console.log(chalk.yellow('⚠️ No project detected (no .git or package.json found in this directory).'));
45
- console.log(chalk.yellow(' Make sure you are inside your project before running `ck install`.'));
43
+ console.log(
44
+ chalk.yellow('⚠️ No project detected (no .git or package.json found in this directory).')
45
+ );
46
+ console.log(
47
+ chalk.yellow(' Make sure you are inside your project before running `ck install`.')
48
+ );
46
49
  console.log('');
47
50
  }
48
51
 
@@ -87,7 +90,9 @@ class InstallCommand {
87
90
  const { shouldContinue } = await this.promptReinstall();
88
91
  if (!shouldContinue) {
89
92
  console.log(chalk.yellow('⏭️ Installation cancelled'));
90
- console.log(chalk.dim('💡 To get the latest command and squad files, run: ') + chalk.cyan('ck update'));
93
+ console.log(
94
+ chalk.dim('💡 To get the latest command and squad files, run: ') + chalk.cyan('ck update')
95
+ );
91
96
  return;
92
97
  }
93
98
  }
@@ -114,6 +119,11 @@ class InstallCommand {
114
119
  await this.gitHooksManager.installHooks(packageManager, hookChoices);
115
120
  }
116
121
 
122
+ // Offer quality tooling scaffold for Node.js projects with pre-push enabled
123
+ if (hookChoices.prePush && !options.nonInteractive) {
124
+ await this.promptQualityTooling(packageManager, options);
125
+ }
126
+
117
127
  // Ask about GitHub Actions CI squad workflow
118
128
  let squadCi = false;
119
129
  if (!options.nonInteractive) {
@@ -151,7 +161,11 @@ class InstallCommand {
151
161
 
152
162
  if (!integration) {
153
163
  console.log(chalk.red(`❌ Unknown platform: ${platform}`));
154
- console.log(chalk.yellow('💡 Available: claude, cursor, copilot, codex, opencode, gemini, aider, continue, windsurf'));
164
+ console.log(
165
+ chalk.yellow(
166
+ '💡 Available: claude, cursor, copilot, codex, opencode, gemini, aider, continue, windsurf'
167
+ )
168
+ );
155
169
  return;
156
170
  }
157
171
 
@@ -161,7 +175,6 @@ class InstallCommand {
161
175
  console.log(chalk.green(`🎉 ${integration.displayName} integration installed!`));
162
176
 
163
177
  integration.showUsage();
164
-
165
178
  } catch (error) {
166
179
  console.log(chalk.red(`❌ Failed to install ${platform} integration`));
167
180
  console.log(chalk.yellow(error.message));
@@ -186,30 +199,32 @@ class InstallCommand {
186
199
  type: 'confirm',
187
200
  name: 'shouldContinue',
188
201
  message: 'ContextKit is already installed. Do you want to reinstall?',
189
- default: false
190
- }
202
+ default: false,
203
+ },
191
204
  ]);
192
205
  return { shouldContinue };
193
206
  }
194
207
 
195
208
  async promptPlatformChoice() {
196
- const { platform } = await inquirer.prompt([{
197
- type: 'list',
198
- name: 'platform',
199
- message: 'Which AI tool are you using?',
200
- choices: [
201
- { name: 'Claude Code', value: 'claude' },
202
- { name: 'Cursor', value: 'cursor' },
203
- { name: 'Copilot (GitHub)', value: 'copilot' },
204
- { name: 'Codex (OpenAI)', value: 'codex' },
205
- { name: 'OpenCode', value: 'opencode' },
206
- { name: 'Gemini', value: 'gemini' },
207
- { name: 'Windsurf', value: 'windsurf' },
208
- { name: 'Aider', value: 'aider' },
209
- { name: 'Continue', value: 'continue' },
210
- { name: 'Skip (base only)', value: null },
211
- ]
212
- }]);
209
+ const { platform } = await inquirer.prompt([
210
+ {
211
+ type: 'list',
212
+ name: 'platform',
213
+ message: 'Which AI tool are you using?',
214
+ choices: [
215
+ { name: 'Claude Code', value: 'claude' },
216
+ { name: 'Cursor', value: 'cursor' },
217
+ { name: 'Copilot (GitHub)', value: 'copilot' },
218
+ { name: 'Codex (OpenAI)', value: 'codex' },
219
+ { name: 'OpenCode', value: 'opencode' },
220
+ { name: 'Gemini', value: 'gemini' },
221
+ { name: 'Windsurf', value: 'windsurf' },
222
+ { name: 'Aider', value: 'aider' },
223
+ { name: 'Continue', value: 'continue' },
224
+ { name: 'Skip (base only)', value: null },
225
+ ],
226
+ },
227
+ ]);
213
228
  return platform;
214
229
  }
215
230
 
@@ -257,7 +272,9 @@ class InstallCommand {
257
272
  `${this.repoUrl}/templates/github-actions/squad-issue.yml`,
258
273
  '.github/workflows/squad-issue.yml'
259
274
  );
260
- console.log(chalk.green('✅ CI squad workflow installed → .github/workflows/squad-issue.yml'));
275
+ console.log(
276
+ chalk.green('✅ CI squad workflow installed → .github/workflows/squad-issue.yml')
277
+ );
261
278
  } catch (error) {
262
279
  console.log(chalk.yellow('⚠️ Could not install CI squad workflow:'), error.message);
263
280
  }
@@ -270,7 +287,7 @@ class InstallCommand {
270
287
  return { prePush: false, commitMsg: false };
271
288
  }
272
289
 
273
- if (!await fs.pathExists('.git')) {
290
+ if (!(await fs.pathExists('.git'))) {
274
291
  console.log(chalk.yellow('⚠️ Skipping Git hooks setup (not a git repository)'));
275
292
  return { prePush: false, commitMsg: false };
276
293
  }
@@ -291,8 +308,8 @@ class InstallCommand {
291
308
  type: 'confirm',
292
309
  name: 'prePush',
293
310
  message: 'Enable pre-push hook? (runs quality checks before push)',
294
- default: false
295
- }
311
+ default: false,
312
+ },
296
313
  ]);
297
314
 
298
315
  if (prePush) {
@@ -314,8 +331,8 @@ class InstallCommand {
314
331
  type: 'confirm',
315
332
  name: 'commitMsg',
316
333
  message: 'Enable commit-msg hook? (enforces conventional commit format)',
317
- default: false
318
- }
334
+ default: false,
335
+ },
319
336
  ]);
320
337
 
321
338
  if (commitMsg) {
@@ -336,7 +353,7 @@ class InstallCommand {
336
353
 
337
354
  async createDirectoryStructure() {
338
355
  console.log(chalk.blue('📁 Creating structure...'));
339
-
356
+
340
357
  const directories = [
341
358
  '.contextkit/standards',
342
359
  '.contextkit/standards/code-style', // Granular code style files
@@ -350,7 +367,7 @@ class InstallCommand {
350
367
  '.contextkit/templates',
351
368
  '.contextkit/scripts',
352
369
  '.contextkit/integrations',
353
- '.contextkit/policies' // Policy enforcement configs
370
+ '.contextkit/policies', // Policy enforcement configs
354
371
  ];
355
372
 
356
373
  for (const dir of directories) {
@@ -393,7 +410,7 @@ Use conditional loading tags:
393
410
  [TypeScript-specific content]
394
411
  \`\`\`
395
412
  `,
396
-
413
+
397
414
  'standards/testing.md': `# Testing Standards
398
415
 
399
416
  <!-- Content will be generated by running: /analyze -->
@@ -428,7 +445,7 @@ describe("ComponentName", () => {
428
445
  - Consistent organization across all test files
429
446
  - Quick debugging and maintenance
430
447
  `,
431
-
448
+
432
449
  'standards/architecture.md': `# Architecture
433
450
 
434
451
  ## Documentation Levels
@@ -476,7 +493,7 @@ Documentation for a single component or a small, cohesive set of components.
476
493
  Your architecture patterns will be documented based on your project structure and organization.
477
494
 
478
495
  Run \`/analyze\` to generate this content.`,
479
-
496
+
480
497
  'standards/ai-guidelines.md': `# AI Guidelines
481
498
 
482
499
  <!-- Content will be generated by running: /analyze -->
@@ -484,14 +501,14 @@ Run \`/analyze\` to generate this content.`,
484
501
  Guidelines for AI assistance will be defined based on your project's needs and patterns.
485
502
 
486
503
  Run \`/analyze\` to generate this content.`,
487
-
504
+
488
505
  'standards/workflows.md': `# Workflows
489
506
 
490
507
  <!-- Content will be generated by running: /analyze -->
491
508
 
492
509
  Development workflows and processes will be documented based on your project's practices.
493
510
 
494
- Run \`/analyze\` to generate this content.`
511
+ Run \`/analyze\` to generate this content.`,
495
512
  };
496
513
 
497
514
  // Create granular code-style skeleton files
@@ -562,7 +579,7 @@ This file is loaded when HTML-related tasks are detected:
562
579
  <!-- when:html -->
563
580
  [HTML-specific content]
564
581
  \`\`\`
565
- `
582
+ `,
566
583
  };
567
584
 
568
585
  for (const [relativePath, content] of Object.entries(skeletonFiles)) {
@@ -688,7 +705,7 @@ Main paths through this feature:
688
705
  ## Notes
689
706
 
690
707
  Any design decisions, trade-offs, or open questions to resolve before coding.
691
- `
708
+ `,
692
709
  };
693
710
 
694
711
  for (const [relativePath, content] of Object.entries(skeletonFiles)) {
@@ -696,22 +713,22 @@ Any design decisions, trade-offs, or open questions to resolve before coding.
696
713
  }
697
714
  }
698
715
 
699
- async downloadFiles(projectType, options = {}) {
716
+ async downloadFiles(projectType, _options = {}) {
700
717
  try {
701
718
  // Create skeleton standards files (will be customized by analyze)
702
719
  console.log(chalk.blue('📝 Creating skeleton standards files...'));
703
720
  await this.createSkeletonStandards();
704
-
721
+
705
722
  console.log(chalk.green('✅ Skeleton files created'));
706
723
  console.log(chalk.yellow('💡 Run: /analyze to generate content based on your codebase'));
707
724
  console.log('');
708
-
725
+
709
726
  // Download base files
710
727
  await this.downloadManager.downloadFile(
711
728
  `${this.repoUrl}/standards/README.md`,
712
729
  '.contextkit/standards/README.md'
713
730
  );
714
-
731
+
715
732
  // Download the actual glossary (keep it as-is, universal across all projects)
716
733
  await this.downloadManager.downloadFile(
717
734
  `${this.repoUrl}/standards/glossary.md`,
@@ -982,7 +999,7 @@ claude "read .contextkit/standards/README.md .contextkit/standards/glossary.md a
982
999
  claude "read .contextkit/product/mission-lite.md .contextkit/standards/code-style.md and create a feature"
983
1000
  \`\`\`
984
1001
  `;
985
-
1002
+
986
1003
  await fs.writeFile('.contextkit/context.md', context);
987
1004
  await fs.writeFile('.contextkit/CONTEXT.md', context); // Keep uppercase for backward compatibility
988
1005
 
@@ -993,14 +1010,14 @@ claude "read .contextkit/product/mission-lite.md .contextkit/standards/code-styl
993
1010
 
994
1011
  CONTEXT_FILE=".contextkit/context.md"
995
1012
  AI_TOOL="\${AI_TOOL:-aider}"
996
- PROMPT="\$@"
1013
+ PROMPT="$@"
997
1014
 
998
1015
  if [ ! -f "$CONTEXT_FILE" ]; then
999
1016
  echo "❌ ContextKit not initialized. Run: contextkit install"
1000
1017
  exit 1
1001
1018
  fi
1002
1019
 
1003
- CONTEXT=\$(cat "$CONTEXT_FILE")
1020
+ CONTEXT=$(cat "$CONTEXT_FILE")
1004
1021
 
1005
1022
  case "$AI_TOOL" in
1006
1023
  "aider")
@@ -1021,7 +1038,7 @@ esac
1021
1038
 
1022
1039
  await fs.writeFile('.contextkit/scripts/ai-cli.sh', cliScript);
1023
1040
  await fs.chmod('.contextkit/scripts/ai-cli.sh', '755');
1024
-
1041
+
1025
1042
  console.log(chalk.green('✅ CLI helpers installed'));
1026
1043
  }
1027
1044
 
@@ -1046,7 +1063,7 @@ esac
1046
1063
  components: 'src/components',
1047
1064
  tests: 'src/__tests__',
1048
1065
  stories: 'src/stories',
1049
- docs: 'docs'
1066
+ docs: 'docs',
1050
1067
  },
1051
1068
  commands: {
1052
1069
  analyze: '@.contextkit/commands/analyze.md',
@@ -1057,16 +1074,19 @@ esac
1057
1074
  add_docs: '@.contextkit/commands/add-documentation.md',
1058
1075
  quality_check: '@.contextkit/commands/quality-check.md',
1059
1076
  create_component: '@.contextkit/commands/create-component.md',
1060
- create_feature: '@.contextkit/commands/create-feature.md'
1061
- }
1077
+ create_feature: '@.contextkit/commands/create-feature.md',
1078
+ },
1062
1079
  };
1063
1080
 
1064
1081
  const now = new Date().toISOString();
1065
- const isMonorepo = await fs.pathExists('packages') || await fs.pathExists('apps') ||
1066
- (await fs.pathExists('package.json') &&
1067
- (await fs.readJson('package.json').catch(() => ({}))).workspaces);
1068
-
1069
- await fs.writeFile('.contextkit/config.yml',
1082
+ const isMonorepo =
1083
+ (await fs.pathExists('packages')) ||
1084
+ (await fs.pathExists('apps')) ||
1085
+ ((await fs.pathExists('package.json')) &&
1086
+ (await fs.readJson('package.json').catch(() => ({}))).workspaces);
1087
+
1088
+ await fs.writeFile(
1089
+ '.contextkit/config.yml',
1070
1090
  `# ContextKit Configuration
1071
1091
  _source:
1072
1092
  tool: "@nolrm/contextkit"
@@ -1165,7 +1185,7 @@ https://www.npmjs.com/package/@nolrm/contextkit
1165
1185
  status.features.pre_push_hook = hookChoices.prePush;
1166
1186
  status.features.commit_msg_hook = hookChoices.commitMsg;
1167
1187
  status.features.squad_ci_workflow = squadCi;
1168
-
1188
+
1169
1189
  await this.statusManager.saveStatus(status);
1170
1190
  console.log(chalk.green('✅ Status tracking initialized'));
1171
1191
  } catch (error) {
@@ -1175,7 +1195,7 @@ https://www.npmjs.com/package/@nolrm/contextkit
1175
1195
 
1176
1196
  async createProductContext() {
1177
1197
  console.log(chalk.blue('📦 Creating product context files...'));
1178
-
1198
+
1179
1199
  const missionContent = `# Product Mission
1180
1200
 
1181
1201
  <!-- Content will be generated by running: ck analyze or manually fill this in -->
@@ -1373,13 +1393,13 @@ Unlike [COMPETITOR_OR_ALTERNATIVE], we provide [SPECIFIC_ADVANTAGE]. This result
1373
1393
  await fs.writeFile('.contextkit/product/roadmap.md', roadmapContent);
1374
1394
  await fs.writeFile('.contextkit/product/decisions.md', decisionsContent);
1375
1395
  await fs.writeFile('.contextkit/product/context.md', contextContent);
1376
-
1396
+
1377
1397
  console.log(chalk.green('✅ Product context files created'));
1378
1398
  }
1379
1399
 
1380
1400
  async createCorrectionsLog() {
1381
1401
  console.log(chalk.blue('📝 Creating corrections log system...'));
1382
-
1402
+
1383
1403
  const correctionsContent = `# ContextKit Corrections Log
1384
1404
 
1385
1405
  ## Description
@@ -1565,14 +1585,17 @@ Automatically track and log ContextKit performance issues during development ses
1565
1585
  `;
1566
1586
 
1567
1587
  await fs.writeFile('.contextkit/corrections.md', correctionsContent);
1568
- await fs.writeFile('.contextkit/instructions/core/auto-corrections-log.md', autoCorrectionsContent);
1569
-
1588
+ await fs.writeFile(
1589
+ '.contextkit/instructions/core/auto-corrections-log.md',
1590
+ autoCorrectionsContent
1591
+ );
1592
+
1570
1593
  console.log(chalk.green('✅ Corrections log system created'));
1571
1594
  }
1572
1595
 
1573
1596
  async createMetaInstructions() {
1574
1597
  console.log(chalk.blue('📋 Creating meta instructions...'));
1575
-
1598
+
1576
1599
  const preFlightContent = `---
1577
1600
  description: Common Pre-Flight Steps for ContextKit Instructions
1578
1601
  globs:
@@ -1646,13 +1669,13 @@ After completing any development session, automatically update the corrections l
1646
1669
 
1647
1670
  await fs.writeFile('.contextkit/instructions/meta/pre-flight.md', preFlightContent);
1648
1671
  await fs.writeFile('.contextkit/instructions/meta/post-flight.md', postFlightContent);
1649
-
1672
+
1650
1673
  console.log(chalk.green('✅ Meta instructions created'));
1651
1674
  }
1652
1675
 
1653
1676
  async createPolicyFile() {
1654
1677
  console.log(chalk.blue('⚖️ Creating policy file...'));
1655
-
1678
+
1656
1679
  const policyContent = `# ContextKit Policy Configuration
1657
1680
 
1658
1681
  ## Enforcement Levels
@@ -1676,11 +1699,11 @@ enforcement:
1676
1699
  `;
1677
1700
 
1678
1701
  await fs.writeFile('.contextkit/policies/policy.yml', policyContent);
1679
-
1702
+
1680
1703
  console.log(chalk.green('✅ Policy file created'));
1681
1704
  }
1682
1705
 
1683
- showSuccessMessage(hookChoices, platform = null, projectType = '', packageManager = '') {
1706
+ showSuccessMessage(hookChoices, platform = null, _projectType = '', _packageManager = '') {
1684
1707
  console.log('');
1685
1708
  console.log(chalk.green('🎉 ContextKit v1.0.0 successfully installed!'));
1686
1709
  console.log('');
@@ -1702,7 +1725,9 @@ enforcement:
1702
1725
  console.log(chalk.bold('📖 Quick Reference'));
1703
1726
  console.log(''.padEnd(48, '─'));
1704
1727
  console.log(`ck status → Check installation & integrations`);
1705
- console.log(`ck <platform> → Add platform (claude, cursor, copilot, codex, opencode, gemini, aider, continue, windsurf)`);
1728
+ console.log(
1729
+ `ck <platform> → Add platform (claude, cursor, copilot, codex, opencode, gemini, aider, continue, windsurf)`
1730
+ );
1706
1731
  console.log('');
1707
1732
  console.log(`Docs → ${chalk.blue('https://contextkit-docs.vercel.app')}`);
1708
1733
  console.log(`Issues → ${chalk.blue('https://github.com/nolrm/contextkit/issues')}`);
@@ -1747,7 +1772,11 @@ enforcement:
1747
1772
  }
1748
1773
 
1749
1774
  console.log(chalk.bold('In CLI'));
1750
- console.log(chalk.dim('Use your AI tool\'s slash command, e.g. /analyze or @.contextkit/commands/create-component.md'));
1775
+ console.log(
1776
+ chalk.dim(
1777
+ "Use your AI tool's slash command, e.g. /analyze or @.contextkit/commands/create-component.md"
1778
+ )
1779
+ );
1751
1780
  console.log('');
1752
1781
 
1753
1782
  if (platform === 'claude' || platform === 'gemini') {
@@ -1761,6 +1790,179 @@ enforcement:
1761
1790
  console.log('');
1762
1791
  }
1763
1792
 
1793
+ async promptQualityTooling(packageManager, _options = {}) {
1794
+ // Only relevant for Node.js projects
1795
+ if (!(await fs.pathExists('package.json'))) return;
1796
+
1797
+ // Skip in CI / non-interactive mode
1798
+ if (process.env.CI === 'true' || process.env.NON_INTERACTIVE === 'true') return;
1799
+
1800
+ const pkg = await fs.readJson('package.json').catch(() => null);
1801
+ if (!pkg) return;
1802
+
1803
+ const hasFormat = !!(pkg.scripts && pkg.scripts.format);
1804
+ const hasLint = !!(pkg.scripts && pkg.scripts.lint);
1805
+
1806
+ // Both already present — nothing to do
1807
+ if (hasFormat && hasLint) return;
1808
+
1809
+ console.log('');
1810
+ console.log('──────────────────────────────────────────────');
1811
+ console.log(chalk.blue('🛠️ Format + Lint Quality Gates'));
1812
+ console.log('──────────────────────────────────────────────');
1813
+ console.log(chalk.dim('The pre-push hook runs format and lint scripts automatically'));
1814
+ console.log(chalk.dim('when present. Your project has none — add a minimal setup now?'));
1815
+ console.log('');
1816
+
1817
+ const { scaffold } = await inquirer.prompt([
1818
+ {
1819
+ type: 'confirm',
1820
+ name: 'scaffold',
1821
+ message: 'Add prettier + eslint setup? (adds scripts + config files)',
1822
+ default: false,
1823
+ },
1824
+ ]);
1825
+
1826
+ if (scaffold) {
1827
+ await this.scaffoldQualityTooling(pkg, packageManager);
1828
+ } else {
1829
+ console.log(chalk.dim('💡 Add format/lint scripts later to activate these quality gates'));
1830
+ }
1831
+ console.log('');
1832
+ }
1833
+
1834
+ async scaffoldQualityTooling(pkg, packageManager) {
1835
+ const pmInstallFlags = {
1836
+ npm: 'install --save-dev',
1837
+ yarn: 'add --dev',
1838
+ pnpm: 'add -D',
1839
+ bun: 'add -d',
1840
+ };
1841
+ const installFlag = pmInstallFlags[packageManager] || 'install --save-dev';
1842
+ const pm = packageManager === 'none' ? 'npm' : packageManager;
1843
+
1844
+ try {
1845
+ const created = [];
1846
+
1847
+ // 1. Add missing scripts to package.json
1848
+ if (!pkg.scripts) pkg.scripts = {};
1849
+ if (!pkg.scripts.format) {
1850
+ pkg.scripts.format = 'prettier --write .';
1851
+ created.push('format script');
1852
+ }
1853
+ if (!pkg.scripts.lint) {
1854
+ pkg.scripts.lint = 'eslint .';
1855
+ created.push('lint script');
1856
+ }
1857
+ await fs.writeFile('package.json', JSON.stringify(pkg, null, 2) + '\n');
1858
+
1859
+ // 2. Create .prettierrc if no existing config
1860
+ if (!(await this._hasExistingPrettierConfig(pkg))) {
1861
+ await fs.writeFile(
1862
+ '.prettierrc',
1863
+ JSON.stringify(
1864
+ { singleQuote: true, semi: true, tabWidth: 2, printWidth: 100, trailingComma: 'es5' },
1865
+ null,
1866
+ 2
1867
+ ) + '\n'
1868
+ );
1869
+ created.push('.prettierrc');
1870
+ }
1871
+
1872
+ // 3. Create .prettierignore if absent
1873
+ if (!(await fs.pathExists('.prettierignore'))) {
1874
+ await fs.writeFile('.prettierignore', 'node_modules/\ncoverage/\ndist/\nbuild/\n');
1875
+ created.push('.prettierignore');
1876
+ }
1877
+
1878
+ // 4. Create eslint.config.js if no existing config
1879
+ if (!(await this._hasExistingEslintConfig(pkg))) {
1880
+ await fs.writeFile(
1881
+ 'eslint.config.js',
1882
+ `const js = require('@eslint/js');
1883
+ const globals = require('globals');
1884
+
1885
+ module.exports = [
1886
+ js.configs.recommended,
1887
+ {
1888
+ languageOptions: {
1889
+ ecmaVersion: 2022,
1890
+ globals: {
1891
+ ...globals.node,
1892
+ },
1893
+ },
1894
+ rules: {
1895
+ 'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
1896
+ },
1897
+ },
1898
+ {
1899
+ files: ['**/__tests__/**/*.js', '**/*.test.js'],
1900
+ languageOptions: {
1901
+ globals: {
1902
+ ...globals.jest,
1903
+ },
1904
+ },
1905
+ },
1906
+ ];\n`
1907
+ );
1908
+ created.push('eslint.config.js');
1909
+ }
1910
+
1911
+ // 5. Install devDependencies
1912
+ console.log(chalk.blue(`\n📦 Installing prettier + eslint...`));
1913
+ childProcess.execSync(`${pm} ${installFlag} prettier eslint @eslint/js globals`, {
1914
+ stdio: 'inherit',
1915
+ });
1916
+
1917
+ console.log('');
1918
+ console.log(chalk.green('✅ Quality tooling ready:'));
1919
+ created.forEach((item) => console.log(chalk.dim(` • ${item}`)));
1920
+ console.log(chalk.dim(' Run npm run format to apply formatting to existing files'));
1921
+ } catch (error) {
1922
+ console.log(chalk.yellow('⚠️ Could not complete quality tooling setup:'), error.message);
1923
+ console.log(chalk.dim(' You can set this up manually later'));
1924
+ }
1925
+ }
1926
+
1927
+ async _hasExistingPrettierConfig(pkg) {
1928
+ if (pkg.prettier) return true;
1929
+ const configs = [
1930
+ '.prettierrc',
1931
+ '.prettierrc.json',
1932
+ '.prettierrc.js',
1933
+ '.prettierrc.mjs',
1934
+ '.prettierrc.cjs',
1935
+ '.prettierrc.yaml',
1936
+ '.prettierrc.yml',
1937
+ '.prettierrc.toml',
1938
+ 'prettier.config.js',
1939
+ 'prettier.config.mjs',
1940
+ 'prettier.config.cjs',
1941
+ ];
1942
+ for (const f of configs) {
1943
+ if (await fs.pathExists(f)) return true;
1944
+ }
1945
+ return false;
1946
+ }
1947
+
1948
+ async _hasExistingEslintConfig(pkg) {
1949
+ if (pkg.eslintConfig) return true;
1950
+ const configs = [
1951
+ 'eslint.config.js',
1952
+ 'eslint.config.mjs',
1953
+ 'eslint.config.cjs',
1954
+ '.eslintrc',
1955
+ '.eslintrc.js',
1956
+ '.eslintrc.json',
1957
+ '.eslintrc.yml',
1958
+ '.eslintrc.yaml',
1959
+ ];
1960
+ for (const f of configs) {
1961
+ if (await fs.pathExists(f)) return true;
1962
+ }
1963
+ return false;
1964
+ }
1965
+
1764
1966
  async _findFileUpward(name, maxLevels) {
1765
1967
  let dir = process.cwd();
1766
1968
  for (let i = 0; i <= maxLevels; i++) {
@@ -1779,3 +1981,4 @@ async function install(options) {
1779
1981
  }
1780
1982
 
1781
1983
  module.exports = install;
1984
+ module.exports.InstallCommand = InstallCommand;