From 0fb9918978ef0cb718571672867ebe9154a0573d Mon Sep 17 00:00:00 2001 From: "Harun R. Rayhan" Date: Fri, 6 Nov 2026 11:43:01 +0600 Subject: [PATCH] v1.3.0: dev-refactor and the /rs alias --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- CHANGELOG.md | 13 ++++ README.md | 6 +- VERSION | 2 +- docs/agents.md | 2 +- docs/guide.md | 3 +- install.sh | 9 +-- plugin.json | 2 +- skills/dev-gaps/SKILL.md | 2 +- skills/dev-refactor/SKILL.md | 117 ++++++++++++++++++++++++++++++++ skills/dev/SKILL.md | 4 +- skills/raystack/SKILL.md | 6 +- skills/rs/SKILL.md | 13 ++++ tests/check-linkage.sh | 12 ++-- tests/check-skills.sh | 2 +- tests/test-install.sh | 50 ++++++++------ 18 files changed, 206 insertions(+), 43 deletions(-) create mode 100644 skills/dev-refactor/SKILL.md create mode 100644 skills/rs/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c592500..0cb352d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "raystack", "source": "./", - "version": "1.2.1", + "version": "1.3.0", "description": "Skills by Harun R. Rayhan: a full development flow (new project intake, research, PRD, specs, plans, reviewed builds, ship; gap-fill, design review and migration for existing projects), read-only scans and reviews (security, performance, scalability, reliability and availability, architecture incl. MVC and CQRS, frontend, code health, CI/CD pipelines, AWS, infrastructure, databases), working principles and local browser verification.", "category": "devops" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index bed1e4f..a36e457 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "raystack", - "version": "1.2.1", + "version": "1.3.0", "description": "Skills by Harun R. Rayhan: a full development flow (new project intake, research, PRD, specs, plans, reviewed builds, ship; gap-fill, design review and migration for existing projects), read-only scans and reviews (security, performance, scalability, reliability and availability, architecture incl. MVC and CQRS, frontend, code health, CI/CD pipelines, AWS, infrastructure, databases), working principles and local browser verification.", "author": { "name": "Harun R. Rayhan", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index a9e2d99..c720acc 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "raystack", "displayName": "RayStack", - "version": "1.2.1", + "version": "1.3.0", "description": "Skills by Harun R. Rayhan: a full development flow (new project intake, research, PRD, specs, plans, reviewed builds, ship; gap-fill, design review and migration for existing projects), read-only scans and reviews (security, performance, scalability, reliability and availability, architecture incl. MVC and CQRS, frontend, code health, CI/CD pipelines, AWS, infrastructure, databases), working principles and local browser verification.", "author": { "name": "Harun R. Rayhan" diff --git a/CHANGELOG.md b/CHANGELOG.md index 924d3a7..9237b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 1.3.0 (2026-11-06) + +- New `dev-refactor`: refactors a whole codebase, or one area, to the end without changing + what it does. It names the architecture the code already follows and stays inside it, + runs the suite for a baseline, writes tests for untested code it will touch before any + change, lists every problem (hidden and needless links, surplus, special cases, DRY and + SOLID violations) in `docs/refactor/`, fixes them in small commits with the suite green + after each, and repeats with a fresh reviewer until nothing worth fixing is left. Run it + with `/raystack project refactor`. Inspired by caldoai's `untangle-codebase` (MIT). +- New `rs`: `/rs` is `/raystack` with a shorter name, so `/rs refactor` and `/rs scan all` + work. An existing, different `rs` skill is left alone (installer warns; `--force` replaces). + RayStack is now thirty-four skills. + ## 1.2.1 (2026-11-04) From running `dev-migrate` end to end (a pricing module moved from Express to Go, with the diff --git a/README.md b/README.md index c382d66..8e70353 100644 --- a/README.md +++ b/README.md @@ -477,7 +477,7 @@ above). Start a new agent session afterwards. ## Skills -RayStack is thirty-two skills. You only need `/raystack` (or `/dev` to build and `/audit` to review); they call the rest. +RayStack is thirty-four skills. You only need `/raystack` (or `/rs`, the same thing shorter; or `/dev` to build and `/audit` to review); they call the rest. | Skill | What it does | | --- | --- | @@ -492,6 +492,8 @@ RayStack is thirty-two skills. You only need `/raystack` (or `/dev` to build and | `dev-gaps` | Finds the docs an existing project is missing and writes them from the code and your answers | | `dev-review` | Read-only check of the architecture, specs and plan against the code | | `dev-migrate` | Moves part of a project to another language, framework or database, step by step with parity tests | +| `dev-refactor` | Refactors a codebase inside the architecture it already follows: tests first, then DRY, SOLID and change-together fixes in small green steps, reviewed until clean | +| `rs` | Short alias for `raystack`: `/rs refactor`, `/rs scan all`, `/rs project new` | | 14 reviews | See [What it does](#what-it-does) | | `aws-readonly-setup` | Creates a read-only AWS profile for the AWS reviews | | `local-verify` | Checks a user-facing change in a real local browser before it ships | @@ -525,7 +527,7 @@ Or in a terminal: rm -rf ~/.raystack ~/.raystack-installs # then the checkout and its list ``` -`--uninstall` removes exactly the thirty-two RayStack skills and nothing else. +`--uninstall` removes exactly the thirty-four RayStack skills and nothing else. ## Credits diff --git a/VERSION b/VERSION index 6085e94..f0bb29e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.1 +1.3.0 diff --git a/docs/agents.md b/docs/agents.md index 9c6437b..646838d 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -1,6 +1,6 @@ # Agent support -RayStack ships the same thirty-two skills (`skills//SKILL.md`) to every agent. Each agent gets +RayStack ships the same thirty-four skills (`skills//SKILL.md`) to every agent. Each agent gets a manifest only when it has a documented plugin format. Researched 2026-10-28. Anything marked **unverified** was not confirmed against official docs or a local run. diff --git a/docs/guide.md b/docs/guide.md index 1754d33..651ab0f 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -19,7 +19,7 @@ and all supported agents are in the [README](../README.md#install). To update after a release, type `/raystack update`, or run `~/.raystack/install.sh --update`. That refreshes every global and project install made from `~/.raystack`. -RayStack has one front door, `/raystack`, and two entry points behind it: `/dev` for building +RayStack has one front door, `/raystack` (`/rs` is the same, shorter), and two entry points behind it: `/dev` for building and `/audit` for reviews. Type any of them with a few words and it routes you. There are three things you can do: @@ -37,6 +37,7 @@ things you can do: | Add missing PRD, architecture, specs, plan to an existing repo | `/raystack project update` (or `project gaps`) | | Check the architecture and plan against the code mid-build | `/raystack project review` | | Move part of the project to another stack | `/raystack project migrate` | +| Refactor the code: DRY, SOLID, missing unit tests, same architecture | `/raystack project refactor` (or `/rs refactor`) | | Build a feature or fix a bug the reviewed way | `/raystack build ` or `/raystack fix ` | | See where a project stands | `/raystack where are we` | | Review the architecture (MVC, CQRS, layers) | `/audit architecture` (or `/audit cqrs`, `/audit mvc`) | diff --git a/install.sh b/install.sh index 787ab11..9343ad4 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Install (or remove) the thirty-two RayStack skills for any agent that reads SKILL.md folders. +# Install (or remove) the thirty-four RayStack skills for any agent that reads SKILL.md folders. set -eu # Resolve symlinks to this script so ROOT is the real checkout. @@ -11,14 +11,14 @@ done ROOT=$(cd "$(dirname "$self")" && pwd -P) # Skills earlier versions shipped; removed from the target only when RayStack installed them. RETIRED="blog blog-topics blog-draft humanize blog-cover" -SKILLS="raystack audit aws-billing-review aws-security-review aws-bottleneck-review infra-review scaling-review database-review app-security-review performance-review pipeline-security-review aws-readonly-setup principle-ship principle-smallest-change principle-ask-with-question-tool principle-secrets-layout local-verify dev dev-hub dev-research dev-prd dev-spec dev-plan dev-build dev-gaps dev-review dev-migrate architecture-review frontend-review reliability-review codebase-review cve-review" +SKILLS="raystack audit aws-billing-review aws-security-review aws-bottleneck-review infra-review scaling-review database-review app-security-review performance-review pipeline-security-review aws-readonly-setup principle-ship principle-smallest-change principle-ask-with-question-tool principle-secrets-layout local-verify dev dev-hub dev-research dev-prd dev-spec dev-plan dev-build dev-gaps dev-review dev-migrate dev-refactor architecture-review frontend-review reliability-review codebase-review cve-review rs" usage() { cat <<'USAGE' Usage: install.sh [--agent all||custom] [--dir ] [--project []] [--link] [--uninstall] [--update] [--dry-run] [--yes] [--force] [-h|--help] -Copies the thirty-two RayStack skills into an agent's skills directory (default agent: claude). +Copies the thirty-four RayStack skills into an agent's skills directory (default agent: claude). Agents: claude codex cursor grok antigravity gemini copilot windsurf cline kiro roo kilo amp goose opencode qwen trae junie augment warp zed openhands continue openclaw clawdbot moltbot. @@ -30,7 +30,7 @@ clawdbot moltbot. --update pull the latest RayStack into this checkout, then reinstall every place this checkout installed to (global and project); other flags are ignored --link symlink to this checkout instead of copying - --uninstall remove exactly the thirty-two RayStack skills from the target + --uninstall remove exactly the thirty-four RayStack skills from the target --dry-run print what would happen, change nothing --force also replace/remove same-named folders RayStack did not install Same-named folders without the .raystack-installed marker (or a link into this checkout) @@ -215,6 +215,7 @@ skip_foreign() { ours "$1" "$2" && return 1 [ "$force" = 0 ] || return 1 echo "skipping $1: not installed by RayStack (use --force to replace)" >&2 + [ "$2" != rs ] || echo "note: /raystack still works without the /rs alias" >&2 skipped=1 } diff --git a/plugin.json b/plugin.json index b070b94..a1f2a20 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.2.1/plugin.schema.json", "name": "raystack", - "version": "1.2.1", + "version": "1.3.0", "description": "Skills by Harun R. Rayhan: a full development flow (new project intake, research, PRD, specs, plans, reviewed builds, ship; gap-fill, design review and migration for existing projects), read-only scans and reviews (security, performance, scalability, reliability and availability, architecture incl. MVC and CQRS, frontend, code health, CI/CD pipelines, AWS, infrastructure, databases), working principles and local browser verification.", "author": { "name": "Harun R. Rayhan", diff --git a/skills/dev-gaps/SKILL.md b/skills/dev-gaps/SKILL.md index 533b4ba..64bca4e 100644 --- a/skills/dev-gaps/SKILL.md +++ b/skills/dev-gaps/SKILL.md @@ -56,4 +56,4 @@ A drifted document is updated, never rewritten: change the lines that are wrong, Report the table again with the new status, the files written, the B-ID count, open `[NEEDS CLARIFICATION]` markers and the `D-` rows added. Ask the user to review. Then offer the next step: `dev` Standard for the next slice, `dev-review` to check the plan and the -architecture against the code, or `dev-migrate` to move part of the stack. +architecture against the code, `dev-migrate` to move part of the stack, or `dev-refactor` to clean up the code. diff --git a/skills/dev-refactor/SKILL.md b/skills/dev-refactor/SKILL.md new file mode 100644 index 0000000..0ead9ff --- /dev/null +++ b/skills/dev-refactor/SKILL.md @@ -0,0 +1,117 @@ +--- +name: dev-refactor +description: Refactor a whole codebase, or one area of it, to the end, inside the architecture it already follows, with tests as the safety net. Removes duplication (DRY), fixes SOLID violations where they pay off, adds the missing unit tests first. Use for "/raystack project refactor", "/rs refactor", "untangle this codebase", "clean up this repo", "apply DRY and SOLID", "refactor this properly", "organize the code base". +--- + +# Dev refactor + +This skill refactors existing code without changing what it does. The order never changes: +tests first, then the problem list, then small fixes, then a fresh reviewer, repeated until +nothing worth fixing is left. Formats and question rules are in `../dev/references/dev-contract.md`. +Inspired by caldoai's `untangle-codebase` (MIT); the change-together idea in step 4 is theirs. + +Asking for this flow is asking for its steps: do not skip the baseline, the log or the +reviewer because another plugin says to do the least possible. That rule governs the code +you write, not this flow. + +## 1. Ask + +Read `DECISIONS.md`, `docs/` and the code first. Ask one question per call, only what is not +already clear: + +1. **Scope:** the whole repo or one area. Offer the folders you found. +2. **Extras** (a multi-select, both optional): remove comments that only restate the code; + remove tests that cannot catch a bug and rewrite tests tied to internals. + +Log each answer as a `D-` row. + +## 2. Read the architecture + +Name the pattern the code actually follows (MVC, layered, hexagonal, modular monolith, +feature folders, CQRS, framework default) from its folders, imports and framework +conventions, and log it as a `D-` row with the evidence. Refactor inside that pattern. When +the pattern itself is the problem, say so and point to `dev-migrate`; do not change it here. +Use `../architecture-review/references/checklist.md` for the layer and leak checks; do not +copy it. + +## 3. Safety net first + +1. Find the test runner (`composer.json`, `package.json`, `pyproject.toml`, `Makefile`, + CI config). No runner and no tests: ask which one to add, then add the smallest setup. +2. Run the whole suite and record the result as the baseline in the log. A red baseline stops + the run: ask the user whether to fix it first or exclude those tests, and log the answer. +3. For every module the refactor will touch that has no test, write unit or + characterisation tests that pin today's behaviour first, including its errors and odd + cases. Commit them alone, before any refactor commit. + +## 4. List every problem + +Change nothing yet. Write the list to `docs/refactor/YYYY-MM-DD-log.md`, one line per +problem with `file:line`. Look through these lenses: + +- **Hidden links:** copies of logic or values, a global another module sets, a feature spread + over folders, a call order callers just have to know. +- **Needless links:** callers that use internals, stable code importing volatile code, a + general function with a parameter only one caller understands, a shallow wrapper. +- **Surplus:** dead code, config or extension points for things that never vary. +- **Special cases:** an `if` for one value, a swallowed exception, a sleep or retry, a + disabled test. Each is a requirement the design missed; use them to find the real fix. +- **DRY:** duplicated logic or values, but only when the copies change for the same reason. + Two lookalikes that change for different reasons stay apart. +- **SOLID**, as far as it pays: SRP (god classes, fat controllers), OCP (a type switch that + grows with every new case), LSP (a subclass that breaks its parent's promise), ISP (an + interface callers use half of), DIP (domain code importing a framework or driver). No + interface with one implementation, no extension point nobody uses. + +## 5. Redesign + +Work out the fixes as if today's requirements had been known from the start. Look for the +one fix behind several problems. Write each fix above the problems it solves: + +```md +- Extract a `PaymentProvider` interface that Stripe and PayPal implement, so checkout and + refunds stop knowing which one runs. + - `checkout/submit.ts:9` imports `payments/stripe.ts` directly. + - `refunds/refund.ts:22` wraps every Stripe call in `if (order.provider === "paypal")`. +``` + +## 6. Fix in small steps + +One fix group per commit. Run the suite after each; a red run is fixed or reverted before the +next step. Behaviour stays the same: never weaken a test, raise a timeout or add a +suppression to get green. Remove the special cases the redesign made unnecessary and keep +the ones the system still needs. + +With the extras chosen, do them last, after the code is settled: delete comments that only +restate the implementation (keep comments about something outside the code), and remove +tests that repeat the implementation or cannot fail. + +## 7. Check and repeat + +Start a fresh read-only reviewer subagent (two when the repo is large, split by folder) with +the log, the diff and the problem lenses; it reports problems still worth fixing. When the +host has no subagents, re-read the diff yourself against the lenses and say so in the log. +Fix what is real and repeat. Done when: + +- the full suite is green and matches or beats the baseline; +- every touched module has unit tests; +- a typical change lands in one place; +- no import points at something more volatile than itself; +- the reviewer finds nothing worth fixing; +- behaviour is unchanged, apart from bugs the refactor fixed and logged. + +## 8. Ship + +Follow `principle-ship`: PR, self-review, green CI, merge, confirm the deploy. In the log +write `Shipped: ` (or `Committed:` with no remote, per the contract). Tell the user the +log path and the before and after counts: special cases, duplicated blocks, untested +modules, tests added. + +## Tips + +- Script away function bodies (a skeleton) so the whole repo fits in context. +- Have subagents summarise folders so you can reason across all of it. +- Script an import graph and rerun it as you go. +- Use `git log --name-only` to see which files change together; that is the evidence for + hidden links. +- Keep the log current; it survives a context compaction. diff --git a/skills/dev/SKILL.md b/skills/dev/SKILL.md index 2b30dfc..47031e5 100644 --- a/skills/dev/SKILL.md +++ b/skills/dev/SKILL.md @@ -1,6 +1,6 @@ --- name: dev -description: Entry point for the RayStack development flow. Use for "/dev", "/raystack new project", "/raystack project new", "/raystack project update", "/raystack project gaps", "/raystack project review", "/raystack project migrate", "start a new project", "plan this feature", "build this", "fix this bug properly", "where are we", or any request to take work from idea to shipped code. +description: Entry point for the RayStack development flow. Use for "/dev", "/raystack new project", "/raystack project new", "/raystack project update", "/raystack project gaps", "/raystack project review", "/raystack project migrate", "/raystack project refactor", "start a new project", "plan this feature", "build this", "fix this bug properly", "where are we", or any request to take work from idea to shipped code. --- # Dev @@ -26,6 +26,7 @@ a step, by saying so. | `dev-gaps` | Finds and fills missing documents in an existing project | | `dev-review` | Read-only review of architecture, specs and plan against the code | | `dev-migrate` | Moves part of a project to another stack, step by step | +| `dev-refactor` | Refactors a codebase inside its own architecture, tests first, DRY and SOLID | **Project commands** (`/raystack project `, or `new project`) go straight to: @@ -35,6 +36,7 @@ a step, by saying so. | `update`, `gaps` | `dev-gaps` | | `review` | `dev-review` | | `migrate` | `dev-migrate` | +| `refactor` | `dev-refactor` | ## 1. Detect state diff --git a/skills/raystack/SKILL.md b/skills/raystack/SKILL.md index 2a34581..d36a857 100644 --- a/skills/raystack/SKILL.md +++ b/skills/raystack/SKILL.md @@ -1,12 +1,12 @@ --- name: raystack -description: Entry point for everything in RayStack. Use for "/raystack", "what can RayStack do", development work ("new project", "project new", "project update", "project gaps", "project review", "project migrate", "start a project", "write a PRD", "plan this", "build this", "fix this bug properly") and read-only reviews ("review my AWS", "review my database", "audit my stack", "which review should I run", cloud cost, security, bottlenecks, deploys, scaling, databases, app code security, performance, N+1 queries, CI/CD security or GitHub Actions security, architecture, CQRS, MVC, frontend, reliability, availability, code quality or tech debt), and scans that run several reviews as one job ("scan", "scan all", "scan security"), and updating RayStack ("update raystack"). +description: Entry point for everything in RayStack. Use for "/raystack", "what can RayStack do", development work ("new project", "project new", "project update", "project gaps", "project review", "project migrate", "project refactor", "refactor this", "start a project", "write a PRD", "plan this", "build this", "fix this bug properly") and read-only reviews ("review my AWS", "review my database", "audit my stack", "which review should I run", cloud cost, security, bottlenecks, deploys, scaling, databases, app code security, performance, N+1 queries, CI/CD security or GitHub Actions security, architecture, CQRS, MVC, frontend, reliability, availability, code quality or tech debt), and scans that run several reviews as one job ("scan", "scan all", "scan security"), and updating RayStack ("update raystack"). --- # RayStack RayStack is Harun R. Rayhan's skill pack for building and reviewing software. This is its front -door: it works out what the user wants and runs the right skills. +door (`/rs` is the same entry point with a shorter name): it works out what the user wants and runs the right skills. | Job | Skill | | --- | --- | @@ -20,7 +20,7 @@ Four principle skills load on their own; this router never runs them: `principle ## 0. Pick the job (in this order; the first match wins) - The request starts with "scan" (`/raystack scan all`), or uses a scan phrase ("security scan", "performance scan", "scan and fix", "full audit", "check everything"): hand off to `audit` with the words. "scan" elsewhere in a request ("build a barcode scan screen") is not this rule. -- `project new|update|gaps|review|migrate`, `new project`, or "review the plan" or "review the spec": `dev`, project commands (`dev-review` for the last two). +- `project new|update|gaps|review|migrate|refactor`, `new project`, or "review the plan" or "review the spec": `dev`, project commands (`dev-review` for the last two). - `/raystack update`, "update raystack", "which version": follow [references/update.md](references/update.md). - Any other request that starts with "review" or "audit" (`/raystack review architecture`): hand off to `audit` with the words. - Words about making or changing something ("start", "PRD", "plan", "build", "fix", "where are we"): hand off to `dev` and stop here, even when told to do the least possible (see `dev`). diff --git a/skills/rs/SKILL.md b/skills/rs/SKILL.md new file mode 100644 index 0000000..35c8639 --- /dev/null +++ b/skills/rs/SKILL.md @@ -0,0 +1,13 @@ +--- +name: rs +description: Short alias for /raystack, the entry point for everything in RayStack. Use for "/rs", "/rs refactor", "/rs scan all", "/rs project new", "/rs build", "/rs review", "/rs update", or any request that starts with rs followed by a RayStack job. +--- + +# rs + +`/rs` is `/raystack` with a shorter name. It has no logic of its own: hand the user's words +to `raystack` unchanged and stop. `/rs refactor` runs `/raystack project refactor`. + +Claude Code: invoke the Skill tool with the user's words in the args, by the name your agent +lists (`raystack:raystack` in a plugin install, else `raystack`). Other agents: read +`../raystack/SKILL.md` and follow it. diff --git a/tests/check-linkage.sh b/tests/check-linkage.sh index 6f65958..b714e37 100644 --- a/tests/check-linkage.sh +++ b/tests/check-linkage.sh @@ -372,7 +372,7 @@ check "router: scan rule lists the scan phrases" grep -qF '"security scan", "per check "router: scan elsewhere does not win" grep -qF '"scan" elsewhere in a request' "$rt" check "router: scan line comes before the build line" test "$(grep -n 'The request starts with "scan"' "$rt" | head -1 | cut -d: -f1)" -lt "$(grep -n 'Words about making or changing' "$rt" | head -1 | cut -d: -f1)" # project commands: dev-gaps, dev-review, dev-migrate, intake -for sk in dev-gaps dev-review dev-migrate; do +for sk in dev-gaps dev-review dev-migrate dev-refactor; do f=skills/$sk/SKILL.md check "$sk is 120 lines or fewer" test "$(wc -l < "$f")" -le 120 check "$sk links the contract" grep -qF '../dev/references/dev-contract.md' "$f" @@ -380,12 +380,16 @@ done check "dev-review has the read-only guard" grep -qF 'readonly-guard.sh' skills/dev-review/SKILL.md check "dev-review writes project-review report" grep -qF 'reviews/YYYY-MM-DD-project-review.md' skills/dev-review/SKILL.md check "dev-migrate uses parity tests" grep -qF '## Parity tests' skills/dev-migrate/SKILL.md +check "dev-refactor runs tests first" grep -qF "## 3. Safety net first" skills/dev-refactor/SKILL.md +check "dev-refactor keeps a log" grep -qF "docs/refactor/YYYY-MM-DD-log.md" skills/dev-refactor/SKILL.md +check "dev-refactor stays in the architecture" grep -qF "dev-migrate" skills/dev-refactor/SKILL.md +check "rs hands off to raystack" grep -qF "../raystack/SKILL.md" skills/rs/SKILL.md check "dev-gaps lists drift" grep -qF 'Drifted means' skills/dev-gaps/SKILL.md check "intake asks one question per call" grep -qF 'one question per call' skills/dev/references/intake.md -for v in new update gaps review migrate; do - check "router names project $v" grep -qF "$v" <(grep -F 'project new|update|gaps|review|migrate' skills/raystack/SKILL.md) +for v in new update gaps review migrate refactor; do + check "router names project $v" grep -qF "$v" <(grep -F 'project new|update|gaps|review|migrate|refactor' skills/raystack/SKILL.md) done -for s in dev-gaps dev-review dev-migrate references/intake.md; do +for s in dev-gaps dev-review dev-migrate dev-refactor references/intake.md; do check "dev maps project command to $s" grep -qF "$s" skills/dev/SKILL.md done for v in reliability architecture code frontend; do diff --git a/tests/check-skills.sh b/tests/check-skills.sh index 988cfe9..3a3c65d 100755 --- a/tests/check-skills.sh +++ b/tests/check-skills.sh @@ -3,7 +3,7 @@ cd "$(dirname "$0")/.." || exit 1 . tests/lib.sh REQUIRED=(raystack aws-billing-review aws-security-review aws-bottleneck-review infra-review scaling-review database-review app-security-review performance-review pipeline-security-review aws-readonly-setup - principle-ship principle-smallest-change principle-ask-with-question-tool principle-secrets-layout local-verify dev dev-hub dev-research dev-prd dev-spec dev-plan dev-build dev-gaps dev-review dev-migrate architecture-review frontend-review reliability-review codebase-review) + principle-ship principle-smallest-change principle-ask-with-question-tool principle-secrets-layout local-verify dev dev-hub dev-research dev-prd dev-spec dev-plan dev-build dev-gaps dev-review dev-migrate dev-refactor rs architecture-review frontend-review reliability-review codebase-review) for s in "${REQUIRED[@]}"; do [ -f "skills/$s/SKILL.md" ] || fail "missing skill $s" diff --git a/tests/test-install.sh b/tests/test-install.sh index 72981a3..f8954da 100644 --- a/tests/test-install.sh +++ b/tests/test-install.sh @@ -6,7 +6,7 @@ cd "$(dirname "$0")/.." || exit 1 . ./tests/lib.sh INSTALL=./install.sh if [ ! -f "$INSTALL" ]; then fail "install.sh missing"; exit 1; fi -SKILLS="raystack audit aws-billing-review aws-security-review aws-bottleneck-review infra-review scaling-review database-review app-security-review performance-review pipeline-security-review aws-readonly-setup principle-ship principle-smallest-change principle-ask-with-question-tool principle-secrets-layout local-verify dev dev-hub dev-research dev-prd dev-spec dev-plan dev-build dev-gaps dev-review dev-migrate architecture-review frontend-review reliability-review codebase-review cve-review" +SKILLS="raystack audit aws-billing-review aws-security-review aws-bottleneck-review infra-review scaling-review database-review app-security-review performance-review pipeline-security-review aws-readonly-setup principle-ship principle-smallest-change principle-ask-with-question-tool principle-secrets-layout local-verify dev dev-hub dev-research dev-prd dev-spec dev-plan dev-build dev-gaps dev-review dev-migrate dev-refactor architecture-review frontend-review reliability-review codebase-review cve-review rs" T=$(mktemp -d) trap 'rm -rf "$T"' EXIT export RAYSTACK_REGISTRY="$T/registry" @@ -16,7 +16,7 @@ count() { n=0; for s in $SKILLS; do [ -e "$1/$s/SKILL.md" ] && n=$((n + 1)); don # install twice into a dir that holds another skill D="$T/a"; mkdir -p "$D/other-skill"; echo keep > "$D/other-skill/SKILL.md" bash "$INSTALL" --dir "$D" >/dev/null; assert_eq "$?" 0 "install exits 0" -assert_eq "$(count "$D")" 32 "all thirty-two skills installed" +assert_eq "$(count "$D")" 34 "all thirty-four skills installed" assert_eq "$(cat "$D/other-skill/SKILL.md")" keep "other-skill intact" assert_eq "$(find "$D" -maxdepth 1 -name ".raystack-tmp*" | wc -l | tr -d " ")" 0 "no staging folder left behind" cp -R "$D" "$T/snap" @@ -31,7 +31,7 @@ assert_eq "$(gl "$IC")" 0 "installed guard allows its own gitleaks config" assert_eq "$(gl "$(pwd -P)/skills/raystack/references/gitleaks.toml")" 2 "installed guard denies the checkout's config" [ ! -e "$D/.denylist" ] && [ ! -e "$D/hooks" ] && [ ! -e "$D/scripts" ] && [ ! -e "$D/docs" ] && [ ! -e "$D/raystack/.denylist" ] \ && pass "nothing outside skills/ copied" || fail "extra files copied" -assert_eq "$(ls "$D" | wc -l | tr -d ' ')" 33 "target holds thirty-two skills plus other-skill" +assert_eq "$(ls "$D" | wc -l | tr -d ' ')" 35 "target holds thirty-four skills plus other-skill" # uninstall removes only ours bash "$INSTALL" --dir "$D" --uninstall >/dev/null @@ -42,7 +42,7 @@ assert_eq "$(ls "$D")" other-skill "uninstall left other-skill" L="$T/l" bash "$INSTALL" --dir "$L" --link >/dev/null 2>&1 links=0; for s in $SKILLS; do [ -L "$L/$s" ] && links=$((links + 1)); done -assert_eq "$links" 32 "--link makes thirty-two symlinks" +assert_eq "$links" 34 "--link makes thirty-four symlinks" bash "$INSTALL" --dir "$L" >/dev/null assert_eq "$([ -L "$L/raystack" ] && echo link || echo dir)" dir "copy replaces a symlink with a real dir" bash "$INSTALL" --dir "$L" --uninstall >/dev/null @@ -54,7 +54,7 @@ bash "$INSTALL" --dir "$T/dry" --dry-run >/dev/null bash "$INSTALL" --dir "$D" --uninstall --dry-run >/dev/null bash "$INSTALL" --dir "$D" >/dev/null bash "$INSTALL" --dir "$D" --uninstall --dry-run >/dev/null -assert_eq "$(count "$D")" 32 "--uninstall --dry-run removes nothing" +assert_eq "$(count "$D")" 34 "--uninstall --dry-run removes nothing" # refusals bash "$INSTALL" --dir / >/dev/null 2>&1; assert_eq "$?" 1 "refuses /" @@ -66,7 +66,7 @@ bash "$INSTALL" --agent nope >/dev/null 2>&1; assert_eq "$?" 1 "unknown agent" # agent default dir, caveat, works from another cwd H="$T/home"; mkdir -p "$H" out=$(cd "$T" && HOME="$H" bash "$OLDPWD/$INSTALL" --agent codex 2>&1) -assert_eq "$(count "$H/.agents/skills")" 32 "codex default dir" +assert_eq "$(count "$H/.agents/skills")" 34 "codex default dir" case "$out" in *docs/agents.md*) pass "prints guard caveat";; *) fail "no guard caveat";; esac case "$out" in *"type /raystack"*) pass "prints the next step";; *) fail "no next step";; esac case "$out" in *"grok plugin marketplace"*) fail "stale grok tip";; *) pass "no stale grok tip";; esac @@ -76,24 +76,24 @@ assert_eq "$(sed -n 2p "$T/portable/raystack/.raystack-installed")" "source=~/rs # --agent all installs for each agent found in $HOME; Grok only when it has no Claude or Cursor folder to read. HA="$T/homeall"; mkdir -p "$HA/.claude" "$HA/.codex" "$HA/.grok" outall=$(HOME="$HA" bash ./install.sh --agent all --yes 2>&1) -assert_eq "$(count "$HA/.claude/skills")" 32 "all: claude" -assert_eq "$(count "$HA/.agents/skills")" 32 "all: codex" +assert_eq "$(count "$HA/.claude/skills")" 34 "all: claude" +assert_eq "$(count "$HA/.agents/skills")" 34 "all: codex" [ ! -d "$HA/.grok/skills" ] && pass "all: grok reads ~/.claude/skills" || fail "all: grok got its own copy" case "$outall" in *"Installed for: Claude Code, Codex."*) pass "all: summary";; *) fail "all: summary (got $outall)";; esac HN="$T/homenone"; mkdir -p "$HN"; HOME="$HN" bash ./install.sh --agent all --yes >/dev/null 2>&1 -assert_eq "$(count "$HN/.claude/skills")" 32 "all: claude when no agent found" +assert_eq "$(count "$HN/.claude/skills")" 34 "all: claude when no agent found" # Agents that share a folder get one copy; each agent maps to its own global folder. HS="$T/homeshared"; mkdir -p "$HS/.codex" "$HS/.cline" "$HS/.codeium/windsurf" "$HS/.gemini/antigravity" outs=$(HOME="$HS" bash ./install.sh --agent all --yes 2>&1) -assert_eq "$(count "$HS/.codeium/windsurf/skills")" 32 "all: windsurf folder" -assert_eq "$(count "$HS/.gemini/antigravity/skills")" 32 "all: antigravity folder" -assert_eq "$(count "$HS/.gemini/skills")" 32 "all: gemini folder" +assert_eq "$(count "$HS/.codeium/windsurf/skills")" 34 "all: windsurf folder" +assert_eq "$(count "$HS/.gemini/antigravity/skills")" 34 "all: antigravity folder" +assert_eq "$(count "$HS/.gemini/skills")" 34 "all: gemini folder" case "$outs" in *"same folder: Cline"*) pass "all: shared folder named";; *) fail "all: shared folder not named";; esac HOME="$T/x" bash ./install.sh --agent nosuch >/dev/null 2>&1 && fail "unknown agent accepted" || pass "unknown agent rejected" P="$T/projgem"; mkdir -p "$P"; HOME="$T/x" bash ./install.sh --agent gemini --project "$P" >/dev/null 2>&1 -assert_eq "$(count "$P/.agents/skills")" 32 "gemini project folder" +assert_eq "$(count "$P/.agents/skills")" 34 "gemini project folder" HO="$T/homeclaw"; mkdir -p "$HO/.openclaw"; HOME="$HO" bash ./install.sh --agent all --yes >/dev/null 2>&1 -assert_eq "$(count "$HO/.openclaw/skills")" 32 "all: openclaw folder" +assert_eq "$(count "$HO/.openclaw/skills")" 34 "all: openclaw folder" bash -n get.sh && pass "get.sh parses" || fail "get.sh syntax" [ "$(tail -1 get.sh | cut -c1-10)" = 'main "$@" ' ] && pass "get.sh runs main last" || fail "get.sh must end with main" case "$out" in *"codex plugin"*) pass "prints plugin hint";; *) fail "no plugin hint";; esac @@ -104,7 +104,7 @@ err=$(bash "$INSTALL" --dir "$F" 2>&1 >/dev/null); rc=$? assert_eq "$rc" 1 "install exits 1 when a foreign dir is skipped" assert_eq "$(cat "$F/infra-review/notes.txt")" mine "foreign dir preserved by install" case "$err" in *"skipping $F/infra-review: not installed by RayStack (use --force to replace)"*) pass "skip warning";; *) fail "no skip warning: $err";; esac -assert_eq "$(count "$F")" 31 "other thirty-one still installed" +assert_eq "$(count "$F")" 33 "other thirty-three still installed" bash "$INSTALL" --dir "$F" --uninstall >/dev/null 2>&1; assert_eq "$?" 1 "uninstall exits 1 on foreign dir" assert_eq "$(cat "$F/infra-review/notes.txt")" mine "foreign dir preserved by uninstall" assert_eq "$(find "$F" -mindepth 1 -maxdepth 1 | wc -l | tr -d " ")" 1 "uninstall removed only ours" @@ -126,8 +126,18 @@ bash "$INSTALL" --dir "$R" --uninstall >/dev/null 2>&1 [ ! -e "$R/blog-draft" ] && pass "uninstall removes retired skills it installed" || fail "uninstall left retired skill" assert_eq "$(cat "$R/blog-cover/notes.txt")" mine "uninstall keeps foreign retired-name dir" +# a foreign rs skill is skipped with a note; raystack itself still installs +RS="$T/rs"; mkdir -p "$RS/rs"; RS=$(cd "$RS" && pwd -P); echo mine > "$RS/rs/notes.txt" +err=$(bash "$INSTALL" --dir "$RS" 2>&1 >/dev/null); rc=$? +assert_eq "$rc" 1 "foreign rs: install exits 1" +assert_eq "$(cat "$RS/rs/notes.txt")" mine "foreign rs preserved" +case "$err" in *"/raystack still works"*) pass "foreign rs note";; *) fail "no rs note: $err";; esac +[ -f "$RS/raystack/SKILL.md" ] && pass "raystack installed beside foreign rs" || fail "raystack missing" +bash "$INSTALL" --dir "$RS" --force >/dev/null 2>&1 +[ -f "$RS/rs/.raystack-installed" ] && pass "--force replaces foreign rs" || fail "--force kept foreign rs" + # path with spaces -bash "$INSTALL" --dir "$T/with space/skills" >/dev/null; assert_eq "$(count "$T/with space/skills")" 32 "dir with spaces" +bash "$INSTALL" --dir "$T/with space/skills" >/dev/null; assert_eq "$(count "$T/with space/skills")" 34 "dir with spaces" # source tree and HOME/. refused bash "$INSTALL" --dir "$PWD/skills" >/dev/null 2>&1; assert_eq "$?" 1 "refuses source skills dir" @@ -166,14 +176,14 @@ bash "$INSTALL" --dir "$T/bad\"dir" >/dev/null 2>&1; assert_eq "$?" 1 "refuses q # symlinked install.sh still finds the checkout mkdir -p "$T/bin"; ln -s "$PWD/install.sh" "$T/bin/install.sh" -bash "$T/bin/install.sh" --dir "$T/viaLink" >/dev/null; assert_eq "$(count "$T/viaLink")" 32 "works via symlinked install.sh" +bash "$T/bin/install.sh" --dir "$T/viaLink" >/dev/null; assert_eq "$(count "$T/viaLink")" 34 "works via symlinked install.sh" # --project installs into the repo's agent folder (default: current folder) P="$T/repo"; mkdir -p "$P" -bash "$INSTALL" --project "$P" >/dev/null; assert_eq "$(count "$P/.claude/skills")" 32 "--project installs to .claude/skills" -bash "$INSTALL" --agent codex --project "$P" >/dev/null; assert_eq "$(count "$P/.agents/skills")" 32 "--agent codex --project installs to .agents/skills" +bash "$INSTALL" --project "$P" >/dev/null; assert_eq "$(count "$P/.claude/skills")" 34 "--project installs to .claude/skills" +bash "$INSTALL" --agent codex --project "$P" >/dev/null; assert_eq "$(count "$P/.agents/skills")" 34 "--agent codex --project installs to .agents/skills" P2="$T/repo2"; mkdir -p "$P2"; I=$PWD/install.sh -(cd "$P2" && bash "$I" --project >/dev/null); assert_eq "$(count "$P2/.claude/skills")" 32 "--project with no path uses the current folder" +(cd "$P2" && bash "$I" --project >/dev/null); assert_eq "$(count "$P2/.claude/skills")" 34 "--project with no path uses the current folder" bash "$INSTALL" --project "$T/missing" >/dev/null 2>&1; assert_eq "$?" 1 "--project refuses a missing folder" grep -q "^$(cd "$P" && pwd -P)/.claude/skills 0$" "$RAYSTACK_REGISTRY" && pass "registry records the project target" || fail "registry missing project target" bash "$INSTALL" --project "$P" --uninstall >/dev/null