@raftlabs/raftstack 1.8.0 → 1.9.1

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/dist/cli.js CHANGED
@@ -1650,14 +1650,65 @@ async function generateClaudeSkills(targetDir, options) {
1650
1650
  return result;
1651
1651
  }
1652
1652
 
1653
- // src/generators/eslint.ts
1653
+ // src/generators/claude-commands.ts
1654
1654
  import { existsSync as existsSync6 } from "fs";
1655
+ import { readdir as readdir2, copyFile as copyFile3 } from "fs/promises";
1656
+ import { join as join16, dirname as dirname3 } from "path";
1657
+ import { fileURLToPath as fileURLToPath2 } from "url";
1658
+ function getPackageCommandsDir() {
1659
+ const currentFilePath = fileURLToPath2(import.meta.url);
1660
+ const packageRoot = join16(dirname3(currentFilePath), "..");
1661
+ return join16(packageRoot, ".claude", "commands");
1662
+ }
1663
+ async function copyDirectory2(srcDir, destDir, result, baseDir) {
1664
+ await ensureDir(destDir);
1665
+ const entries = await readdir2(srcDir, { withFileTypes: true });
1666
+ for (const entry of entries) {
1667
+ const srcPath = join16(srcDir, entry.name);
1668
+ const destPath = join16(destDir, entry.name);
1669
+ const relativePath = destPath.replace(baseDir + "/", "");
1670
+ if (entry.isDirectory()) {
1671
+ await copyDirectory2(srcPath, destPath, result, baseDir);
1672
+ } else {
1673
+ if (existsSync6(destPath)) {
1674
+ const backupPath = await backupFile(destPath);
1675
+ if (backupPath) {
1676
+ result.backedUp.push(relativePath);
1677
+ }
1678
+ }
1679
+ await copyFile3(srcPath, destPath);
1680
+ result.created.push(relativePath);
1681
+ }
1682
+ }
1683
+ }
1684
+ async function generateClaudeCommands(targetDir) {
1685
+ const result = {
1686
+ created: [],
1687
+ modified: [],
1688
+ skipped: [],
1689
+ backedUp: []
1690
+ };
1691
+ const packageCommandsDir = getPackageCommandsDir();
1692
+ const targetCommandsDir = join16(targetDir, ".claude", "commands");
1693
+ if (!existsSync6(packageCommandsDir)) {
1694
+ console.warn(
1695
+ "Warning: Commands directory not found in package. Skipping commands generation."
1696
+ );
1697
+ return result;
1698
+ }
1699
+ await ensureDir(join16(targetDir, ".claude"));
1700
+ await copyDirectory2(packageCommandsDir, targetCommandsDir, result, targetDir);
1701
+ return result;
1702
+ }
1703
+
1704
+ // src/generators/eslint.ts
1705
+ import { existsSync as existsSync7 } from "fs";
1655
1706
  import { readFile as readFile4 } from "fs/promises";
1656
- import { join as join16 } from "path";
1707
+ import { join as join17 } from "path";
1657
1708
  async function hasReact(targetDir) {
1658
1709
  try {
1659
- const pkgPath = join16(targetDir, "package.json");
1660
- if (existsSync6(pkgPath)) {
1710
+ const pkgPath = join17(targetDir, "package.json");
1711
+ if (existsSync7(pkgPath)) {
1661
1712
  const content = await readFile4(pkgPath, "utf-8");
1662
1713
  const pkg = JSON.parse(content);
1663
1714
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
@@ -1669,8 +1720,8 @@ async function hasReact(targetDir) {
1669
1720
  }
1670
1721
  async function hasNextJs(targetDir) {
1671
1722
  try {
1672
- const pkgPath = join16(targetDir, "package.json");
1673
- if (existsSync6(pkgPath)) {
1723
+ const pkgPath = join17(targetDir, "package.json");
1724
+ if (existsSync7(pkgPath)) {
1674
1725
  const content = await readFile4(pkgPath, "utf-8");
1675
1726
  const pkg = JSON.parse(content);
1676
1727
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
@@ -1949,7 +2000,7 @@ async function generateEslint(targetDir, usesTypeScript, force = false) {
1949
2000
  } else {
1950
2001
  config = generateJsConfig();
1951
2002
  }
1952
- const configPath = join16(targetDir, "eslint.config.mjs");
2003
+ const configPath = join17(targetDir, "eslint.config.mjs");
1953
2004
  const writeResult = await writeFileSafe(configPath, config);
1954
2005
  if (writeResult.backedUp) {
1955
2006
  result.backedUp.push("eslint.config.mjs");
@@ -1959,7 +2010,7 @@ async function generateEslint(targetDir, usesTypeScript, force = false) {
1959
2010
  }
1960
2011
 
1961
2012
  // src/generators/quick-reference.ts
1962
- import { join as join17 } from "path";
2013
+ import { join as join18 } from "path";
1963
2014
  async function generateQuickReference(targetDir, pm) {
1964
2015
  const result = {
1965
2016
  created: [],
@@ -1967,7 +2018,7 @@ async function generateQuickReference(targetDir, pm) {
1967
2018
  skipped: [],
1968
2019
  backedUp: []
1969
2020
  };
1970
- const quickRefPath = join17(targetDir, ".github", "QUICK_REFERENCE.md");
2021
+ const quickRefPath = join18(targetDir, ".github", "QUICK_REFERENCE.md");
1971
2022
  const content = `# RaftStack Quick Reference
1972
2023
 
1973
2024
  > One-page guide for the RaftStack Git workflow
@@ -2101,7 +2152,7 @@ ${pm.run} test
2101
2152
  }
2102
2153
 
2103
2154
  // src/generators/shared-configs.ts
2104
- import { join as join18 } from "path";
2155
+ import { join as join19 } from "path";
2105
2156
  function getEslintConfigPackageJson() {
2106
2157
  return JSON.stringify(
2107
2158
  {
@@ -2367,15 +2418,15 @@ async function generateSharedConfigs(targetDir, projectType) {
2367
2418
  if (!isMonorepo(projectType)) {
2368
2419
  return result;
2369
2420
  }
2370
- const packagesDir = join18(targetDir, "packages");
2371
- const eslintConfigDir = join18(packagesDir, "eslint-config");
2421
+ const packagesDir = join19(targetDir, "packages");
2422
+ const eslintConfigDir = join19(packagesDir, "eslint-config");
2372
2423
  await ensureDir(eslintConfigDir);
2373
2424
  const eslintFiles = [
2374
- { path: join18(eslintConfigDir, "package.json"), content: getEslintConfigPackageJson(), name: "packages/eslint-config/package.json" },
2375
- { path: join18(eslintConfigDir, "base.js"), content: getBaseEslintConfig(), name: "packages/eslint-config/base.js" },
2376
- { path: join18(eslintConfigDir, "next.js"), content: getNextJsEslintConfig(), name: "packages/eslint-config/next.js" },
2377
- { path: join18(eslintConfigDir, "react-internal.js"), content: getReactInternalEslintConfig(), name: "packages/eslint-config/react-internal.js" },
2378
- { path: join18(eslintConfigDir, "vite.js"), content: getViteEslintConfig(), name: "packages/eslint-config/vite.js" }
2425
+ { path: join19(eslintConfigDir, "package.json"), content: getEslintConfigPackageJson(), name: "packages/eslint-config/package.json" },
2426
+ { path: join19(eslintConfigDir, "base.js"), content: getBaseEslintConfig(), name: "packages/eslint-config/base.js" },
2427
+ { path: join19(eslintConfigDir, "next.js"), content: getNextJsEslintConfig(), name: "packages/eslint-config/next.js" },
2428
+ { path: join19(eslintConfigDir, "react-internal.js"), content: getReactInternalEslintConfig(), name: "packages/eslint-config/react-internal.js" },
2429
+ { path: join19(eslintConfigDir, "vite.js"), content: getViteEslintConfig(), name: "packages/eslint-config/vite.js" }
2379
2430
  ];
2380
2431
  for (const file of eslintFiles) {
2381
2432
  const writeResult = await writeFileSafe(file.path, file.content, { backup: true });
@@ -2386,14 +2437,14 @@ async function generateSharedConfigs(targetDir, projectType) {
2386
2437
  }
2387
2438
  }
2388
2439
  }
2389
- const tsConfigDir = join18(packagesDir, "typescript-config");
2440
+ const tsConfigDir = join19(packagesDir, "typescript-config");
2390
2441
  await ensureDir(tsConfigDir);
2391
2442
  const tsFiles = [
2392
- { path: join18(tsConfigDir, "package.json"), content: getTsConfigPackageJson(), name: "packages/typescript-config/package.json" },
2393
- { path: join18(tsConfigDir, "base.json"), content: getBaseTsConfig(), name: "packages/typescript-config/base.json" },
2394
- { path: join18(tsConfigDir, "nextjs.json"), content: getNextJsTsConfig(), name: "packages/typescript-config/nextjs.json" },
2395
- { path: join18(tsConfigDir, "react-library.json"), content: getReactLibraryTsConfig(), name: "packages/typescript-config/react-library.json" },
2396
- { path: join18(tsConfigDir, "node-library.json"), content: getNodeLibraryTsConfig(), name: "packages/typescript-config/node-library.json" }
2443
+ { path: join19(tsConfigDir, "package.json"), content: getTsConfigPackageJson(), name: "packages/typescript-config/package.json" },
2444
+ { path: join19(tsConfigDir, "base.json"), content: getBaseTsConfig(), name: "packages/typescript-config/base.json" },
2445
+ { path: join19(tsConfigDir, "nextjs.json"), content: getNextJsTsConfig(), name: "packages/typescript-config/nextjs.json" },
2446
+ { path: join19(tsConfigDir, "react-library.json"), content: getReactLibraryTsConfig(), name: "packages/typescript-config/react-library.json" },
2447
+ { path: join19(tsConfigDir, "node-library.json"), content: getNodeLibraryTsConfig(), name: "packages/typescript-config/node-library.json" }
2397
2448
  ];
2398
2449
  for (const file of tsFiles) {
2399
2450
  const writeResult = await writeFileSafe(file.path, file.content, { backup: true });
@@ -2409,10 +2460,10 @@ async function generateSharedConfigs(targetDir, projectType) {
2409
2460
 
2410
2461
  // src/utils/git.ts
2411
2462
  import { execa } from "execa";
2412
- import { existsSync as existsSync7 } from "fs";
2413
- import { join as join19 } from "path";
2463
+ import { existsSync as existsSync8 } from "fs";
2464
+ import { join as join20 } from "path";
2414
2465
  async function isGitRepo(targetDir = process.cwd()) {
2415
- if (existsSync7(join19(targetDir, ".git"))) {
2466
+ if (existsSync8(join20(targetDir, ".git"))) {
2416
2467
  return true;
2417
2468
  }
2418
2469
  try {
@@ -2548,8 +2599,8 @@ async function runInit(targetDir = process.cwd()) {
2548
2599
  );
2549
2600
  installFailed = true;
2550
2601
  }
2551
- const spinner4 = p2.spinner();
2552
- spinner4.start("Generating configuration files...");
2602
+ const spinner5 = p2.spinner();
2603
+ spinner5.start("Generating configuration files...");
2553
2604
  const results = [];
2554
2605
  try {
2555
2606
  results.push(
@@ -2584,10 +2635,11 @@ async function runInit(targetDir = process.cwd()) {
2584
2635
  results.push(await generateClaudeSkills(targetDir, {
2585
2636
  includeAsana: !!config.asanaBaseUrl
2586
2637
  }));
2638
+ results.push(await generateClaudeCommands(targetDir));
2587
2639
  results.push(await updateProjectPackageJson(targetDir, config));
2588
- spinner4.stop("Configuration files generated!");
2640
+ spinner5.stop("Configuration files generated!");
2589
2641
  } catch (error) {
2590
- spinner4.stop("Error generating files");
2642
+ spinner5.stop("Error generating files");
2591
2643
  p2.log.error(
2592
2644
  pc2.red(
2593
2645
  `Error: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -2735,11 +2787,11 @@ async function applyMergeStrategy(owner, repo, settings) {
2735
2787
  async function runSetupProtection(targetDir = process.cwd()) {
2736
2788
  console.log();
2737
2789
  p3.intro(pc3.bgCyan(pc3.black(" Branch Protection Setup ")));
2738
- const spinner4 = p3.spinner();
2739
- spinner4.start("Checking GitHub CLI...");
2790
+ const spinner5 = p3.spinner();
2791
+ spinner5.start("Checking GitHub CLI...");
2740
2792
  const ghAvailable = await isGhCliAvailable();
2741
2793
  if (!ghAvailable) {
2742
- spinner4.stop("GitHub CLI not found or not authenticated");
2794
+ spinner5.stop("GitHub CLI not found or not authenticated");
2743
2795
  console.log();
2744
2796
  p3.log.error(pc3.red("The GitHub CLI (gh) is required for this command."));
2745
2797
  p3.log.info("Install it from: https://cli.github.com/");
@@ -2752,18 +2804,18 @@ async function runSetupProtection(targetDir = process.cwd()) {
2752
2804
  );
2753
2805
  process.exit(1);
2754
2806
  }
2755
- spinner4.stop("GitHub CLI ready");
2756
- spinner4.start("Getting repository info...");
2807
+ spinner5.stop("GitHub CLI ready");
2808
+ spinner5.start("Getting repository info...");
2757
2809
  const repoInfo = await getGitHubRepoInfo(targetDir);
2758
2810
  if (!repoInfo) {
2759
- spinner4.stop("Could not determine repository");
2811
+ spinner5.stop("Could not determine repository");
2760
2812
  p3.log.error(
2761
2813
  pc3.red("Could not determine the GitHub repository for this directory.")
2762
2814
  );
2763
2815
  p3.log.info("Make sure you're in a git repository with a GitHub remote.");
2764
2816
  process.exit(1);
2765
2817
  }
2766
- spinner4.stop(`Repository: ${pc3.cyan(`${repoInfo.owner}/${repoInfo.repo}`)}`);
2818
+ spinner5.stop(`Repository: ${pc3.cyan(`${repoInfo.owner}/${repoInfo.repo}`)}`);
2767
2819
  const branches = await p3.multiselect({
2768
2820
  message: "Which branches need protection?",
2769
2821
  options: [
@@ -2852,13 +2904,13 @@ async function runSetupProtection(targetDir = process.cwd()) {
2852
2904
  p3.cancel("Setup cancelled.");
2853
2905
  process.exit(0);
2854
2906
  }
2855
- spinner4.start("Configuring merge strategy...");
2907
+ spinner5.start("Configuring merge strategy...");
2856
2908
  try {
2857
2909
  const repoSettings = getMergeStrategySettings(mergeStrategy);
2858
2910
  await applyMergeStrategy(repoInfo.owner, repoInfo.repo, repoSettings);
2859
- spinner4.stop("Merge strategy configured!");
2911
+ spinner5.stop("Merge strategy configured!");
2860
2912
  } catch (error) {
2861
- spinner4.stop("Failed to configure merge strategy");
2913
+ spinner5.stop("Failed to configure merge strategy");
2862
2914
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
2863
2915
  p3.log.warn(pc3.yellow(`Warning: Could not set merge strategy: ${errorMsg}`));
2864
2916
  p3.log.info(pc3.dim("Continuing with branch protection..."));
@@ -2866,16 +2918,16 @@ async function runSetupProtection(targetDir = process.cwd()) {
2866
2918
  const protectedBranches = [];
2867
2919
  const failedBranches = [];
2868
2920
  for (const branch of branches) {
2869
- spinner4.start(`Protecting branch: ${branch}...`);
2921
+ spinner5.start(`Protecting branch: ${branch}...`);
2870
2922
  try {
2871
2923
  const settings = getDefaultSettings(branch);
2872
2924
  settings.requiredReviews = requiredReviews;
2873
2925
  await applyBranchProtection(repoInfo.owner, repoInfo.repo, settings);
2874
2926
  protectedBranches.push(branch);
2875
- spinner4.stop(`Protected: ${pc3.green(branch)}`);
2927
+ spinner5.stop(`Protected: ${pc3.green(branch)}`);
2876
2928
  } catch (error) {
2877
2929
  failedBranches.push(branch);
2878
- spinner4.stop(`Failed: ${pc3.red(branch)}`);
2930
+ spinner5.stop(`Failed: ${pc3.red(branch)}`);
2879
2931
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
2880
2932
  p3.log.warn(
2881
2933
  pc3.yellow(
@@ -3398,8 +3450,8 @@ async function runMetrics(targetDir, options = {}) {
3398
3450
  selectedDays = daysOption;
3399
3451
  }
3400
3452
  const analyzeDays = selectedDays || 30;
3401
- const spinner4 = ciMode ? null : p4.spinner();
3402
- spinner4?.start("Analyzing repository...");
3453
+ const spinner5 = ciMode ? null : p4.spinner();
3454
+ spinner5?.start("Analyzing repository...");
3403
3455
  let overallCompliance = 100;
3404
3456
  const complianceScores = [];
3405
3457
  if (showGit) {
@@ -3420,7 +3472,7 @@ async function runMetrics(targetDir, options = {}) {
3420
3472
  conventionalCompliance,
3421
3473
  branchMetrics.branchCompliance
3422
3474
  );
3423
- spinner4?.stop("Git analysis complete");
3475
+ spinner5?.stop("Git analysis complete");
3424
3476
  if (ciMode) {
3425
3477
  console.log("\n=== GIT METRICS ===");
3426
3478
  console.log(`Commits (last ${analyzeDays} days): ${totalCommits}`);
@@ -3478,13 +3530,13 @@ ${pc4.bold("Branches")}
3478
3530
  }
3479
3531
  if (showCode) {
3480
3532
  if (!ciMode && showGit) {
3481
- spinner4?.start("Analyzing codebase...");
3533
+ spinner5?.start("Analyzing codebase...");
3482
3534
  } else if (!ciMode) {
3483
- spinner4?.start("Analyzing codebase...");
3535
+ spinner5?.start("Analyzing codebase...");
3484
3536
  }
3485
3537
  const codebaseMetrics = await analyzeCodebase(targetDir);
3486
3538
  complianceScores.push(codebaseMetrics.overallCompliance);
3487
- spinner4?.stop("Codebase analysis complete");
3539
+ spinner5?.stop("Codebase analysis complete");
3488
3540
  if (ciMode) {
3489
3541
  console.log("\n=== CODEBASE METRICS ===");
3490
3542
  console.log(`Files analyzed: ${codebaseMetrics.filesAnalyzed}`);
@@ -3528,10 +3580,60 @@ PASSED: Compliance meets threshold`);
3528
3580
  }
3529
3581
  }
3530
3582
 
3583
+ // src/commands/install-commands.ts
3584
+ import * as p5 from "@clack/prompts";
3585
+ import pc5 from "picocolors";
3586
+ async function runInstallCommands(targetDir = process.cwd()) {
3587
+ p5.intro(pc5.cyan("RaftStack: Install Claude Code commands and skills"));
3588
+ const spinner5 = p5.spinner();
3589
+ spinner5.start("Installing Claude Code commands and skills...");
3590
+ try {
3591
+ const commandsResult = await generateClaudeCommands(targetDir);
3592
+ const skillsResult = await generateClaudeSkills(targetDir);
3593
+ spinner5.stop("Claude Code commands and skills installed!");
3594
+ const created = [...commandsResult.created, ...skillsResult.created];
3595
+ const backedUp = [...commandsResult.backedUp, ...skillsResult.backedUp];
3596
+ console.log();
3597
+ if (created.length > 0) {
3598
+ p5.log.success(pc5.green("Installed files:"));
3599
+ for (const file of created) {
3600
+ console.log(` ${pc5.dim("+")} ${file}`);
3601
+ }
3602
+ }
3603
+ if (backedUp.length > 0) {
3604
+ console.log();
3605
+ p5.log.info(pc5.dim("Backed up existing files:"));
3606
+ for (const file of backedUp) {
3607
+ console.log(` ${pc5.dim("\u2192")} ${file}.backup`);
3608
+ }
3609
+ }
3610
+ console.log();
3611
+ p5.note(
3612
+ [
3613
+ `${pc5.cyan("/raftstack/init-context")} - Analyze codebase, generate constitution`,
3614
+ `${pc5.cyan("/raftstack/shape")} - Plan features with adaptive depth`,
3615
+ `${pc5.cyan("/raftstack/discover")} - Extract patterns into standards`,
3616
+ `${pc5.cyan("/raftstack/inject")} - Surface relevant context for tasks`,
3617
+ `${pc5.cyan("/raftstack/index")} - Maintain standards registry`
3618
+ ].join("\n"),
3619
+ "Available Commands"
3620
+ );
3621
+ p5.outro(pc5.green("Ready to use! Try /raftstack/init-context to get started."));
3622
+ } catch (error) {
3623
+ spinner5.stop("Error installing commands");
3624
+ p5.log.error(
3625
+ pc5.red(
3626
+ `Error: ${error instanceof Error ? error.message : "Unknown error"}`
3627
+ )
3628
+ );
3629
+ process.exit(1);
3630
+ }
3631
+ }
3632
+
3531
3633
  // package.json
3532
3634
  var package_default = {
3533
3635
  name: "@raftlabs/raftstack",
3534
- version: "1.8.0",
3636
+ version: "1.9.1",
3535
3637
  description: "CLI tool for setting up Git hooks, commit conventions, and GitHub integration",
3536
3638
  type: "module",
3537
3639
  main: "./dist/index.js",
@@ -3548,7 +3650,8 @@ var package_default = {
3548
3650
  files: [
3549
3651
  "dist",
3550
3652
  "templates",
3551
- ".claude/skills"
3653
+ ".claude/skills",
3654
+ ".claude/commands"
3552
3655
  ],
3553
3656
  scripts: {
3554
3657
  build: "tsup",
@@ -3636,5 +3739,8 @@ program.command("metrics").description("Analyze repository compliance with RaftS
3636
3739
  });
3637
3740
  }
3638
3741
  );
3742
+ program.command("install-commands").description("Install or update Claude Code commands and skills").action(async () => {
3743
+ await runInstallCommands(process.cwd());
3744
+ });
3639
3745
  program.parse();
3640
3746
  //# sourceMappingURL=cli.js.map