390 lines
11 KiB
Markdown
390 lines
11 KiB
Markdown
# 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**
|
|
|
|
```ts
|
|
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**
|
|
|
|
```ts
|
|
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**
|
|
|
|
```json
|
|
{
|
|
"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**
|
|
|
|
```bash
|
|
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**
|
|
|
|
```ts
|
|
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**
|
|
|
|
```ts
|
|
// 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`**
|
|
|
|
```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**
|
|
|
|
```bash
|
|
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**
|
|
|
|
```ts
|
|
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**
|
|
|
|
```ts
|
|
// 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**
|
|
|
|
```bash
|
|
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**
|
|
|
|
```ts
|
|
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**
|
|
|
|
```ts
|
|
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**
|
|
|
|
```bash
|
|
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**
|
|
|
|
```md
|
|
- 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**
|
|
|
|
````md
|
|
# 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**
|
|
|
|
```bash
|
|
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"
|
|
```
|