@ericrisco/rsc 1.1.5 → 1.2.0
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/README.md +72 -14
- package/manifest.json +888 -231
- package/package.json +3 -2
- package/schema/ecc-originality.json +16 -0
- package/schema/ecc-originality.u64 +0 -0
- package/scripts/build-manifest.js +43 -3
- package/scripts/doctor.js +47 -1
- package/scripts/install-apply.js +148 -31
- package/scripts/lib/capabilities.js +21 -0
- package/scripts/lib/domains.js +3 -3
- package/scripts/lib/manifest-file.js +4 -1
- package/scripts/originality-check.js +137 -0
- package/scripts/rsc.js +122 -14
- package/skills/design/SKILL.md +3 -3
- package/skills/design/references/design-systems.md +1 -1
- package/skills/design/references/inspiration-sources.md +2 -2
- package/skills/design/references/motion-and-interaction.md +2 -2
- package/skills/design-dna/evals/cases.yaml +1 -1
- package/skills/design-loop/SKILL.md +3 -3
- package/skills/design-loop/evals/cases.yaml +3 -3
- package/skills/expo/SKILL.md +1 -1
- package/skills/motion-craft/SKILL.md +125 -0
- package/skills/motion-craft/evals/cases.yaml +38 -0
- package/skills/motion-craft/scripts/verify.sh +75 -0
- package/skills/react/SKILL.md +2 -2
- package/skills/react-native/SKILL.md +1 -1
- package/skills/swift-ios/SKILL.md +2 -2
- package/skills/ui-engineering/SKILL.md +114 -0
- package/skills/ui-engineering/evals/cases.yaml +38 -0
- package/skills/variant-explorer/SKILL.md +93 -0
- package/skills/variant-explorer/evals/cases.yaml +38 -0
- package/targets/agent-catalog.js +220 -0
- package/targets/agent-sources.json +48 -0
- package/targets/agents.js +61 -13
- package/targets/commands.js +212 -0
- package/targets/memory.js +276 -0
- package/targets/opencode-memory-plugin.js +65 -0
- package/targets/session-memory-adapter.mjs +117 -0
- package/targets/session-memory-core.mjs +477 -0
- package/targets/session-memory.mjs +27 -0
- package/skills/animate/SKILL.md +0 -851
- package/skills/animate/evals/cases.yaml +0 -36
- package/skills/animate/references/recipes.md +0 -324
- package/skills/animate-expo/SKILL.md +0 -281
- package/skills/animate-expo/evals/cases.yaml +0 -36
- package/skills/animate-expo/references/recipes.md +0 -385
- package/skills/animation-vocabulary/SKILL.md +0 -189
- package/skills/animation-vocabulary/evals/cases.yaml +0 -31
- package/skills/apple-design/SKILL.md +0 -301
- package/skills/apple-design/evals/cases.yaml +0 -33
- package/skills/ask-sonner/SKILL.md +0 -104
- package/skills/ask-sonner/evals/cases.yaml +0 -33
- package/skills/ask-sonner/references/api.md +0 -64
- package/skills/design-eng/SKILL.md +0 -88
- package/skills/design-eng/evals/cases.yaml +0 -36
- package/skills/find-animation-opportunities/SKILL.md +0 -150
- package/skills/find-animation-opportunities/evals/cases.yaml +0 -31
- package/skills/improve-animations/SKILL.md +0 -126
- package/skills/improve-animations/evals/cases.yaml +0 -31
- package/skills/improve-animations/references/audit.md +0 -115
- package/skills/improve-animations/references/plan-template.md +0 -73
- package/skills/pick-ui-library/SKILL.md +0 -101
- package/skills/pick-ui-library/evals/cases.yaml +0 -33
- package/skills/pick-ui-library/references/component-galleries.md +0 -59
- package/skills/prototype/SKILL.md +0 -113
- package/skills/prototype/evals/cases.yaml +0 -31
- package/skills/prototype/references/picker.md +0 -197
- package/skills/review-animations/SKILL.md +0 -188
- package/skills/review-animations/evals/cases.yaml +0 -34
- package/skills/review-animations/references/standards.md +0 -187
- package/skills/review-animations/scripts/verify.sh +0 -164
- package/skills/write-swift/SKILL.md +0 -413
- package/skills/write-swift/evals/cases.yaml +0 -36
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# verify.sh — the never-animate list of `motion-craft`, made mechanical.
|
|
4
|
+
#
|
|
5
|
+
# The SKILL.md declares rules as binding ("never animate layout", "never
|
|
6
|
+
# `transition: all`"). A rule declared binding without a mechanism that checks it
|
|
7
|
+
# is a decorative gate: it buys the feeling of safety without the safety. So each
|
|
8
|
+
# hard rule in the skill body has a row here, and each row has a fixture in
|
|
9
|
+
# tests/motion-craft-rules.test.js that watches it FAIL and then PASS.
|
|
10
|
+
#
|
|
11
|
+
# Static and network-free. Everything WARNS by default; --strict makes findings
|
|
12
|
+
# fatal, which is what CI should use.
|
|
13
|
+
set -u
|
|
14
|
+
TARGET="${1:-.}"
|
|
15
|
+
STRICT=0
|
|
16
|
+
[ "${2:-}" = "--strict" ] && STRICT=1
|
|
17
|
+
[ "${1:-}" = "--strict" ] && { STRICT=1; TARGET="."; }
|
|
18
|
+
|
|
19
|
+
warn_count=0
|
|
20
|
+
warn() { printf ' ⚠ %s\n' "$1"; warn_count=$((warn_count + 1)); }
|
|
21
|
+
ok() { printf ' ✓ %s\n' "$1"; }
|
|
22
|
+
|
|
23
|
+
# id <TAB> grep -E pattern <TAB> message
|
|
24
|
+
# One row per hard rule in SKILL.md §"What must never animate". Adding a rule to
|
|
25
|
+
# the prose without adding it here is what the test suite refuses to allow.
|
|
26
|
+
#
|
|
27
|
+
# Fields are TAB-separated on purpose: the patterns are extended regexes and are
|
|
28
|
+
# full of `|`, so a pipe delimiter silently truncated every pattern at its first
|
|
29
|
+
# alternation and only the one rule without alternation ever fired.
|
|
30
|
+
rules() {
|
|
31
|
+
printf '%b\n' \
|
|
32
|
+
"transition-all\ttransition:[[:space:]]*all\t\`transition: all\` animates layout by accident — list the exact properties" \
|
|
33
|
+
"layout-anim\ttransition:[^;]*(width|height|top:|left:|right:|bottom:|margin|padding)\tanimating layout properties forces reflow every frame — use transform and opacity" \
|
|
34
|
+
"linear-move\t(transition|animation):[^;]*(transform|translate|scale)[^;]*linear\tlinear easing on positional movement reads as mechanical — nothing physical starts at full speed" \
|
|
35
|
+
"long-duration\t(transition|animation):[^;]*[^0-9]([5-9][0-9][0-9]|[0-9][0-9][0-9][0-9])ms\tover 400ms stops being a transition and becomes a wait"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
files=$(find "$TARGET" \( -name node_modules -o -name .git -o -name dist -o -name build -o -name .next \) -prune -o \
|
|
39
|
+
\( -name '*.css' -o -name '*.scss' -o -name '*.tsx' -o -name '*.jsx' -o -name '*.ts' -o -name '*.js' -o -name '*.vue' -o -name '*.svelte' \) -print 2>/dev/null)
|
|
40
|
+
|
|
41
|
+
echo "motion-craft — never-animate checks"
|
|
42
|
+
if [ -z "$files" ]; then
|
|
43
|
+
echo " (no style or component files found under $TARGET)"
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
while IFS=$'\t' read -r id pattern message; do
|
|
48
|
+
[ -z "$id" ] && continue
|
|
49
|
+
hits=$(printf '%s\n' "$files" | xargs grep -lEi -- "$pattern" 2>/dev/null | head -5)
|
|
50
|
+
if [ -n "$hits" ]; then
|
|
51
|
+
warn "$id: $message"
|
|
52
|
+
printf ' %s\n' $hits
|
|
53
|
+
else
|
|
54
|
+
ok "$id"
|
|
55
|
+
fi
|
|
56
|
+
done <<< "$(rules)"
|
|
57
|
+
|
|
58
|
+
# reduced-motion is a finishing condition, not an edge case: if anything animates
|
|
59
|
+
# at all, the project must say what it does for people who asked for less motion.
|
|
60
|
+
if printf '%s\n' "$files" | xargs grep -lEi -- '(transition|animation|@keyframes)' >/dev/null 2>&1; then
|
|
61
|
+
if printf '%s\n' "$files" | xargs grep -lEi -- 'prefers-reduced-motion' >/dev/null 2>&1; then
|
|
62
|
+
ok "reduced-motion: honoured somewhere"
|
|
63
|
+
else
|
|
64
|
+
warn "reduced-motion: this project animates and never mentions prefers-reduced-motion — unchecked means unfinished"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
echo
|
|
69
|
+
if [ "$warn_count" -gt 0 ]; then
|
|
70
|
+
echo "$warn_count finding(s)."
|
|
71
|
+
[ "$STRICT" -eq 1 ] && exit 1
|
|
72
|
+
else
|
|
73
|
+
echo "No findings."
|
|
74
|
+
fi
|
|
75
|
+
exit 0
|
package/skills/react/SKILL.md
CHANGED
|
@@ -10,8 +10,8 @@ origin: risco
|
|
|
10
10
|
|
|
11
11
|
**Hand-off — picking dependencies.** Which library to reach for (UI primitives, command menus,
|
|
12
12
|
toasts, OTP inputs, charts, virtualization, drag and drop, state, styling, theming) is
|
|
13
|
-
`../
|
|
14
|
-
install is `../
|
|
13
|
+
`../ui-engineering/SKILL.md`'s — build vs adopt is decided there, before hand-rolling a component. Animating what you
|
|
14
|
+
install is `../motion-craft/SKILL.md`'s.
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
> Build or review a fast, typed, correctly-architected **client-side** React single-page app bundled by Vite. No server runtime, no RSC tree, no framework router. Server-rendered React (App Router, server actions, SSR/SSG) is not this skill — that is `../nextjs/SKILL.md`.
|
|
@@ -10,7 +10,7 @@ origin: risco
|
|
|
10
10
|
|
|
11
11
|
**Hand-off — the app vs its motion.** Animation and gestures in a React Native app — Reanimated
|
|
12
12
|
worklets on the UI runtime, gesture handoff, sheets, press feedback, haptics — are
|
|
13
|
-
`../
|
|
13
|
+
`../motion-craft/SKILL.md`'s, whose guidance is platform-agnostic and applies to bare React Native as well as Expo.
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
You write the code that runs *inside* the app: render, navigate, animate, list, persist, and author native modules. Mobile is split across two skills by **verb** — run the verb test before doing anything:
|
|
@@ -12,8 +12,8 @@ origin: risco
|
|
|
12
12
|
**Hand-off — the language vs the platform.** This skill owns the **platform**: SwiftUI/UIKit views,
|
|
13
13
|
app lifecycle, Xcode, iOS APIs, App Store shipping. Language-level Swift — Swift 6 data-race safety
|
|
14
14
|
and approachable concurrency, actors and task groups, `some` vs `any`, ARC and performance, Swift
|
|
15
|
-
Testing, macros — is
|
|
16
|
-
retain cycle
|
|
15
|
+
Testing, macros — is this skill's own ground. A concurrency error, a hang, a data race or a
|
|
16
|
+
retain cycle belongs here. Interface feel, springs and materials: `../motion-craft/SKILL.md`.
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
Native Apple-platform apps: the code is `.swift`, the UI is SwiftUI, and the compiler enforces actor isolation. If the answer is a `@MainActor`, `@Observable`, `async let`, `#expect`, `NavigationStack`, or `@Model`, you are in the right place.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-engineering
|
|
3
|
+
description: "Use when the question is how interface should be BUILT — component boundaries, state that belongs in the URL vs the component, loading/empty/error states, forms, optimistic updates, build vs adopt. NOT the visual system (`design`), NOT framework mechanics (`react`/`nextjs`)."
|
|
4
|
+
tags: [interface, frontend, architecture, components, state, craft]
|
|
5
|
+
recommends: [design, motion-craft]
|
|
6
|
+
profiles: [core, full]
|
|
7
|
+
origin: risco
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# UI engineering — the decisions between the design and the framework
|
|
11
|
+
|
|
12
|
+
There is a layer that neither the design skill nor the framework skill owns. The design says what
|
|
13
|
+
the screen should be. The framework says how its API works. **In between sits every decision that
|
|
14
|
+
makes an interface either durable or a maintenance tax** — and it is where most interface work
|
|
15
|
+
actually goes wrong.
|
|
16
|
+
|
|
17
|
+
Nothing here is framework-specific. These decisions are the same in React, Vue, Svelte, SwiftUI and
|
|
18
|
+
plain HTML; only the syntax moves.
|
|
19
|
+
|
|
20
|
+
## The one rule
|
|
21
|
+
|
|
22
|
+
**State goes to the outermost place that still needs it, and no further.** Almost every interface
|
|
23
|
+
tangle is that rule broken in one of two directions: state hoarded in a component that others need,
|
|
24
|
+
or state hoisted into a global store that only one component ever reads.
|
|
25
|
+
|
|
26
|
+
| Where the state lives | When that is right |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| **The URL** | Anything a user would bookmark, share, or expect the back button to restore: filters, tabs, pagination, the open item |
|
|
29
|
+
| **The server** | Anything that outlives the session or belongs to more than one user |
|
|
30
|
+
| **A shared parent** | Two siblings genuinely need it, and it dies with the screen |
|
|
31
|
+
| **The component** | Nobody else reads it: hover, focus, an open menu, an in-flight draft |
|
|
32
|
+
|
|
33
|
+
The most common and most expensive mistake is the first row: state that belongs in the URL kept in a
|
|
34
|
+
component. It looks fine until someone reloads, shares a link, or presses back — and then the
|
|
35
|
+
interface silently loses what the user did.
|
|
36
|
+
|
|
37
|
+
## Every state, not just the happy one
|
|
38
|
+
|
|
39
|
+
An interface is not finished when it renders data. It is finished when it renders **all five**:
|
|
40
|
+
|
|
41
|
+
| State | The question it answers | The usual failure |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| **Loading** | "Is it working?" | A spinner where the shape is already known — use the shape |
|
|
44
|
+
| **Empty** | "Is it broken, or is there nothing?" | Blank. It must say which, and what to do next |
|
|
45
|
+
| **Error** | "What now?" | A message with no action. Every denial carries its own way out |
|
|
46
|
+
| **Partial** | "Some of it failed" | Ignored entirely; the screen shows a half-truth |
|
|
47
|
+
| **Success** | "It worked" | Silence, so the user does it twice |
|
|
48
|
+
|
|
49
|
+
The empty state is the one that gets skipped, and it is the first state a new user ever sees.
|
|
50
|
+
|
|
51
|
+
## Component boundaries
|
|
52
|
+
|
|
53
|
+
A component earns its existence by **hiding a decision**, not by being reused. Splitting for reuse
|
|
54
|
+
alone produces the worst interfaces: a wrapper around a wrapper, each passing props through, none
|
|
55
|
+
owning anything.
|
|
56
|
+
|
|
57
|
+
Draw the boundary where:
|
|
58
|
+
|
|
59
|
+
- it owns a piece of state nothing outside needs;
|
|
60
|
+
- it can be described in one sentence without "and";
|
|
61
|
+
- swapping its internals changes nothing outside it.
|
|
62
|
+
|
|
63
|
+
Do **not** draw a boundary because a file is long. Long is not a defect; a file that does two
|
|
64
|
+
unrelated things is, and it is usually short.
|
|
65
|
+
|
|
66
|
+
## Forms
|
|
67
|
+
|
|
68
|
+
Forms are where interfaces most often insult people, and the rules are unglamorous:
|
|
69
|
+
|
|
70
|
+
- **Validate on blur, not on keystroke.** Telling someone their email is invalid while they are on
|
|
71
|
+
the third character is scolding them for not having finished.
|
|
72
|
+
- **Never clear what they typed.** Not on error, not on refresh, not on navigation. Their input is
|
|
73
|
+
theirs.
|
|
74
|
+
- **Disable the submit only while submitting**, never as a way of enforcing validity — a disabled
|
|
75
|
+
button with no reason is a dead end.
|
|
76
|
+
- **The error goes next to the field**, and says what to do, not what is wrong.
|
|
77
|
+
|
|
78
|
+
## Optimistic updates
|
|
79
|
+
|
|
80
|
+
Show the result before the server confirms **only** when you can honestly undo it. That means: the
|
|
81
|
+
operation is idempotent, the failure is rare, and reverting it is visible and comprehensible.
|
|
82
|
+
|
|
83
|
+
Optimism on a destructive or an expensive action is a lie you will have to retract in front of the
|
|
84
|
+
user. Deleting, paying, sending — those wait.
|
|
85
|
+
|
|
86
|
+
## Build or adopt
|
|
87
|
+
|
|
88
|
+
Adopting a component costs bundle size, an API you did not design, and an upgrade you will owe
|
|
89
|
+
later. Building costs the accessibility work you will get wrong.
|
|
90
|
+
|
|
91
|
+
The split is not about difficulty, it is about **who is the authority on the behaviour**:
|
|
92
|
+
|
|
93
|
+
- **Adopt** for behaviour with a specification you would otherwise be reimplementing badly: focus
|
|
94
|
+
trapping, combobox semantics, date handling, virtualisation.
|
|
95
|
+
- **Build** for anything that is your product's own logic wearing a UI, or where the library gives
|
|
96
|
+
you 60% and you fight the remaining 40% forever.
|
|
97
|
+
|
|
98
|
+
## Anti-patterns → STOP
|
|
99
|
+
|
|
100
|
+
| If you're about to… | Reality / Fix |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| Put a filter or tab selection in component state | It belongs in the URL. Reload, share and back are part of the interface. |
|
|
103
|
+
| Add a global store because passing props feels tedious | Tedious is not broken. Global state that one component reads is a leak with ceremony. |
|
|
104
|
+
| Ship a screen with only the loaded state | Loading, empty, error and partial are not extras. The empty one is what a new user sees first. |
|
|
105
|
+
| Split a component because the file got long | Length is not the defect. Two unrelated responsibilities are, and they are usually short. |
|
|
106
|
+
| Show a delete as done before the server confirms | Optimism on a destructive action is a lie you will retract in public. |
|
|
107
|
+
| Reach here for how the framework's hooks work | That is `../react/SKILL.md` or `../nextjs/SKILL.md`. This skill decides what to build; those decide how to express it. |
|
|
108
|
+
|
|
109
|
+
## See Also
|
|
110
|
+
|
|
111
|
+
- `../design/SKILL.md` — the visual system these decisions serve.
|
|
112
|
+
- `../motion-craft/SKILL.md` — how the pieces here move once they exist.
|
|
113
|
+
- `../design-loop/SKILL.md` — the graded critique that judges the result.
|
|
114
|
+
- `../accessibility/SKILL.md` — the ground under focus, semantics and forms.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
skill: ui-engineering
|
|
2
|
+
|
|
3
|
+
should_trigger:
|
|
4
|
+
- prompt: "Where should the filter state live — component, context or the URL?"
|
|
5
|
+
why: "The canonical case: the one rule this skill exists to apply."
|
|
6
|
+
- prompt: "How should I split these components? The file is getting big."
|
|
7
|
+
why: "Component boundaries, and the file-length premise is one this skill corrects."
|
|
8
|
+
- prompt: "We only render the loaded state. What else does this screen need?"
|
|
9
|
+
why: "The five states are the checklist this skill owns."
|
|
10
|
+
- prompt: "¿Valido el formulario mientras escribe o al salir del campo?"
|
|
11
|
+
why: "Form behaviour, in Spanish; the trigger is the decision, not the language."
|
|
12
|
+
- prompt: "Should I use a component library for the date picker or build it?"
|
|
13
|
+
why: "Build vs adopt, decided by who owns the behaviour's specification."
|
|
14
|
+
- prompt: "Can we show the delete as done immediately and roll back if it fails?"
|
|
15
|
+
why: "Optimistic updates, and this is the case where the answer is no."
|
|
16
|
+
|
|
17
|
+
should_not_trigger:
|
|
18
|
+
- prompt: "Design the pricing page — layout, type scale, the colour system."
|
|
19
|
+
route_to: "design"
|
|
20
|
+
why: "The visual system is decided before this layer has anything to build."
|
|
21
|
+
- prompt: "My useEffect runs twice in development and I don't understand why."
|
|
22
|
+
route_to: "react"
|
|
23
|
+
why: "Framework mechanics. This skill decides what to build, not how the framework behaves."
|
|
24
|
+
- prompt: "The dropdown animation feels cheap, what duration should it use?"
|
|
25
|
+
route_to: "motion-craft"
|
|
26
|
+
why: "Once the component exists, how it moves is motion craft."
|
|
27
|
+
- prompt: "Grade this dashboard and tell me if it is actually good."
|
|
28
|
+
route_to: "design-loop"
|
|
29
|
+
why: "Judging a finished screen is the critique loop, not the build decisions."
|
|
30
|
+
- prompt: "Set up the Next.js App Router with server actions for this project."
|
|
31
|
+
route_to: "nextjs"
|
|
32
|
+
why: "Stack setup is the stack skill's; this one is framework-agnostic by construction."
|
|
33
|
+
|
|
34
|
+
capability:
|
|
35
|
+
- prompt: "Our search page keeps its query and filters in component state. Users complain the back button loses everything. What is wrong and what is the fix?"
|
|
36
|
+
why: "The single most expensive instance of the one rule, stated as a real symptom rather than as theory."
|
|
37
|
+
- prompt: "This list screen shows a spinner, then rows. What states are we missing and why does each matter?"
|
|
38
|
+
why: "Forces the five-state checklist, including the empty state that a new user sees first."
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: variant-explorer
|
|
3
|
+
description: "Use when someone wants OPTIONS rather than an answer — 'show me a few versions', picking between approaches by seeing them. Genuinely different candidates, cheap to throw away, trade-off named. NOT the critique that judges one (`design-loop`), NOT the spec (`specify`)."
|
|
4
|
+
tags: [prototyping, divergence, options, exploration, decision]
|
|
5
|
+
recommends: [design-loop]
|
|
6
|
+
profiles: [full]
|
|
7
|
+
origin: risco
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Variant explorer — several real options, cheap enough to discard
|
|
11
|
+
|
|
12
|
+
"Show me a few versions and I'll pick" is a different request from "build this". It asks for
|
|
13
|
+
**divergence**: several candidates that are actually different, each cheap enough that throwing it
|
|
14
|
+
away costs nothing, with the difference between them stated out loud so a human can decide.
|
|
15
|
+
|
|
16
|
+
Done badly it produces the thing that wastes the most time in creative work: three variations of one
|
|
17
|
+
idea, dressed up as a choice.
|
|
18
|
+
|
|
19
|
+
## The one rule
|
|
20
|
+
|
|
21
|
+
**Variants must differ in a decision, not in a detail.** If two candidates could both be reached by
|
|
22
|
+
editing the other, there is one candidate.
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Not variants: the same layout in blue, in green, in dark
|
|
26
|
+
Variants: a single scrolling page · a three-step wizard · a table with inline editing
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The test before showing anything: **name the decision each variant is taking.** If you cannot name
|
|
30
|
+
it, or two share it, that set is not ready.
|
|
31
|
+
|
|
32
|
+
## How many, and how finished
|
|
33
|
+
|
|
34
|
+
- **Three.** Two reads as a false dichotomy; five past the point where a person can hold them side by
|
|
35
|
+
side. Three forces the middle option to justify itself.
|
|
36
|
+
- **Finished enough to judge the decision, and no more.** If the decision is about structure, the
|
|
37
|
+
copy can be placeholder. If it is about tone, the layout can be crude. Polish that is not being
|
|
38
|
+
judged is polish you are asking someone to ignore.
|
|
39
|
+
- **Comparable.** Same content, same constraints, same fidelity across all three. A variant that is
|
|
40
|
+
prettier wins for reasons nobody chose.
|
|
41
|
+
|
|
42
|
+
## Making them genuinely different
|
|
43
|
+
|
|
44
|
+
The failure mode is anchoring: the first idea contaminates the rest. Force divergence by generating
|
|
45
|
+
each from a different starting constraint.
|
|
46
|
+
|
|
47
|
+
| Angle | Ask |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| **Cheapest** | What if this had to ship this week? |
|
|
50
|
+
| **Boldest** | What if we ignored what similar products do? |
|
|
51
|
+
| **Fewest steps** | What if the user only did one thing? |
|
|
52
|
+
| **Familiar** | What if it worked exactly like the thing they already know? |
|
|
53
|
+
| **Inverted** | What if the primary and secondary swapped? |
|
|
54
|
+
|
|
55
|
+
Pick three angles that are actually in tension for this problem. Do not run all five — the point is
|
|
56
|
+
tension, not coverage.
|
|
57
|
+
|
|
58
|
+
## Presenting them so a decision is possible
|
|
59
|
+
|
|
60
|
+
Each candidate carries three things, and no more:
|
|
61
|
+
|
|
62
|
+
1. **What decision it takes** — one sentence, in the user's terms.
|
|
63
|
+
2. **What it costs** — the honest downside, not a hedge. A variant with no downside is one you have
|
|
64
|
+
not thought about.
|
|
65
|
+
3. **What it would take to be wrong** — the fact that would kill it.
|
|
66
|
+
|
|
67
|
+
Then: **a recommendation.** Presenting three options and standing back is not neutrality, it is
|
|
68
|
+
handing the work back. Say which one you would pick and why, and make it easy to overrule.
|
|
69
|
+
|
|
70
|
+
## When this is the wrong skill
|
|
71
|
+
|
|
72
|
+
- The answer is already known and only needs building → build it. Manufacturing options to look
|
|
73
|
+
thorough wastes everyone's time.
|
|
74
|
+
- The decision is reversible and cheap → just pick one and ship. Exploration is for decisions that
|
|
75
|
+
are expensive to unwind.
|
|
76
|
+
- What is needed is a **judgement on one thing** → that is critique, not divergence.
|
|
77
|
+
|
|
78
|
+
## Anti-patterns → STOP
|
|
79
|
+
|
|
80
|
+
| If you're about to… | Reality / Fix |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| Show the same idea in three colours | Those are not variants. Name the decision each takes; if you cannot, you have one. |
|
|
83
|
+
| Polish one candidate more than the others | It will win on polish, not on the decision. Same fidelity, always. |
|
|
84
|
+
| Present five or six to be thorough | Past three, they stop being compared and start being skimmed. |
|
|
85
|
+
| Give options with no recommendation | Standing back is not neutrality; it hands the work back to the person who asked. |
|
|
86
|
+
| Explore a decision that is cheap to reverse | Just ship one. Divergence is for what is expensive to unwind. |
|
|
87
|
+
| Keep iterating on the winner here | Once one is chosen, refining it is `../design-loop/SKILL.md` or the relevant build skill. |
|
|
88
|
+
|
|
89
|
+
## See Also
|
|
90
|
+
|
|
91
|
+
- `../design-loop/SKILL.md` — the graded critique that takes over once a direction is chosen.
|
|
92
|
+
- `../specify/SKILL.md` — when what is missing is an agreed WHAT/WHY, not options.
|
|
93
|
+
- `../design/SKILL.md` — building the chosen direction properly.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
skill: variant-explorer
|
|
2
|
+
|
|
3
|
+
should_trigger:
|
|
4
|
+
- prompt: "Show me a few versions of this onboarding and I'll pick one."
|
|
5
|
+
why: "The canonical phrasing: options to choose from, not an answer."
|
|
6
|
+
- prompt: "Give me three directions for how this could work before we commit."
|
|
7
|
+
why: "Explicit divergence ahead of an expensive decision."
|
|
8
|
+
- prompt: "Enséñame varias opciones de cómo montar el checkout, que ya elijo yo."
|
|
9
|
+
why: "Same request in Spanish; the trigger is the ask for options."
|
|
10
|
+
- prompt: "I can't decide between a wizard and a single page. Can I see both?"
|
|
11
|
+
why: "Two named approaches where seeing them is what settles it."
|
|
12
|
+
- prompt: "What are some different ways we could structure this dashboard?"
|
|
13
|
+
why: "Open-ended exploration of structural decisions, which is exactly divergence."
|
|
14
|
+
- prompt: "Before we build it, I want to compare a couple of approaches side by side."
|
|
15
|
+
why: "Comparability is the property this skill guarantees."
|
|
16
|
+
|
|
17
|
+
should_not_trigger:
|
|
18
|
+
- prompt: "Review this checkout screen and grade it."
|
|
19
|
+
route_to: "design-loop"
|
|
20
|
+
why: "Judging one finished thing is critique, the opposite of divergence."
|
|
21
|
+
- prompt: "We need to agree what this feature is for before anyone builds it."
|
|
22
|
+
route_to: "specify"
|
|
23
|
+
why: "A missing WHAT/WHY is a spec problem; options would be premature."
|
|
24
|
+
- prompt: "Build the pricing page we agreed on last week."
|
|
25
|
+
route_to: "design"
|
|
26
|
+
why: "The decision is already made; manufacturing options would waste the time this skill is meant to save."
|
|
27
|
+
- prompt: "Which colour should the primary button be?"
|
|
28
|
+
route_to: "design"
|
|
29
|
+
why: "Cheap and reversible, and a detail rather than a decision — the explicit non-case here."
|
|
30
|
+
- prompt: "Run an A/B test to see which headline converts better."
|
|
31
|
+
route_to: "ab-testing"
|
|
32
|
+
why: "Measuring a winner with users is experimentation, not showing a human candidates to choose from."
|
|
33
|
+
|
|
34
|
+
capability:
|
|
35
|
+
- prompt: "Give me three options for this signup flow. Make sure they are actually different, not the same thing restyled."
|
|
36
|
+
why: "Goes straight at the failure mode this skill exists to prevent, and demands the decision behind each be nameable."
|
|
37
|
+
- prompt: "Here are three variants I made. Are these real alternatives or the same idea three times?"
|
|
38
|
+
why: "Applies the one rule as a test on someone else's set, which is where it is hardest and most useful."
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
const REVIEW_TOOLS = Object.freeze(['read', 'search']);
|
|
2
|
+
const RESOLVER_TOOLS = Object.freeze(['read', 'search', 'edit', 'shell']);
|
|
3
|
+
|
|
4
|
+
const reviewerBody = ({ label, scope, probes, escalation = '' }) => `You are the ${label} reviewer. Review the requested diff or pull request; do not edit files.
|
|
5
|
+
|
|
6
|
+
Boundary: ${scope}
|
|
7
|
+
|
|
8
|
+
Work from changed lines outward just far enough to verify callers, imports, guards, and tests. Record an **attack list** even when every attack is clean. For each proposed finding, answer before reporting:
|
|
9
|
+
1. What exact changed line supports it?
|
|
10
|
+
2. What concrete input and state produce what wrong result?
|
|
11
|
+
3. Which caller, import, and relevant test did you inspect?
|
|
12
|
+
4. Why do existing guards not reduce the severity?
|
|
13
|
+
|
|
14
|
+
HIGH and CRITICAL findings require the exact line and the full failure path. If either is missing, lower the severity or omit the finding. Prefer a clean verdict with a useful attack list over speculative volume.
|
|
15
|
+
|
|
16
|
+
Stack attacks:
|
|
17
|
+
${probes.map((probe) => `- ${probe}`).join('\n')}
|
|
18
|
+
${escalation ? `\n${escalation}\n` : ''}
|
|
19
|
+
Return: scope, attack list, findings ordered by severity, and verdict. A finding names file:line, failure mode, evidence, and the smallest test that would expose it.`;
|
|
20
|
+
|
|
21
|
+
const resolverBody = ({ label, scope, signals, attempts = 3 }) => `You are the ${label} build resolver. Diagnose and repair one failing build with the smallest stack-correct change.
|
|
22
|
+
|
|
23
|
+
Boundary: ${scope}
|
|
24
|
+
|
|
25
|
+
1. Reproduce the named failure and preserve its exact command and first causal diagnostic.
|
|
26
|
+
2. Trace that diagnostic to configuration, dependency, generated code, compiler, linker, or runtime setup.
|
|
27
|
+
3. Change only what is needed for that cause; do not refactor, upgrade broadly, or clean unrelated warnings.
|
|
28
|
+
4. Re-run the narrow failing command, then the nearest relevant test/build gate.
|
|
29
|
+
5. Stop after ${attempts} unsuccessful repair attempts or as soon as the next step requires a refactor or product choice.
|
|
30
|
+
|
|
31
|
+
Signals to distinguish:
|
|
32
|
+
${signals.map((signal) => `- ${signal}`).join('\n')}
|
|
33
|
+
|
|
34
|
+
Return the root cause, commands run, files changed, proof of the result, and any remaining blocker.`;
|
|
35
|
+
|
|
36
|
+
const reviewer = ({ name, label, skills, not, probes, should, shouldNot, tier = 'balanced', escalation }) => ({
|
|
37
|
+
name,
|
|
38
|
+
role: 'reviewer',
|
|
39
|
+
tier,
|
|
40
|
+
tools: [...REVIEW_TOOLS],
|
|
41
|
+
skills,
|
|
42
|
+
desc: `${label} reviewer: inspects changed code for stack-specific failure modes and reports evidence without editing. NOT ${not}.`,
|
|
43
|
+
routing: { should, shouldNot },
|
|
44
|
+
body: reviewerBody({ label, scope: `Use for ${skills.join(' or ')} code. NOT ${not}.`, probes, escalation }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const resolver = ({ name, label, skills, not, signals, should, shouldNot, tier = 'balanced' }) => ({
|
|
48
|
+
name,
|
|
49
|
+
role: 'build-resolver',
|
|
50
|
+
tier,
|
|
51
|
+
tools: [...RESOLVER_TOOLS],
|
|
52
|
+
skills,
|
|
53
|
+
desc: `${label} build resolver: reproduces a failing build and applies the minimum verified repair. NOT ${not}.`,
|
|
54
|
+
routing: { should, shouldNot },
|
|
55
|
+
body: resolverBody({ label, scope: `Use for ${skills.join(' or ')} build failures. NOT ${not}.`, signals }),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const REVIEWERS = [
|
|
59
|
+
reviewer({
|
|
60
|
+
name: 'django-reviewer', label: 'Django', skills: ['django'], not: 'general Python review or build repair',
|
|
61
|
+
probes: ['QuerySet evaluation, N+1 reads, and accidental full-table work.', 'Transaction boundaries, row locking, retries, and side effects inside atomic blocks.', 'Migration reversibility, historical models, nullable transitions, and deploy ordering.', 'Authorization at object/query boundaries rather than only at the view.'],
|
|
62
|
+
should: ['Review a Django ORM diff for query and transaction defects.'], shouldNot: ['Fix a failing Django migration command.'],
|
|
63
|
+
}),
|
|
64
|
+
reviewer({
|
|
65
|
+
name: 'java-reviewer', label: 'Java and Spring', skills: ['java', 'spring-boot'], not: 'Android/Kotlin review or generic build repair',
|
|
66
|
+
probes: ['Nullability and Optional contracts across public boundaries.', 'Equality, hashing, mutability, and collection aliasing.', 'Spring proxy boundaries: transactions, self-invocation, scopes, and configuration binding.', 'Thread ownership, blocking work, interruption, and resource closure.'],
|
|
67
|
+
should: ['Review a Spring transaction change.'], shouldNot: ['Repair a Gradle dependency resolution failure.'],
|
|
68
|
+
}),
|
|
69
|
+
reviewer({
|
|
70
|
+
name: 'kotlin-reviewer', label: 'Kotlin Android', skills: ['kotlin-android'], not: 'Java server review or Gradle repair',
|
|
71
|
+
probes: ['Coroutine scope ownership, cancellation, dispatcher choice, and exception propagation.', 'Lifecycle collection, recomposition stability, and state restoration.', 'Nullable/platform types and sealed-state exhaustiveness.', 'Context/activity retention and Android resource lifetime.'],
|
|
72
|
+
should: ['Review a coroutine-driven Android screen.'], shouldNot: ['Resolve an Android Gradle Plugin mismatch.'],
|
|
73
|
+
}),
|
|
74
|
+
reviewer({
|
|
75
|
+
name: 'flutter-reviewer', label: 'Flutter', skills: ['flutter'], not: 'native Android review or Flutter build repair',
|
|
76
|
+
probes: ['Widget identity, keys, rebuild scope, and stale BuildContext use.', 'Controller, subscription, isolate, and animation disposal.', 'Async mounted checks and state changes after teardown.', 'Platform-channel contracts and divergent iOS/Android behavior.'],
|
|
77
|
+
should: ['Review a Flutter widget state diff.'], shouldNot: ['Fix a CocoaPods build failure.'],
|
|
78
|
+
}),
|
|
79
|
+
reviewer({
|
|
80
|
+
name: 'react-reviewer', label: 'React and Next.js', skills: ['react', 'nextjs'], not: 'Vue review or frontend build repair',
|
|
81
|
+
probes: ['Render purity, hook dependency identity, stale closures, and effect cleanup.', 'Server/client boundaries, serialization, caching, and request-scoped data.', 'Concurrent rendering races, transitions, hydration, and optimistic rollback.', 'Keys, controlled state, accessibility semantics, and error/loading states.'],
|
|
82
|
+
should: ['Review a Next.js server/client boundary.'], shouldNot: ['Repair a webpack or Turbopack build.'],
|
|
83
|
+
}),
|
|
84
|
+
reviewer({
|
|
85
|
+
name: 'rust-reviewer', label: 'Rust', skills: ['rust'], not: 'C++ review or Cargo build repair',
|
|
86
|
+
probes: ['Ownership workarounds that hide lifetime or aliasing defects.', 'Unsafe blocks: stated invariant, pointer provenance, layout, and synchronization.', 'Error propagation, panic reachability, and poisoned/shared state.', 'Async cancellation, Send/Sync assumptions, locks across await, and task lifetime.'],
|
|
87
|
+
should: ['Review an unsafe Rust boundary.'], shouldNot: ['Fix a Cargo feature conflict.'],
|
|
88
|
+
}),
|
|
89
|
+
reviewer({
|
|
90
|
+
name: 'swift-reviewer', label: 'Swift iOS', skills: ['swift-ios'], not: 'Kotlin review or Xcode build repair',
|
|
91
|
+
probes: ['Actor isolation, Sendable claims, cancellation, and main-thread ownership.', 'Reference cycles, escaping closures, and resource lifetime.', 'Value/reference semantics, copy-on-write, and mutation through shared state.', 'View identity, navigation state, persistence, and platform availability.'],
|
|
92
|
+
should: ['Review Swift concurrency changes in an iOS app.'], shouldNot: ['Resolve a signing or linker failure.'],
|
|
93
|
+
}),
|
|
94
|
+
reviewer({
|
|
95
|
+
name: 'go-reviewer', label: 'Go', skills: ['go'], not: 'generic service review or Go build repair',
|
|
96
|
+
probes: ['Goroutine lifetime, cancellation ownership, channel closure, and races.', 'Error wrapping and identity where callers use errors.Is or errors.As.', 'Interface nil values, slice/map aliasing, loop capture, and deferred cleanup lifetime.', 'API and doc-comment contracts, zero values, receiver choice, and lock copying.'],
|
|
97
|
+
should: ['Review Go concurrency and error handling.'], shouldNot: ['Repair a missing Go module or compile error.'],
|
|
98
|
+
}),
|
|
99
|
+
reviewer({
|
|
100
|
+
name: 'cpp-reviewer', label: 'C++', skills: ['cpp'], not: 'Rust review or compiler/linker repair',
|
|
101
|
+
probes: ['Ownership expressed through RAII and any raw pointer whose lifetime is unstated.', 'Dangling views/references, moved-from values, invalidation, and destructor behavior.', 'Bounds, narrowing, signedness, undefined behavior, and exception guarantees.', 'Synchronization, data races, virtual destruction, and unsafe casts.'],
|
|
102
|
+
should: ['Review a C++ ownership or lifetime diff.'], shouldNot: ['Resolve a CMake or linker failure.'],
|
|
103
|
+
}),
|
|
104
|
+
reviewer({
|
|
105
|
+
name: 'vue-reviewer', label: 'Vue and Nuxt', skills: ['vue-nuxt'], not: 'React review or Vue build repair',
|
|
106
|
+
probes: ['Reactive identity, destructuring, watcher cleanup, and computed side effects.', 'SSR/client divergence, hydration, route middleware, and request-scoped state.', 'Composable lifetime, provide/inject contracts, and error/loading states.', 'Template keys, accessibility semantics, and unsafe rendered content.'],
|
|
107
|
+
should: ['Review a Nuxt SSR and hydration diff.'], shouldNot: ['Fix a Vite build.'],
|
|
108
|
+
}),
|
|
109
|
+
reviewer({
|
|
110
|
+
name: 'csharp-reviewer', label: 'C# and .NET', skills: ['csharp-dotnet'], not: 'Java review or dotnet build repair',
|
|
111
|
+
probes: ['Async cancellation, sync-over-async, disposal, and scoped service lifetime.', 'Nullable annotations versus runtime validation and serializer behavior.', 'LINQ query translation, repeated enumeration, tracking, and transaction scope.', 'Records/equality, mutable shared state, authorization, and configuration binding.'],
|
|
112
|
+
should: ['Review an ASP.NET or EF Core change.'], shouldNot: ['Repair a NuGet restore failure.'],
|
|
113
|
+
}),
|
|
114
|
+
reviewer({
|
|
115
|
+
name: 'php-reviewer', label: 'PHP and Laravel', skills: ['laravel', 'php'], not: 'Django review or Composer/build repair',
|
|
116
|
+
probes: ['Request validation, mass assignment, policy scope, and tenant isolation.', 'Eloquent loading, query count, transactions, and queued work after commit.', 'PHP type coercion, nullable boundaries, array shape assumptions, and error conversion.', 'Migration deploy ordering, cache/config behavior, and serialization.'],
|
|
117
|
+
should: ['Review Laravel authorization and ORM code.'], shouldNot: ['Resolve a Composer dependency error.'],
|
|
118
|
+
}),
|
|
119
|
+
reviewer({
|
|
120
|
+
name: 'typescript-reviewer', label: 'TypeScript', skills: ['typescript'], not: 'framework-specific UI review or TypeScript build repair',
|
|
121
|
+
probes: ['Runtime validation at boundaries where static types disappear.', 'Unsound assertions, any/unknown narrowing, variance, and optional-property semantics.', 'Promise ownership, rejection paths, cancellation, and event-listener cleanup.', 'Module/export contracts and type/value divergence across build targets.'],
|
|
122
|
+
should: ['Review a TypeScript library API diff.'], shouldNot: ['Fix tsconfig or bundler errors.'],
|
|
123
|
+
}),
|
|
124
|
+
reviewer({
|
|
125
|
+
name: 'fastapi-reviewer', label: 'FastAPI', skills: ['fastapi'], not: 'generic Python review or environment repair',
|
|
126
|
+
probes: ['Dependency lifetime and cleanup, override leakage, and request-scoped state.', 'Async blocking, cancellation, background task ownership, and exception mapping.', 'Pydantic input/output boundaries, partial updates, and response data exposure.', 'Authorization per resource, transaction completion, and OpenAPI contract drift.'],
|
|
127
|
+
should: ['Review a FastAPI endpoint and dependency change.'], shouldNot: ['Repair a Python package installation.'],
|
|
128
|
+
}),
|
|
129
|
+
reviewer({
|
|
130
|
+
name: 'python-reviewer', label: 'Python', skills: ['python'], not: 'FastAPI/Django-specific review or environment repair',
|
|
131
|
+
probes: ['Async tasks that are never awaited, cancellation loss, and TaskGroup failure propagation.', 'Mutable defaults, late binding, descriptor/protocol behavior, and truthiness edge cases.', 'Context-manager and iterator cleanup, exception chaining, and generator finalization.', 'Shared mutable state, import-time effects, typing/runtime gaps, and process/thread boundaries.'],
|
|
132
|
+
should: ['Review Python language and asyncio correctness.'], shouldNot: ['Review Django ORM behavior.'],
|
|
133
|
+
}),
|
|
134
|
+
reviewer({
|
|
135
|
+
name: 'postgres-reviewer', label: 'PostgreSQL', skills: ['postgresdb'], not: 'application ORM review or migration execution',
|
|
136
|
+
probes: ['Lock strength/order, long transactions, deadlocks, and concurrent DDL behavior.', 'Index/operator compatibility, selectivity, null semantics, and query-plan regressions.', 'RLS policy coverage, SECURITY DEFINER/search_path, grants, and tenant isolation.', 'Migration reversibility, constraint validation, backfill batching, and replication effects.'],
|
|
137
|
+
should: ['Review a PostgreSQL migration and policy diff.'], shouldNot: ['Run or repair application migrations.'],
|
|
138
|
+
}),
|
|
139
|
+
reviewer({
|
|
140
|
+
name: 'rag-reviewer', label: 'RAG', skills: ['rag'], not: 'general ML training review or retrieval implementation',
|
|
141
|
+
probes: ['Evaluation set leakage, temporal leakage, and mismatch between offline and live queries.', 'Chunk/metadata identity, permission filtering before retrieval, and deletion propagation.', 'Recall/rerank trade-offs, empty retrieval, citation grounding, and abstention.', 'Embedding/model/index version compatibility and rollback behavior.'],
|
|
142
|
+
should: ['Review a retrieval pipeline for leakage and grounding.'], shouldNot: ['Tune a neural training loop.'],
|
|
143
|
+
}),
|
|
144
|
+
reviewer({
|
|
145
|
+
name: 'mle-reviewer', label: 'production ML', skills: ['machine-learning'], not: 'notebook style review or model training implementation', tier: 'heavy',
|
|
146
|
+
probes: ['Temporal/entity leakage in splits and features available only after prediction time.', 'Training-serving schema skew, data contracts, null/category drift, and feature ownership.', 'Promotion gates that fail open, canary comparability, monitoring delay, and rollback without retraining.', 'Reproducible artifacts: data/code/model lineage, seeds, environment, and deterministic evaluation.'],
|
|
147
|
+
should: ['Review an ML promotion and rollback pipeline.'], shouldNot: ['Implement or tune the model.'],
|
|
148
|
+
}),
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const RESOLVERS = [
|
|
152
|
+
resolver({ name: 'django-build-resolver', label: 'Django', skills: ['django'], not: 'ORM performance review or feature refactoring', signals: ['Migration graph conflicts versus unapplied migrations.', 'Settings/import errors versus database connectivity.', 'Static collection/template failures versus application tests.'], should: ['Fix a failing Django migrate/check command.'], shouldNot: ['Review an ORM diff for N+1 queries.'] }),
|
|
153
|
+
resolver({ name: 'java-build-resolver', label: 'Java and Spring', skills: ['java', 'spring-boot'], not: 'code review or broad dependency upgrades', signals: ['Compiler diagnostics versus annotation/generated-source failures.', 'Maven/Gradle resolution versus toolchain/JDK mismatch.', 'Spring context/bootstrap failures versus test assertion failures.'], should: ['Fix a Gradle/Maven/Spring build failure.'], shouldNot: ['Review transaction correctness.'] }),
|
|
154
|
+
resolver({ name: 'kotlin-build-resolver', label: 'Kotlin Android', skills: ['kotlin-android'], not: 'UI/coroutine review or project modernization', signals: ['Kotlin/AGP/Gradle/JDK compatibility.', 'KSP/KAPT/generated source failures.', 'Manifest/resource merge versus compiler diagnostics.'], should: ['Repair an Android Gradle build.'], shouldNot: ['Review Compose state.'] }),
|
|
155
|
+
resolver({ name: 'flutter-build-resolver', label: 'Flutter', skills: ['flutter'], not: 'widget review or package modernization', signals: ['Dart analyzer/compile errors versus generated-code drift.', 'CocoaPods/Xcode signing versus Android Gradle failures.', 'Plugin platform constraints versus app code failures.'], should: ['Repair a Flutter build failure.'], shouldNot: ['Review widget lifecycle.'] }),
|
|
156
|
+
resolver({ name: 'react-build-resolver', label: 'React and Next.js', skills: ['react', 'nextjs'], not: 'UI review or broad package upgrades', signals: ['Type/lint failure versus bundler/module resolution.', 'Server/client boundary or static rendering diagnostics.', 'Environment/config presence versus application logic.'], should: ['Repair a Next.js or React production build.'], shouldNot: ['Review hydration correctness.'] }),
|
|
157
|
+
resolver({ name: 'rust-build-resolver', label: 'Rust', skills: ['rust'], not: 'unsafe-code review or dependency modernization', signals: ['Borrow/type diagnostics versus feature unification.', 'Build-script/native library/linker failures.', 'Toolchain/edition mismatch versus source error.'], should: ['Repair a Cargo build failure.'], shouldNot: ['Review unsafe invariants.'] }),
|
|
158
|
+
resolver({ name: 'swift-build-resolver', label: 'Swift iOS', skills: ['swift-ios'], not: 'concurrency review or signing-policy changes', signals: ['Swift compiler/generic diagnostics versus package resolution.', 'Linker/module map/generated interface failure.', 'Simulator/device availability versus signing/provisioning.'], should: ['Repair an Xcode or SwiftPM build.'], shouldNot: ['Review actor isolation.'] }),
|
|
159
|
+
resolver({ name: 'go-build-resolver', label: 'Go', skills: ['go'], not: 'concurrency review or module-wide upgrades', signals: ['Compiler/type error versus generated code drift.', 'Module/version/sum failure versus private module credentials.', 'CGO/toolchain/linker failure versus test failure.'], should: ['Repair a go build failure.'], shouldNot: ['Review goroutine lifetime.'] }),
|
|
160
|
+
resolver({ name: 'cpp-build-resolver', label: 'C++', skills: ['cpp'], not: 'ownership review or build-system modernization', signals: ['Compiler diagnostic versus template instantiation cause.', 'CMake/configuration/generator failure versus source failure.', 'Undefined reference/ABI/library order versus runtime test failure.'], should: ['Repair a CMake/compiler/linker failure.'], shouldNot: ['Review C++ lifetime safety.'] }),
|
|
161
|
+
resolver({ name: 'pytorch-build-resolver', label: 'PyTorch', skills: ['machine-learning'], not: 'model-quality review or training redesign', signals: ['Python package/import mismatch versus compiled extension ABI.', 'CUDA/driver/toolkit/architecture incompatibility.', 'Distributed launcher/environment failure versus model code failure.'], should: ['Repair a PyTorch environment or extension build.'], shouldNot: ['Review data leakage or model promotion.'] }),
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const SPEC_MINER = {
|
|
165
|
+
name: 'spec-miner',
|
|
166
|
+
role: 'spec-miner',
|
|
167
|
+
tier: 'heavy',
|
|
168
|
+
tools: ['read', 'search', 'edit'],
|
|
169
|
+
skills: [],
|
|
170
|
+
desc: 'Brownfield spec miner: extracts an anchored SDD draft from existing code under a bounded read budget. NOT feature planning or overwriting an approved spec.',
|
|
171
|
+
routing: {
|
|
172
|
+
should: ['Extract an initial SDD spec from an existing repository with no specs.'],
|
|
173
|
+
shouldNot: ['Design a new feature or amend an approved spec in place.'],
|
|
174
|
+
},
|
|
175
|
+
body: `You are the brownfield spec miner. Produce an **extracted draft**, never a claim that undocumented intent is approved.
|
|
176
|
+
|
|
177
|
+
Read at most 40 files or 20,000 lines. Start from entry points, public interfaces, tests, schemas, migrations, configuration, and user-facing documentation. For every inferred invariant, cite a stable repository anchor (symbol plus path; line only as a convenience). Separate observed behavior, inferred requirement, contradiction, and unknown.
|
|
178
|
+
|
|
179
|
+
Write a new SDD-shaped draft with problem, users, observable behavior, invariants, non-goals, acceptance criteria, source anchors, and open points. Mark status extracted-draft. If the intended slug already has an approved spec, do not edit it: propose a new slug or a revision artifact. Do not plan implementation or invent product intent absent from evidence.
|
|
180
|
+
|
|
181
|
+
Return the read budget used, artifacts inspected, confidence per requirement, and the draft path.`,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const STACK_AGENTS = Object.freeze([...REVIEWERS, ...RESOLVERS, SPEC_MINER]);
|
|
185
|
+
|
|
186
|
+
export const STACK_AGENT_BY_SKILL = Object.freeze(Object.fromEntries(
|
|
187
|
+
['django', 'java', 'spring-boot', 'kotlin-android', 'flutter', 'react', 'nextjs', 'rust', 'swift-ios', 'go', 'cpp', 'vue-nuxt', 'csharp-dotnet', 'laravel', 'php', 'typescript', 'fastapi', 'python', 'postgresdb', 'rag', 'machine-learning']
|
|
188
|
+
.map((skill) => [skill, STACK_AGENTS.filter((agent) => agent.skills.includes(skill)).map((agent) => agent.name)]),
|
|
189
|
+
));
|
|
190
|
+
|
|
191
|
+
export const stackAgents = () => STACK_AGENTS.map((agent) => ({ ...agent, tools: [...agent.tools], skills: [...agent.skills], routing: { should: [...agent.routing.should], shouldNot: [...agent.routing.shouldNot] } }));
|
|
192
|
+
export const stackAgentNames = () => STACK_AGENTS.map((agent) => agent.name);
|
|
193
|
+
export const stackAgentByName = (name) => STACK_AGENTS.find((agent) => agent.name === name);
|
|
194
|
+
|
|
195
|
+
export function resolveStackAgentNames(skillIds = [], explicitAgentIds = []) {
|
|
196
|
+
const names = new Set();
|
|
197
|
+
for (const skill of skillIds) for (const name of STACK_AGENT_BY_SKILL[skill] || []) names.add(name);
|
|
198
|
+
for (const name of explicitAgentIds) if (stackAgentByName(name)) names.add(name);
|
|
199
|
+
return [...names].sort();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function validateAgentCatalog(definitions = STACK_AGENTS) {
|
|
203
|
+
const errors = [];
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
const allowedRoles = new Set(['reviewer', 'build-resolver', 'spec-miner']);
|
|
206
|
+
for (const agent of definitions) {
|
|
207
|
+
const prefix = agent?.name || '<unnamed>';
|
|
208
|
+
if (!agent?.name || seen.has(agent.name)) errors.push(`${prefix}: unique name required`);
|
|
209
|
+
seen.add(agent?.name);
|
|
210
|
+
if (!agent?.desc?.includes('NOT ')) errors.push(`${prefix}: description requires a NOT boundary`);
|
|
211
|
+
if (!allowedRoles.has(agent?.role)) errors.push(`${prefix}: invalid role`);
|
|
212
|
+
if (!['balanced', 'heavy'].includes(agent?.tier)) errors.push(`${prefix}: tier must be balanced or heavy`);
|
|
213
|
+
if (!agent?.routing?.should?.length || !agent?.routing?.shouldNot?.length) errors.push(`${prefix}: routing cases required`);
|
|
214
|
+
if (Buffer.byteLength(agent?.body || '', 'utf8') > 6500) errors.push(`${prefix}: body exceeds 6500 bytes`);
|
|
215
|
+
if (/everything[- ]claude|affaan|worldflow|\.claude\/plugins\/ecc/iu.test(`${agent?.desc || ''}\n${agent?.body || ''}`)) errors.push(`${prefix}: foreign project vocabulary`);
|
|
216
|
+
if (agent?.role === 'reviewer' && JSON.stringify(agent.tools) !== JSON.stringify(REVIEW_TOOLS)) errors.push(`${prefix}: reviewer tools must be read-only`);
|
|
217
|
+
if (agent?.role === 'build-resolver' && JSON.stringify(agent.tools) !== JSON.stringify(RESOLVER_TOOLS)) errors.push(`${prefix}: resolver tools must be bounded`);
|
|
218
|
+
}
|
|
219
|
+
return errors;
|
|
220
|
+
}
|