Files
skills-vault/docs/superpowers/plans/2026-03-25-skills-vault-cli-package-reorg.md
T

11 KiB

Skills Vault CLI Package Reorganization Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Reorganize the repository into a publishable pure CLI package named @xixu-me/skills-vault with the public binary skvlt, while preserving the existing backup and restore behavior.

Architecture: Introduce a single CLI dispatcher in src/cli.ts, move command orchestration into src/commands/, isolate non-public helpers under src/internal/, and relocate tests into test/ with behavior-oriented names. The migration should preserve behavior through TDD: write the failing tests first, then move logic, then remove the legacy scripts/ production layout.

Tech Stack: Bun 1.3.x, TypeScript, Bun test runner, Node-compatible filesystem/process APIs


File Map

Create

  • src/cli.ts
  • src/commands/backup.ts
  • src/commands/restore.ts
  • src/internal/args/parse-backup-args.ts
  • src/internal/args/parse-restore-args.ts
  • src/internal/install/resolve-install-concurrency.ts
  • src/internal/install/run-with-concurrency.ts
  • src/internal/manifest/build-manifest.ts
  • src/internal/manifest/parse-manifest.ts
  • src/internal/paths/defaults.ts
  • src/internal/process/run-bunx.ts
  • test/cli.test.ts
  • test/backup.test.ts
  • test/restore.test.ts
  • test/install-concurrency.test.ts
  • README.md

Modify

  • package.json

Delete After Migration

  • scripts/backup.ts
  • scripts/restore.ts
  • scripts/restore-lib.ts
  • scripts/restore-lib.test.ts

Task 1: Establish the CLI Shell and Package Metadata

Files:

  • Create: src/cli.ts

  • Create: test/cli.test.ts

  • Modify: package.json

  • Step 1: Write the failing CLI dispatch test

import { expect, test } from "bun:test";
import { runCli } from "../src/cli";

test("dispatches the restore command", async () => {
  const result = await runCli(["restore", "--help"]);
  expect(result.exitCode).toBe(0);
});
  • Step 2: Run test to verify it fails

Run: bun test test/cli.test.ts Expected: FAIL because ../src/cli does not exist yet.

  • Step 3: Write minimal CLI implementation
type CliResult = { exitCode: number };

export async function runCli(argv: string[]): Promise<CliResult> {
  const [command] = argv;
  if (command === "backup") {
    return { exitCode: 0 };
  }
  if (command === "restore") {
    return { exitCode: 0 };
  }
  console.error("Usage: skvlt <backup|restore>");
  return { exitCode: 1 };
}

if (import.meta.main) {
  const result = await runCli(Bun.argv.slice(2));
  process.exit(result.exitCode);
}
  • Step 4: Update package metadata for the new CLI
{
  "name": "@xixu-me/skills-vault",
  "bin": {
    "skvlt": "./src/cli.ts"
  },
  "scripts": {
    "backup": "bun run ./src/cli.ts backup",
    "backup:dry-run": "bun run ./src/cli.ts backup --dry-run",
    "restore": "bun run ./src/cli.ts restore",
    "restore:dry-run": "bun run ./src/cli.ts restore --dry-run"
  }
}
  • Step 5: Run test to verify it passes

Run: bun test test/cli.test.ts Expected: PASS

  • Step 6: Smoke test the top-level CLI

Run: bun run ./src/cli.ts Expected: exit code 1 and top-level usage text for skvlt

  • Step 7: Commit
git add package.json src/cli.ts test/cli.test.ts
git commit -m "refactor(cli): add top-level skvlt entrypoint"

Task 2: Move Backup Into Command and Internal Modules

Files:

  • Create: src/commands/backup.ts

  • Create: src/internal/args/parse-backup-args.ts

  • Create: src/internal/manifest/build-manifest.ts

  • Create: src/internal/paths/defaults.ts

  • Create: src/internal/process/run-bunx.ts

  • Create: test/backup.test.ts

  • Modify: src/cli.ts

  • Delete later: scripts/backup.ts

  • Step 1: Write the failing backup dry-run test

import { expect, test } from "bun:test";
import { runBackup } from "../src/commands/backup";

test("prints manifest YAML during backup dry-run", async () => {
  const result = await runBackup(["--dry-run"]);
  expect(result.exitCode).toBe(0);
  expect(result.stdout).toContain("sources:");
});
  • Step 2: Run test to verify it fails

Run: bun test test/backup.test.ts Expected: FAIL because ../src/commands/backup does not exist yet.

  • Step 3: Extract shared process and path helpers
// src/internal/process/run-bunx.ts
export async function runBunx(args: string[]) {
  const proc = Bun.spawn({
    cmd: ["bunx", ...args],
    stdout: "pipe",
    stderr: "pipe",
  });
  const [stdout, stderr, exitCode] = await Promise.all([
    new Response(proc.stdout).text(),
    new Response(proc.stderr).text(),
    proc.exited,
  ]);
  return { stdout, stderr, exitCode };
}
  • Step 4: Move backup orchestration into src/commands/backup.ts
export async function runBackup(argv: string[]) {
  const options = parseBackupArgs(argv);
  const installedSkillNames = await getInstalledSkillNames(
    options.projectScope,
  );
  const manifest = buildManifest(installedSkillNames, options.lockFilePath);
  return options.dryRun
    ? { exitCode: 0, stdout: manifest, stderr: "" }
    : await writeBackupManifest(options.outputPath, manifest);
}
  • Step 5: Wire the CLI dispatcher to the backup command

Run: bun test test/cli.test.ts test/backup.test.ts Expected: PASS

  • Step 6: Smoke test backup dry-run through the new CLI

Run: bun run ./src/cli.ts backup --dry-run Expected: prints YAML with sources: and exits 0

  • Step 7: Commit
git add src/cli.ts src/commands/backup.ts src/internal/args/parse-backup-args.ts src/internal/manifest/build-manifest.ts src/internal/paths/defaults.ts src/internal/process/run-bunx.ts test/backup.test.ts
git commit -m "refactor(backup): move backup flow into command modules"

Task 3: Move Restore Concurrency Helpers Into Internal Install Modules

Files:

  • Create: src/internal/install/resolve-install-concurrency.ts

  • Create: src/internal/install/run-with-concurrency.ts

  • Create: test/install-concurrency.test.ts

  • Delete later: scripts/restore-lib.ts

  • Delete later: scripts/restore-lib.test.ts

  • Step 1: Copy the existing concurrency coverage into the new test location and add one naming assertion

import { expect, test } from "bun:test";
import { resolveInstallConcurrency } from "../src/internal/install/resolve-install-concurrency";
import { runWithConcurrency } from "../src/internal/install/run-with-concurrency";

test("resolveInstallConcurrency caps auto concurrency at task count", () => {
  expect(
    resolveInstallConcurrency({ taskCount: 2, availableParallelism: 16 }),
  ).toBe(2);
});
  • Step 2: Run test to verify it fails

Run: bun test test/install-concurrency.test.ts Expected: FAIL because the new install modules do not exist yet.

  • Step 3: Move the concurrency implementations into focused internal files
// src/internal/install/resolve-install-concurrency.ts
export function resolveInstallConcurrency(options: ResolveOptions): number {
  // keep half-of-available-parallelism default with safe caps
}

// src/internal/install/run-with-concurrency.ts
export async function runWithConcurrency<T>(
  items: T[],
  concurrency: number,
  worker: Worker<T>,
  options: Options,
) {
  // preserve bounded scheduling and continue-on-error semantics
}
  • Step 4: Run the concurrency tests to verify they pass

Run: bun test test/install-concurrency.test.ts Expected: PASS

  • Step 5: Commit
git add src/internal/install/resolve-install-concurrency.ts src/internal/install/run-with-concurrency.ts test/install-concurrency.test.ts
git commit -m "refactor(restore): isolate install concurrency helpers"

Task 4: Move Restore Into Command and Manifest Modules

Files:

  • Create: src/commands/restore.ts

  • Create: src/internal/args/parse-restore-args.ts

  • Create: src/internal/manifest/parse-manifest.ts

  • Create: test/restore.test.ts

  • Modify: src/cli.ts

  • Modify: src/internal/process/run-bunx.ts

  • Delete later: scripts/restore.ts

  • Step 1: Write the failing restore dry-run test

import { expect, test } from "bun:test";
import { runRestore } from "../src/commands/restore";

test("prints per-source install commands during restore dry-run", async () => {
  const result = await runRestore(["--dry-run"]);
  expect(result.exitCode).toBe(0);
  expect(result.stdout).toContain("[dry-run] bunx skills add");
});
  • Step 2: Run test to verify it fails

Run: bun test test/restore.test.ts Expected: FAIL because ../src/commands/restore does not exist yet.

  • Step 3: Move manifest parsing and restore orchestration into the new structure
export async function runRestore(argv: string[]) {
  const options = parseRestoreArgs(argv);
  const manifest = parseManifest(options.manifestPath);
  const plan = createRestorePlan(manifest, options);
  return options.dryRun
    ? previewRestorePlan(plan)
    : await executeRestorePlan(plan, options);
}
  • Step 4: Reconnect CLI dispatch to the restore command

Run: bun test test/cli.test.ts test/restore.test.ts test/install-concurrency.test.ts Expected: PASS

  • Step 5: Smoke test restore dry-run through the CLI

Run: bun run ./src/cli.ts restore --dry-run --concurrency 4 --only-source anthropics/skills Expected: prints dry-run commands and a concurrency summary using the new module layout

  • Step 6: Commit
git add src/cli.ts src/commands/restore.ts src/internal/args/parse-restore-args.ts src/internal/manifest/parse-manifest.ts src/internal/process/run-bunx.ts test/restore.test.ts
git commit -m "refactor(restore): move restore flow into command modules"

Task 5: Remove Legacy Script Layout and Finish Packaging

Files:

  • Create: README.md

  • Modify: package.json

  • Delete: scripts/backup.ts

  • Delete: scripts/restore.ts

  • Delete: scripts/restore-lib.ts

  • Delete: scripts/restore-lib.test.ts

  • Step 1: Write the failing packaging/readme expectation test or checklist

- package name is @xixu-me/skills-vault
- CLI docs use skvlt
- no production code remains under scripts/
  • Step 2: Delete the legacy production script files after the new tests are green

Run: rg --files scripts Expected: only temporary migration leftovers remain before deletion, then no production code files remain there

  • Step 3: Write the README around the single supported CLI
# Skills Vault

## Install

```bash
bun install
```

## Usage

```bash
skvlt backup
skvlt restore --dry-run
```
  • Step 4: Run full verification

Run: bun test Expected: all tests pass

Run: bun run ./src/cli.ts backup --dry-run Expected: YAML manifest output

Run: bun run ./src/cli.ts restore --dry-run --only-source anthropics/skills Expected: dry-run install command output

  • Step 5: Commit
git add package.json README.md src test
git rm scripts/backup.ts scripts/restore.ts scripts/restore-lib.ts scripts/restore-lib.test.ts
git commit -m "refactor(package): publish as skvlt CLI package"